Skip to content

Commit

Permalink
feat(core): allow using AsyncLocalStorage for request context
Browse files Browse the repository at this point in the history
Closes #575
  • Loading branch information
B4nan committed Sep 15, 2020
1 parent 682189b commit 47cd9a5
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 9 deletions.
19 changes: 19 additions & 0 deletions docs/docs/async-local-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Using `AsyncLocalStorage`
---

By default, `domain` api use used in the `RequestContext` helper. Since v4.0.3,
you can use the new `AsyncLocalStorage` too, if you are on up to date node version:

```typescript
const storage = new AsyncLocalStorage<EntityManager>();

const orm = await MikroORM.init({
context: () => storage.getStore(),
// ...
});

app.use((req, res, next) => {
storage.run(orm.em.fork(true, true), next);
});
```
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module.exports = {
'entity-constructors',
'multiple-schemas',
'using-bigint-pks',
'async-local-storage',
'custom-driver',
],
'Example Integrations': [
Expand Down
19 changes: 19 additions & 0 deletions docs/versioned_docs/version-4.0/async-local-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Using `AsyncLocalStorage`
---

By default, `domain` api use used in the `RequestContext` helper. Since v4.0.3,
you can use the new `AsyncLocalStorage` too, if you are on up to date node version:

```typescript
const storage = new AsyncLocalStorage<EntityManager>();

const orm = await MikroORM.init({
context: () => storage.getStore(),
// ...
});

app.use((req, res, next) => {
storage.run(orm.em.fork(true, true), next);
});
```
4 changes: 4 additions & 0 deletions docs/versioned_sidebars/version-4.0-sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@
"type": "doc",
"id": "version-4.0/using-bigint-pks"
},
{
"type": "doc",
"id": "version-4.0/async-local-storage"
},
{
"type": "doc",
"id": "version-4.0/custom-driver"
Expand Down
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@
"fs-extra": "^9.0.1",
"globby": "^11.0.1",
"reflect-metadata": "^0.1.13",
"strip-json-comments": "^3.1.1",
"uuid": "^8.3.0"
"strip-json-comments": "^3.1.1"
},
"peerDependencies": {
"@mikro-orm/entity-generator": "^4.0.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/EntityManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { v4 as uuid } from 'uuid';
import { inspect } from 'util';

import { Configuration, QueryHelper, RequestContext, Utils } from './utils';
import { Configuration, QueryHelper, Utils } from './utils';
import { AssignOptions, EntityAssigner, EntityFactory, EntityLoader, EntityRepository, EntityValidator, IdentifiedReference, Reference } from './entity';
import { UnitOfWork } from './unit-of-work';
import { CountOptions, DeleteOptions, EntityManagerType, FindOneOptions, FindOneOrFailOptions, FindOptions, IDatabaseDriver, UpdateOptions } from './drivers';
Expand All @@ -19,7 +18,8 @@ import { OptimisticLockError, ValidationError } from './errors';
*/
export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {

readonly id = uuid();
private static counter = 1;
readonly id = EntityManager.counter++;
private readonly validator = new EntityValidator(this.config.get('strict'));
private readonly repositoryMap: Dictionary<EntityRepository<AnyEntity>> = {};
private readonly entityLoader: EntityLoader = new EntityLoader(this);
Expand Down Expand Up @@ -647,15 +647,15 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*/
getUnitOfWork(): UnitOfWork {
const em = this.useContext ? (RequestContext.getEntityManager() || this) : this;
const em = this.useContext ? (this.config.get('context')() || this) : this;
return em.unitOfWork;
}

/**
* Gets the EntityFactory used by the EntityManager.
*/
getEntityFactory(): EntityFactory {
const em = this.useContext ? (RequestContext.getEntityManager() || this) : this;
const em = this.useContext ? (this.config.get('context')() || this) : this;
return em.entityFactory;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/utils/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { EventSubscriber } from '../events';
import { IDatabaseDriver } from '../drivers/IDatabaseDriver';
import { EntityOptions } from '../decorators';
import { NotFoundError } from '../errors';
import { RequestContext } from './RequestContext';

export class Configuration<D extends IDatabaseDriver = IDatabaseDriver> {

Expand All @@ -35,6 +36,7 @@ export class Configuration<D extends IDatabaseDriver = IDatabaseDriver> {
},
strict: false,
validate: false,
context: () => RequestContext.getEntityManager(),
// eslint-disable-next-line no-console
logger: console.log.bind(console),
findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => NotFoundError.findOneFailed(entityName, where),
Expand Down Expand Up @@ -329,6 +331,7 @@ export interface MikroORMOptions<D extends IDatabaseDriver = IDatabaseDriver> ex
replicas?: Partial<ConnectionOptions>[];
strict: boolean;
validate: boolean;
context: () => EntityManager | undefined;
logger: (message: string) => void;
findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => Error;
debug: boolean | LoggerNamespace[];
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/utils/RequestContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import domain, { Domain } from 'domain';
import { v4 as uuid } from 'uuid';
import { EntityManager } from '../EntityManager';
import { Dictionary } from '../typings';

export type ORMDomain = Domain & { __mikro_orm_context?: RequestContext };

/**
* For node 14 and above it is suggested to use `AsyncLocalStorage` instead,
* @see https://mikro-orm.io/docs/async-local-storage/
*/
export class RequestContext {

readonly id = uuid();
readonly id = this.em.id;

constructor(readonly em: EntityManager) { }

Expand Down

0 comments on commit 47cd9a5

Please sign in to comment.