@cqrs-ddd/mikro-orm 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +299 -0
  4. package/dist/cache/cache-entry.d.ts +43 -0
  5. package/dist/cache/cache-entry.d.ts.map +1 -0
  6. package/dist/cache/cache-entry.js +92 -0
  7. package/dist/cache/cache-entry.js.map +1 -0
  8. package/dist/cache/mikro-orm.cache.d.ts +82 -0
  9. package/dist/cache/mikro-orm.cache.d.ts.map +1 -0
  10. package/dist/cache/mikro-orm.cache.js +209 -0
  11. package/dist/cache/mikro-orm.cache.js.map +1 -0
  12. package/dist/concurrency/assert-autocommit.d.ts +21 -0
  13. package/dist/concurrency/assert-autocommit.d.ts.map +1 -0
  14. package/dist/concurrency/assert-autocommit.js +27 -0
  15. package/dist/concurrency/assert-autocommit.js.map +1 -0
  16. package/dist/concurrency/conditioned-write.d.ts +23 -0
  17. package/dist/concurrency/conditioned-write.d.ts.map +1 -0
  18. package/dist/concurrency/conditioned-write.js +26 -0
  19. package/dist/concurrency/conditioned-write.js.map +1 -0
  20. package/dist/concurrency/optimistic-delete.d.ts +52 -0
  21. package/dist/concurrency/optimistic-delete.d.ts.map +1 -0
  22. package/dist/concurrency/optimistic-delete.js +67 -0
  23. package/dist/concurrency/optimistic-delete.js.map +1 -0
  24. package/dist/concurrency/optimistic-update.d.ts +51 -0
  25. package/dist/concurrency/optimistic-update.d.ts.map +1 -0
  26. package/dist/concurrency/optimistic-update.js +60 -0
  27. package/dist/concurrency/optimistic-update.js.map +1 -0
  28. package/dist/errors/mikro-orm.dialect.d.ts +32 -0
  29. package/dist/errors/mikro-orm.dialect.d.ts.map +1 -0
  30. package/dist/errors/mikro-orm.dialect.js +96 -0
  31. package/dist/errors/mikro-orm.dialect.js.map +1 -0
  32. package/dist/errors/transient-error.d.ts +51 -0
  33. package/dist/errors/transient-error.d.ts.map +1 -0
  34. package/dist/errors/transient-error.js +97 -0
  35. package/dist/errors/transient-error.js.map +1 -0
  36. package/dist/helpers/sql-identifier.d.ts +19 -0
  37. package/dist/helpers/sql-identifier.d.ts.map +1 -0
  38. package/dist/helpers/sql-identifier.js +23 -0
  39. package/dist/helpers/sql-identifier.js.map +1 -0
  40. package/dist/index.d.ts +14 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +31 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/interfaces/entity-manager-source.d.ts +19 -0
  45. package/dist/interfaces/entity-manager-source.d.ts.map +1 -0
  46. package/dist/interfaces/entity-manager-source.js +4 -0
  47. package/dist/interfaces/entity-manager-source.js.map +1 -0
  48. package/dist/mapping/root-entity.properties.d.ts +63 -0
  49. package/dist/mapping/root-entity.properties.d.ts.map +1 -0
  50. package/dist/mapping/root-entity.properties.js +63 -0
  51. package/dist/mapping/root-entity.properties.js.map +1 -0
  52. package/dist/mapping/unix-timestamp.type.d.ts +17 -0
  53. package/dist/mapping/unix-timestamp.type.d.ts.map +1 -0
  54. package/dist/mapping/unix-timestamp.type.js +55 -0
  55. package/dist/mapping/unix-timestamp.type.js.map +1 -0
  56. package/dist/repository/aggregate.repository.d.ts +68 -0
  57. package/dist/repository/aggregate.repository.d.ts.map +1 -0
  58. package/dist/repository/aggregate.repository.js +103 -0
  59. package/dist/repository/aggregate.repository.js.map +1 -0
  60. package/dist/tenancy/tenant-store.d.ts +63 -0
  61. package/dist/tenancy/tenant-store.d.ts.map +1 -0
  62. package/dist/tenancy/tenant-store.js +91 -0
  63. package/dist/tenancy/tenant-store.js.map +1 -0
  64. package/package.json +57 -0
@@ -0,0 +1,68 @@
1
+ import type { ICache, IWriteSideAggregateRepository } from '@cqrs-ddd/core/application';
2
+ import type { RootEntity, RootEntitySnapshot } from '@cqrs-ddd/core/domain';
3
+ import { CommandRepository } from '@cqrs-ddd/core/persistence';
4
+ import type { EntityName } from '@mikro-orm/core';
5
+ import type { IEntityManagerSource } from '../interfaces/entity-manager-source';
6
+ /**
7
+ * Base class for the command repositories that load and save an existing aggregate.
8
+ *
9
+ * Provides authoritative aggregate hydration via {@link findById}:
10
+ * - Bypasses read-side caches (`@FromCache`) to prevent stale reads on mutation paths.
11
+ * - Forces MikroORM `{ refresh: true }` to avoid returning stale Unit-of-Work identity-map entities.
12
+ * - Translates low-level driver failures through {@link mapPersistenceError} into application-neutral transient signals.
13
+ *
14
+ * Concrete repositories extend this class, inject dependencies into `super(...)`, and provide their
15
+ * decorated `save()` method, usually through `@PersistedWrite`.
16
+ * The `store` is any {@link IEntityManagerSource}, typically the application's own
17
+ * store; its `em` is also what `save()` passes to `optimisticUpdate` or `optimisticDelete`.
18
+ *
19
+ * @typeParam TSnapshot - Snapshot structure of the aggregate.
20
+ * @typeParam TEntity - Domain aggregate class extending {@link RootEntity}.
21
+ * @typeParam TResult - Persisted result type returned by `save()`.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // A NestJS provider; STORE is the application's IEntityManagerSource token.
26
+ * @Injectable()
27
+ * export class UpdateUserCommandRepository extends AggregateRepository<
28
+ * UserSnapshot,
29
+ * User,
30
+ * UserSnapshot
31
+ * > {
32
+ * constructor(
33
+ * @Inject(CACHE_TOKEN) cache: ICache<UserSnapshot>,
34
+ * @Inject(STORE) store: IEntityManagerSource,
35
+ * ) {
36
+ * super(cache, store, User, User.aggregateName, User.fromJSON);
37
+ * }
38
+ *
39
+ * @PersistedWrite<User>({
40
+ * unique: { email: (user) => new UniqueEmailException(user) },
41
+ * })
42
+ * async save(user: User): Promise<UserSnapshot> {
43
+ * ...
44
+ * }
45
+ * }
46
+ * ```
47
+ */
48
+ export declare abstract class AggregateRepository<TSnapshot extends Partial<RootEntitySnapshot>, TEntity extends RootEntity<TSnapshot>, TResult = unknown> extends CommandRepository<TEntity, TResult, TSnapshot> implements IWriteSideAggregateRepository<TEntity> {
49
+ protected readonly store: IEntityManagerSource;
50
+ protected readonly entityClass: EntityName<TEntity>;
51
+ protected readonly aggregateName: string;
52
+ protected readonly hydrateFn: (snapshot: TSnapshot) => TEntity;
53
+ constructor(cache: ICache<TSnapshot>, store: IEntityManagerSource, entityClass: EntityName<TEntity>, aggregateName: string, hydrateFn: (snapshot: TSnapshot) => TEntity);
54
+ /**
55
+ * Loads the authoritative aggregate directly from persistence.
56
+ *
57
+ * Enforces `{ refresh: true }` so that if the entity was already loaded in the active
58
+ * EntityManager's identity map, its columns are re-fetched from the database before
59
+ * domain mutations and optimistic concurrency checks take place. The manager comes
60
+ * from `store.em`, read for this call.
61
+ *
62
+ * @param id - The aggregate identifier.
63
+ * @returns Rehydrated aggregate instance, or `null` if not found.
64
+ * @throws {TransientOperationError} If a retryable database connection or driver error occurs.
65
+ */
66
+ findById(id: string): Promise<TEntity | null>;
67
+ }
68
+ //# sourceMappingURL=aggregate.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.repository.d.ts","sourceRoot":"","sources":["../../src/repository/aggregate.repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,MAAM,EACN,6BAA6B,EAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EACL,iBAAiB,EAElB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAC;AAE/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,8BAAsB,mBAAmB,CACrC,SAAS,SAAS,OAAO,CAAC,kBAAkB,CAAC,EAC7C,OAAO,SAAS,UAAU,CAAC,SAAS,CAAC,EACrC,OAAO,GAAG,OAAO,CAEnB,SAAQ,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CACrD,YAAW,6BAA6B,CAAC,OAAO,CAAC;IAI/C,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,oBAAoB;IAC9C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;IACnD,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM;IACxC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO;gBAJ9D,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,EACL,KAAK,EAAE,oBAAoB,EAC3B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,EAChC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO;IAKhE;;;;;;;;;;;OAWG;IAYG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAcpD"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
4
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
8
+ };
9
+ var __metadata = (this && this.__metadata) || function (k, v) {
10
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.AggregateRepository = void 0;
14
+ const persistence_1 = require("@cqrs-ddd/core/persistence");
15
+ const transient_error_1 = require("../errors/transient-error");
16
+ /**
17
+ * Base class for the command repositories that load and save an existing aggregate.
18
+ *
19
+ * Provides authoritative aggregate hydration via {@link findById}:
20
+ * - Bypasses read-side caches (`@FromCache`) to prevent stale reads on mutation paths.
21
+ * - Forces MikroORM `{ refresh: true }` to avoid returning stale Unit-of-Work identity-map entities.
22
+ * - Translates low-level driver failures through {@link mapPersistenceError} into application-neutral transient signals.
23
+ *
24
+ * Concrete repositories extend this class, inject dependencies into `super(...)`, and provide their
25
+ * decorated `save()` method, usually through `@PersistedWrite`.
26
+ * The `store` is any {@link IEntityManagerSource}, typically the application's own
27
+ * store; its `em` is also what `save()` passes to `optimisticUpdate` or `optimisticDelete`.
28
+ *
29
+ * @typeParam TSnapshot - Snapshot structure of the aggregate.
30
+ * @typeParam TEntity - Domain aggregate class extending {@link RootEntity}.
31
+ * @typeParam TResult - Persisted result type returned by `save()`.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // A NestJS provider; STORE is the application's IEntityManagerSource token.
36
+ * @Injectable()
37
+ * export class UpdateUserCommandRepository extends AggregateRepository<
38
+ * UserSnapshot,
39
+ * User,
40
+ * UserSnapshot
41
+ * > {
42
+ * constructor(
43
+ * @Inject(CACHE_TOKEN) cache: ICache<UserSnapshot>,
44
+ * @Inject(STORE) store: IEntityManagerSource,
45
+ * ) {
46
+ * super(cache, store, User, User.aggregateName, User.fromJSON);
47
+ * }
48
+ *
49
+ * @PersistedWrite<User>({
50
+ * unique: { email: (user) => new UniqueEmailException(user) },
51
+ * })
52
+ * async save(user: User): Promise<UserSnapshot> {
53
+ * ...
54
+ * }
55
+ * }
56
+ * ```
57
+ */
58
+ class AggregateRepository extends persistence_1.CommandRepository {
59
+ store;
60
+ entityClass;
61
+ aggregateName;
62
+ hydrateFn;
63
+ constructor(cache, store, entityClass, aggregateName, hydrateFn) {
64
+ super(cache);
65
+ this.store = store;
66
+ this.entityClass = entityClass;
67
+ this.aggregateName = aggregateName;
68
+ this.hydrateFn = hydrateFn;
69
+ }
70
+ /**
71
+ * Loads the authoritative aggregate directly from persistence.
72
+ *
73
+ * Enforces `{ refresh: true }` so that if the entity was already loaded in the active
74
+ * EntityManager's identity map, its columns are re-fetched from the database before
75
+ * domain mutations and optimistic concurrency checks take place. The manager comes
76
+ * from `store.em`, read for this call.
77
+ *
78
+ * @param id - The aggregate identifier.
79
+ * @returns Rehydrated aggregate instance, or `null` if not found.
80
+ * @throws {TransientOperationError} If a retryable database connection or driver error occurs.
81
+ */
82
+ async findById(id) {
83
+ const entity = await this.store.em.findOne(this.entityClass, { id }, { refresh: true });
84
+ if (!entity) {
85
+ return null;
86
+ }
87
+ const snapshot = entity.toJSON();
88
+ return this.hydrateFn(snapshot);
89
+ }
90
+ }
91
+ exports.AggregateRepository = AggregateRepository;
92
+ __decorate([
93
+ (0, persistence_1.MapPersistenceErrors)({
94
+ entity: ([id]) => id,
95
+ otherwise(error, id) {
96
+ return (0, transient_error_1.mapPersistenceError)(error, `loading ${this.aggregateName} ${id}`);
97
+ },
98
+ }),
99
+ __metadata("design:type", Function),
100
+ __metadata("design:paramtypes", [String]),
101
+ __metadata("design:returntype", Promise)
102
+ ], AggregateRepository.prototype, "findById", null);
103
+ //# sourceMappingURL=aggregate.repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.repository.js","sourceRoot":"","sources":["../../src/repository/aggregate.repository.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;;;;;;;;;;AAOtE,4DAGoC;AAEpC,+DAAgE;AAGhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAsB,mBAKpB,SAAQ,+BAA8C;IAKjC;IACA;IACA;IACA;IALrB,YACE,KAAwB,EACL,KAA2B,EAC3B,WAAgC,EAChC,aAAqB,EACrB,SAA2C;QAE9D,KAAK,CAAC,KAAK,CAAC,CAAC;QALM,UAAK,GAAL,KAAK,CAAsB;QAC3B,gBAAW,GAAX,WAAW,CAAqB;QAChC,kBAAa,GAAb,aAAa,CAAQ;QACrB,cAAS,GAAT,SAAS,CAAkC;IAGhE,CAAC;IAED;;;;;;;;;;;OAWG;IAYG,AAAN,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CACxC,IAAI,CAAC,WAAW,EAChB,EAAE,EAAE,EAA0B,EAC9B,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAC;QAEF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAe,CAAC;QAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF;AAvDD,kDAuDC;AAdO;IAXL,IAAA,kCAAoB,EAKnB;QACA,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE;QACpB,SAAS,CAAC,KAAK,EAAE,EAAE;YACjB,OAAO,IAAA,qCAAmB,EAAC,KAAK,EAAE,WAAW,IAAI,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3E,CAAC;KACF,CAAC;;;;mDAcD"}
@@ -0,0 +1,63 @@
1
+ import type { EntityManager, MikroORM } from '@mikro-orm/core';
2
+ import type { IEntityManagerSource } from '../interfaces/entity-manager-source';
3
+ /**
4
+ * How tenants are kept apart: `'database'` — one ORM per tenant, each on its own
5
+ * database; `'schema'` — one ORM, each tenant a schema of its database.
6
+ */
7
+ export type TenantIsolation = 'database' | 'schema';
8
+ /** Options of {@link TenantStore}. */
9
+ export interface TenantStoreOptions {
10
+ /** The active tenant. Throw when there is none: the store never picks one. */
11
+ tenant(): string;
12
+ /** The initialized ORM that owns `tenant`'s data. Throw for an unknown tenant. */
13
+ orm(tenant: string): MikroORM;
14
+ isolation: TenantIsolation;
15
+ }
16
+ /**
17
+ * The `EntityManager` source of a multi-tenant application: `em` and
18
+ * `transactional()` always act on the active tenant's database or schema, for
19
+ * `AggregateRepository`, `MikroOrmCache` and the application's own repositories.
20
+ *
21
+ * `em` reuses a contextual manager (a MikroORM `RequestContext` or an active
22
+ * transaction) only when it belongs to the tenant's ORM, configuration, driver
23
+ * and schema and no other tenant claimed it first; otherwise it forks one.
24
+ * `transactional()` always runs on a fresh fork. Every manager it hands out is
25
+ * recorded to its tenant outside the manager (a `WeakMap`), so a later request
26
+ * for another tenant cannot reuse it; MikroORM objects are never modified.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const store = new TenantStore({
31
+ * tenant: () => currentTenantId() ?? fail(),
32
+ * orm: (tenant) => orms.get(tenant) ?? fail(),
33
+ * isolation: 'database',
34
+ * });
35
+ * const repository = new UpdateUserRepository(cache, store);
36
+ * ```
37
+ */
38
+ export declare class TenantStore<TManager extends EntityManager = EntityManager> implements IEntityManagerSource {
39
+ private readonly options;
40
+ private readonly owners;
41
+ constructor(options: TenantStoreOptions);
42
+ /**
43
+ * The active tenant's manager: its contextual one when that may be reused,
44
+ * otherwise a new fork. Read it per operation, never keep it.
45
+ *
46
+ * @throws What `options.tenant` or `options.orm` throws.
47
+ */
48
+ get em(): TManager;
49
+ /**
50
+ * Runs `work` in a transaction on a fork dedicated to this call, for the
51
+ * active tenant; commits when it resolves and rolls back when it rejects.
52
+ * What `options.tenant` or `options.orm` throws becomes its rejection.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * await store.transactional((em) => em.nativeDelete(CacheEntry, { key }));
57
+ * ```
58
+ */
59
+ transactional<T>(work: (em: TManager) => Promise<T>): Promise<T>;
60
+ private fork;
61
+ private canReuse;
62
+ }
63
+ //# sourceMappingURL=tenant-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-store.d.ts","sourceRoot":"","sources":["../../src/tenancy/tenant-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpD,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,MAAM,IAAI,MAAM,CAAC;IACjB,kFAAkF;IAClF,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC9B,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW,CAAC,QAAQ,SAAS,aAAa,GAAG,aAAa,CACrE,YAAW,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;gBAE3B,OAAO,EAAE,kBAAkB;IAExD;;;;;OAKG;IACH,IAAI,EAAE,IAAI,QAAQ,CASjB;IAED;;;;;;;;;OASG;IACG,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAMtE,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,QAAQ;CAcjB"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TenantStore = void 0;
5
+ /**
6
+ * The `EntityManager` source of a multi-tenant application: `em` and
7
+ * `transactional()` always act on the active tenant's database or schema, for
8
+ * `AggregateRepository`, `MikroOrmCache` and the application's own repositories.
9
+ *
10
+ * `em` reuses a contextual manager (a MikroORM `RequestContext` or an active
11
+ * transaction) only when it belongs to the tenant's ORM, configuration, driver
12
+ * and schema and no other tenant claimed it first; otherwise it forks one.
13
+ * `transactional()` always runs on a fresh fork. Every manager it hands out is
14
+ * recorded to its tenant outside the manager (a `WeakMap`), so a later request
15
+ * for another tenant cannot reuse it; MikroORM objects are never modified.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const store = new TenantStore({
20
+ * tenant: () => currentTenantId() ?? fail(),
21
+ * orm: (tenant) => orms.get(tenant) ?? fail(),
22
+ * isolation: 'database',
23
+ * });
24
+ * const repository = new UpdateUserRepository(cache, store);
25
+ * ```
26
+ */
27
+ class TenantStore {
28
+ options;
29
+ owners = new WeakMap();
30
+ constructor(options) {
31
+ this.options = options;
32
+ }
33
+ /**
34
+ * The active tenant's manager: its contextual one when that may be reused,
35
+ * otherwise a new fork. Read it per operation, never keep it.
36
+ *
37
+ * @throws What `options.tenant` or `options.orm` throws.
38
+ */
39
+ get em() {
40
+ const tenant = this.options.tenant();
41
+ const orm = this.options.orm(tenant);
42
+ const context = contextOf(orm);
43
+ if (context && this.canReuse(context, orm, tenant)) {
44
+ if (!this.owners.has(context))
45
+ this.owners.set(context, tenant);
46
+ return context;
47
+ }
48
+ return this.fork(orm, tenant);
49
+ }
50
+ /**
51
+ * Runs `work` in a transaction on a fork dedicated to this call, for the
52
+ * active tenant; commits when it resolves and rolls back when it rejects.
53
+ * What `options.tenant` or `options.orm` throws becomes its rejection.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * await store.transactional((em) => em.nativeDelete(CacheEntry, { key }));
58
+ * ```
59
+ */
60
+ async transactional(work) {
61
+ const tenant = this.options.tenant();
62
+ const fork = this.fork(this.options.orm(tenant), tenant);
63
+ return fork.transactional((em) => work(em));
64
+ }
65
+ fork(orm, tenant) {
66
+ const em = orm.em.fork({
67
+ disableContextResolution: true,
68
+ ...(this.options.isolation === 'schema' ? { schema: tenant } : {}),
69
+ });
70
+ this.owners.set(em, tenant);
71
+ return em;
72
+ }
73
+ canReuse(context, orm, tenant) {
74
+ const owner = this.owners.get(context);
75
+ return (context !== orm.em &&
76
+ (owner === undefined || owner === tenant) &&
77
+ (context.schema === undefined || context.schema === tenant) &&
78
+ context.config === orm.config &&
79
+ context.getDriver() === orm.em.getDriver());
80
+ }
81
+ }
82
+ exports.TenantStore = TenantStore;
83
+ function contextOf(orm) {
84
+ try {
85
+ return orm.em.getContext(false);
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ //# sourceMappingURL=tenant-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-store.js","sourceRoot":"","sources":["../../src/tenancy/tenant-store.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAoBtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,WAAW;IAKO;IAFZ,MAAM,GAAG,IAAI,OAAO,EAAkB,CAAC;IAExD,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAE5D;;;;;OAKG;IACH,IAAI,EAAE;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAChE,OAAO,OAAmB,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,aAAa,CAAI,IAAkC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,EAAc,CAAC,CAAC,CAAC;IAC1D,CAAC;IAEO,IAAI,CAAC,GAAa,EAAE,MAAc;QACxC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;YACrB,wBAAwB,EAAE,IAAI;YAC9B,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnE,CAAa,CAAC;QACf,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAEO,QAAQ,CACd,OAAsB,EACtB,GAAa,EACb,MAAc;QAEd,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO,CACL,OAAO,KAAK,GAAG,CAAC,EAAE;YAClB,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,CAAC;YACzC,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC;YAC3D,OAAO,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM;YAC7B,OAAO,CAAC,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAC3C,CAAC;IACJ,CAAC;CACF;AA/DD,kCA+DC;AAED,SAAS,SAAS,CAAC,GAAa;IAC9B,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@cqrs-ddd/mikro-orm",
3
+ "version": "0.2.0",
4
+ "description": "MikroORM persistence adapters for @cqrs-ddd/core: authoritative aggregate loading, version-conditioned writes, a revision-fenced cache and root-entity schema mapping.",
5
+ "author": "Aristotelis",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "engines": {
8
+ "node": ">=22.0.0"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/aristoteliss/nestjs-pipeline.git",
13
+ "directory": "packages/ddd-mikro-orm"
14
+ },
15
+ "homepage": "https://github.com/aristoteliss/nestjs-pipeline/tree/master/packages/ddd-mikro-orm#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/aristoteliss/nestjs-pipeline/issues"
18
+ },
19
+ "keywords": [
20
+ "ddd",
21
+ "cqrs",
22
+ "mikro-orm",
23
+ "repository",
24
+ "optimistic-concurrency",
25
+ "cache"
26
+ ],
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE",
33
+ "COMMERCIAL_LICENSE.txt"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "peerDependencies": {
39
+ "@mikro-orm/core": "^7.2.1",
40
+ "@cqrs-ddd/core": "^0.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@mikro-orm/core": "^7.2.1",
44
+ "@vitest/coverage-v8": "^4.1.11",
45
+ "typescript": "^6.0.3",
46
+ "vitest": "^4.1.11",
47
+ "@cqrs-ddd/core": "^0.2.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
52
+ "rebuild": "pnpm run clean && pnpm run build",
53
+ "lint": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest"
56
+ }
57
+ }