@cqrs-ddd/core 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.
- package/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +707 -0
- package/dist/application/base.command.d.ts +44 -0
- package/dist/application/base.command.d.ts.map +1 -0
- package/dist/application/base.command.js +58 -0
- package/dist/application/base.command.js.map +1 -0
- package/dist/application/base.query.d.ts +42 -0
- package/dist/application/base.query.d.ts.map +1 -0
- package/dist/application/base.query.js +52 -0
- package/dist/application/base.query.js.map +1 -0
- package/dist/application/command-base.handler.d.ts +106 -0
- package/dist/application/command-base.handler.d.ts.map +1 -0
- package/dist/application/command-base.handler.js +119 -0
- package/dist/application/command-base.handler.js.map +1 -0
- package/dist/application/index.d.ts +16 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +33 -0
- package/dist/application/index.js.map +1 -0
- package/dist/application/ports/cache.port.d.ts +104 -0
- package/dist/application/ports/cache.port.d.ts.map +1 -0
- package/dist/application/ports/cache.port.js +17 -0
- package/dist/application/ports/cache.port.js.map +1 -0
- package/dist/application/ports/command-repository.port.d.ts +28 -0
- package/dist/application/ports/command-repository.port.d.ts.map +1 -0
- package/dist/application/ports/command-repository.port.js +4 -0
- package/dist/application/ports/command-repository.port.js.map +1 -0
- package/dist/application/ports/domain-event-publisher.port.d.ts +18 -0
- package/dist/application/ports/domain-event-publisher.port.d.ts.map +1 -0
- package/dist/application/ports/domain-event-publisher.port.js +4 -0
- package/dist/application/ports/domain-event-publisher.port.js.map +1 -0
- package/dist/application/ports/query-repository.port.d.ts +26 -0
- package/dist/application/ports/query-repository.port.d.ts.map +1 -0
- package/dist/application/ports/query-repository.port.js +4 -0
- package/dist/application/ports/query-repository.port.js.map +1 -0
- package/dist/application/ports/write-side-aggregate-repository.port.d.ts +33 -0
- package/dist/application/ports/write-side-aggregate-repository.port.d.ts.map +1 -0
- package/dist/application/ports/write-side-aggregate-repository.port.js +4 -0
- package/dist/application/ports/write-side-aggregate-repository.port.js.map +1 -0
- package/dist/application/query.options.d.ts +22 -0
- package/dist/application/query.options.d.ts.map +1 -0
- package/dist/application/query.options.js +4 -0
- package/dist/application/query.options.js.map +1 -0
- package/dist/application/request-fields.helper.d.ts +5 -0
- package/dist/application/request-fields.helper.d.ts.map +1 -0
- package/dist/application/request-fields.helper.js +20 -0
- package/dist/application/request-fields.helper.js.map +1 -0
- package/dist/application/tenant-resolver.d.ts +56 -0
- package/dist/application/tenant-resolver.d.ts.map +1 -0
- package/dist/application/tenant-resolver.js +60 -0
- package/dist/application/tenant-resolver.js.map +1 -0
- package/dist/domain/decorators/apply-mutation.decorator.d.ts +64 -0
- package/dist/domain/decorators/apply-mutation.decorator.d.ts.map +1 -0
- package/dist/domain/decorators/apply-mutation.decorator.js +86 -0
- package/dist/domain/decorators/apply-mutation.decorator.js.map +1 -0
- package/dist/domain/decorators/mutable.decorator.d.ts +54 -0
- package/dist/domain/decorators/mutable.decorator.d.ts.map +1 -0
- package/dist/domain/decorators/mutable.decorator.js +76 -0
- package/dist/domain/decorators/mutable.decorator.js.map +1 -0
- package/dist/domain/events/domain.event.d.ts +13 -0
- package/dist/domain/events/domain.event.d.ts.map +1 -0
- package/dist/domain/events/domain.event.js +20 -0
- package/dist/domain/events/domain.event.js.map +1 -0
- package/dist/domain/events/event.interface.d.ts +9 -0
- package/dist/domain/events/event.interface.d.ts.map +1 -0
- package/dist/domain/events/event.interface.js +4 -0
- package/dist/domain/events/event.interface.js.map +1 -0
- package/dist/domain/events/root-domain.event.d.ts +50 -0
- package/dist/domain/events/root-domain.event.d.ts.map +1 -0
- package/dist/domain/events/root-domain.event.js +156 -0
- package/dist/domain/events/root-domain.event.js.map +1 -0
- package/dist/domain/exceptions/concurrency-conflict.error.d.ts +20 -0
- package/dist/domain/exceptions/concurrency-conflict.error.d.ts.map +1 -0
- package/dist/domain/exceptions/concurrency-conflict.error.js +32 -0
- package/dist/domain/exceptions/concurrency-conflict.error.js.map +1 -0
- package/dist/domain/exceptions/domain.exception.d.ts +30 -0
- package/dist/domain/exceptions/domain.exception.d.ts.map +1 -0
- package/dist/domain/exceptions/domain.exception.js +39 -0
- package/dist/domain/exceptions/domain.exception.js.map +1 -0
- package/dist/domain/exceptions/entity-not-found.exception.d.ts +14 -0
- package/dist/domain/exceptions/entity-not-found.exception.d.ts.map +1 -0
- package/dist/domain/exceptions/entity-not-found.exception.js +23 -0
- package/dist/domain/exceptions/entity-not-found.exception.js.map +1 -0
- package/dist/domain/exceptions/invalid-value.exception.d.ts +35 -0
- package/dist/domain/exceptions/invalid-value.exception.d.ts.map +1 -0
- package/dist/domain/exceptions/invalid-value.exception.js +67 -0
- package/dist/domain/exceptions/invalid-value.exception.js.map +1 -0
- package/dist/domain/exceptions/missing-tenant-context.exception.d.ts +24 -0
- package/dist/domain/exceptions/missing-tenant-context.exception.d.ts.map +1 -0
- package/dist/domain/exceptions/missing-tenant-context.exception.js +33 -0
- package/dist/domain/exceptions/missing-tenant-context.exception.js.map +1 -0
- package/dist/domain/exceptions/transient-operation.error.d.ts +17 -0
- package/dist/domain/exceptions/transient-operation.error.d.ts.map +1 -0
- package/dist/domain/exceptions/transient-operation.error.js +27 -0
- package/dist/domain/exceptions/transient-operation.error.js.map +1 -0
- package/dist/domain/exceptions/unknown-mutable-field.error.d.ts +14 -0
- package/dist/domain/exceptions/unknown-mutable-field.error.d.ts.map +1 -0
- package/dist/domain/exceptions/unknown-mutable-field.error.js +24 -0
- package/dist/domain/exceptions/unknown-mutable-field.error.js.map +1 -0
- package/dist/domain/index.d.ts +26 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +43 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/interfaces/root-entity-snapshot.interface.d.ts +7 -0
- package/dist/domain/interfaces/root-entity-snapshot.interface.d.ts.map +1 -0
- package/dist/domain/interfaces/root-entity-snapshot.interface.js +4 -0
- package/dist/domain/interfaces/root-entity-snapshot.interface.js.map +1 -0
- package/dist/domain/models/aggregate-root.d.ts +87 -0
- package/dist/domain/models/aggregate-root.d.ts.map +1 -0
- package/dist/domain/models/aggregate-root.js +122 -0
- package/dist/domain/models/aggregate-root.js.map +1 -0
- package/dist/domain/models/root.entity.d.ts +208 -0
- package/dist/domain/models/root.entity.d.ts.map +1 -0
- package/dist/domain/models/root.entity.js +350 -0
- package/dist/domain/models/root.entity.js.map +1 -0
- package/dist/domain/rules/number.rule.d.ts +81 -0
- package/dist/domain/rules/number.rule.d.ts.map +1 -0
- package/dist/domain/rules/number.rule.js +81 -0
- package/dist/domain/rules/number.rule.js.map +1 -0
- package/dist/domain/rules/rule-support.d.ts +6 -0
- package/dist/domain/rules/rule-support.d.ts.map +1 -0
- package/dist/domain/rules/rule-support.js +19 -0
- package/dist/domain/rules/rule-support.js.map +1 -0
- package/dist/domain/rules/text.rule.d.ts +98 -0
- package/dist/domain/rules/text.rule.d.ts.map +1 -0
- package/dist/domain/rules/text.rule.js +103 -0
- package/dist/domain/rules/text.rule.js.map +1 -0
- package/dist/domain/rules/value-violation.d.ts +44 -0
- package/dist/domain/rules/value-violation.d.ts.map +1 -0
- package/dist/domain/rules/value-violation.js +4 -0
- package/dist/domain/rules/value-violation.js.map +1 -0
- package/dist/http/domain-error-http-status.d.ts +43 -0
- package/dist/http/domain-error-http-status.d.ts.map +1 -0
- package/dist/http/domain-error-http-status.js +27 -0
- package/dist/http/domain-error-http-status.js.map +1 -0
- package/dist/http/index.d.ts +9 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +26 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/cache/cache-logger.d.ts +29 -0
- package/dist/persistence/cache/cache-logger.d.ts.map +1 -0
- package/dist/persistence/cache/cache-logger.js +23 -0
- package/dist/persistence/cache/cache-logger.js.map +1 -0
- package/dist/persistence/cache/memory.cache.d.ts +72 -0
- package/dist/persistence/cache/memory.cache.d.ts.map +1 -0
- package/dist/persistence/cache/memory.cache.js +183 -0
- package/dist/persistence/cache/memory.cache.js.map +1 -0
- package/dist/persistence/command-repository.abstract.d.ts +39 -0
- package/dist/persistence/command-repository.abstract.d.ts.map +1 -0
- package/dist/persistence/command-repository.abstract.js +43 -0
- package/dist/persistence/command-repository.abstract.js.map +1 -0
- package/dist/persistence/decorators/acknowledge-persisted.decorator.d.ts +82 -0
- package/dist/persistence/decorators/acknowledge-persisted.decorator.d.ts.map +1 -0
- package/dist/persistence/decorators/acknowledge-persisted.decorator.js +82 -0
- package/dist/persistence/decorators/acknowledge-persisted.decorator.js.map +1 -0
- package/dist/persistence/decorators/cache.decorator.d.ts +93 -0
- package/dist/persistence/decorators/cache.decorator.d.ts.map +1 -0
- package/dist/persistence/decorators/cache.decorator.js +200 -0
- package/dist/persistence/decorators/cache.decorator.js.map +1 -0
- package/dist/persistence/decorators/from-cache.decorator.d.ts +65 -0
- package/dist/persistence/decorators/from-cache.decorator.d.ts.map +1 -0
- package/dist/persistence/decorators/from-cache.decorator.js +165 -0
- package/dist/persistence/decorators/from-cache.decorator.js.map +1 -0
- package/dist/persistence/decorators/map-persistence-errors.decorator.d.ts +88 -0
- package/dist/persistence/decorators/map-persistence-errors.decorator.d.ts.map +1 -0
- package/dist/persistence/decorators/map-persistence-errors.decorator.js +89 -0
- package/dist/persistence/decorators/map-persistence-errors.decorator.js.map +1 -0
- package/dist/persistence/decorators/persisted-write.decorator.d.ts +61 -0
- package/dist/persistence/decorators/persisted-write.decorator.d.ts.map +1 -0
- package/dist/persistence/decorators/persisted-write.decorator.js +57 -0
- package/dist/persistence/decorators/persisted-write.decorator.js.map +1 -0
- package/dist/persistence/helpers/cache-barrier.helper.d.ts +70 -0
- package/dist/persistence/helpers/cache-barrier.helper.d.ts.map +1 -0
- package/dist/persistence/helpers/cache-barrier.helper.js +57 -0
- package/dist/persistence/helpers/cache-barrier.helper.js.map +1 -0
- package/dist/persistence/helpers/cache-key.helper.d.ts +73 -0
- package/dist/persistence/helpers/cache-key.helper.d.ts.map +1 -0
- package/dist/persistence/helpers/cache-key.helper.js +132 -0
- package/dist/persistence/helpers/cache-key.helper.js.map +1 -0
- package/dist/persistence/helpers/cache-owner.helper.d.ts +9 -0
- package/dist/persistence/helpers/cache-owner.helper.d.ts.map +1 -0
- package/dist/persistence/helpers/cache-owner.helper.js +22 -0
- package/dist/persistence/helpers/cache-owner.helper.js.map +1 -0
- package/dist/persistence/helpers/cache-snapshot.helper.d.ts +28 -0
- package/dist/persistence/helpers/cache-snapshot.helper.d.ts.map +1 -0
- package/dist/persistence/helpers/cache-snapshot.helper.js +42 -0
- package/dist/persistence/helpers/cache-snapshot.helper.js.map +1 -0
- package/dist/persistence/helpers/cache-version.helper.d.ts +29 -0
- package/dist/persistence/helpers/cache-version.helper.d.ts.map +1 -0
- package/dist/persistence/helpers/cache-version.helper.js +71 -0
- package/dist/persistence/helpers/cache-version.helper.js.map +1 -0
- package/dist/persistence/index.d.ts +23 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +40 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/persistence-dialect.d.ts +35 -0
- package/dist/persistence/persistence-dialect.d.ts.map +1 -0
- package/dist/persistence/persistence-dialect.js +31 -0
- package/dist/persistence/persistence-dialect.js.map +1 -0
- package/dist/persistence/query-repository.abstract.d.ts +76 -0
- package/dist/persistence/query-repository.abstract.d.ts.map +1 -0
- package/dist/persistence/query-repository.abstract.js +67 -0
- package/dist/persistence/query-repository.abstract.js.map +1 -0
- package/dist/types/Method.type.d.ts +2 -0
- package/dist/types/Method.type.d.ts.map +1 -0
- package/dist/types/Method.type.js +4 -0
- package/dist/types/Method.type.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { CacheFillOptions, CacheSetOptions, CacheStateEntry, IVersionedCache } from '../../application/ports/cache.port';
|
|
2
|
+
export declare const CACHE_TOKEN: unique symbol;
|
|
3
|
+
export type MemoryCacheSetOptions = CacheSetOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Upper bound on retained keys, including revision-only entries.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_MAX_ENTRIES = 10000;
|
|
8
|
+
/**
|
|
9
|
+
* In-memory implementation of {@link IVersionedCache} maintaining deep detachment parity
|
|
10
|
+
* with database/network caches via JSON cloning on both `set()` and `get()`.
|
|
11
|
+
*
|
|
12
|
+
* Implements revision-fenced coordination:
|
|
13
|
+
* - Every write and invalidation takes the next value of one cache-wide counter.
|
|
14
|
+
* - Retains coordination metadata on payload expiry to prevent ABA stale fills.
|
|
15
|
+
* - An absent key reports the absence revision, which advances to the counter
|
|
16
|
+
* whenever an entry is evicted or the cache is cleared, so dropping a key's
|
|
17
|
+
* metadata never returns it to a revision a reader observed before the drop.
|
|
18
|
+
*/
|
|
19
|
+
export declare class MemoryCache<T> implements IVersionedCache<T> {
|
|
20
|
+
readonly isVersioned: true;
|
|
21
|
+
private store;
|
|
22
|
+
private lastRevision;
|
|
23
|
+
private absentRevision;
|
|
24
|
+
private readonly defaultTtlMs;
|
|
25
|
+
private readonly maxEntries;
|
|
26
|
+
constructor(options?: {
|
|
27
|
+
defaultTtlMs?: number;
|
|
28
|
+
maxEntries?: number;
|
|
29
|
+
});
|
|
30
|
+
private nextRevision;
|
|
31
|
+
/**
|
|
32
|
+
* Drops expired entries, then the oldest surviving ones until the store fits.
|
|
33
|
+
*/
|
|
34
|
+
private evict;
|
|
35
|
+
/**
|
|
36
|
+
* Observes current key state and opaque revision token.
|
|
37
|
+
*/
|
|
38
|
+
readState(key: string): Promise<CacheStateEntry<T>>;
|
|
39
|
+
/**
|
|
40
|
+
* Atomically advances the key revision and removes the value.
|
|
41
|
+
*/
|
|
42
|
+
invalidate(key: string): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Atomically checks that current revision matches `observedRevision`, writes
|
|
45
|
+
* value, and advances revision on match.
|
|
46
|
+
*/
|
|
47
|
+
tryFill(key: string, observedRevision: string, value: T, options?: CacheFillOptions): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Stores a value in memory, cloning it via JSON round-trip to guarantee detachment.
|
|
50
|
+
* Enforces TTL expiration and optional atomic CAS stale-write protection (`isNewer`).
|
|
51
|
+
*/
|
|
52
|
+
set(key: string, value: T, options?: MemoryCacheSetOptions): Promise<void>;
|
|
53
|
+
private write;
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves a cached value, checking TTL and returning a detached clone.
|
|
56
|
+
*/
|
|
57
|
+
get(key: string): Promise<T | undefined>;
|
|
58
|
+
/**
|
|
59
|
+
* Removes the value and advances the key's revision, as invalidate() does.
|
|
60
|
+
*/
|
|
61
|
+
delete(key: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Purges all keys from the in-memory cache. Every revision observed before
|
|
64
|
+
* the purge, including an observed absence, is rejected by a later `tryFill`.
|
|
65
|
+
*/
|
|
66
|
+
clear(): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Current number of keys in the in-memory cache.
|
|
69
|
+
*/
|
|
70
|
+
get size(): number;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=memory.cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.cache.d.ts","sourceRoot":"","sources":["../../../persistence/cache/memory.cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,EAChB,MAAM,oCAAoC,CAAC;AAE5C,eAAO,MAAM,WAAW,eAAwB,CAAC;AAEjD,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAEpD;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAS,CAAC;AA6B1C;;;;;;;;;;GAUG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,WAAW,EAAG,IAAI,CAAU;IAErC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,CAAC,EAAE;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IAKD,OAAO,CAAC,YAAY;IAKpB;;OAEG;IACH,OAAO,CAAC,KAAK;IAmBb;;OAEG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAoBzD;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAc9C;;;OAGG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,gBAAgB,EAAE,MAAM,EACxB,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,OAAO,CAAC;IAcnB;;;OAGG;IACG,GAAG,CACP,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAahB,OAAO,CAAC,KAAK;IAoBb;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAU9C;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MemoryCache = exports.DEFAULT_MAX_ENTRIES = exports.CACHE_TOKEN = void 0;
|
|
5
|
+
exports.CACHE_TOKEN = Symbol('MemoryCache');
|
|
6
|
+
/**
|
|
7
|
+
* Upper bound on retained keys, including revision-only entries.
|
|
8
|
+
*/
|
|
9
|
+
exports.DEFAULT_MAX_ENTRIES = 10_000;
|
|
10
|
+
function detach(value) {
|
|
11
|
+
if (value === undefined || value === null || typeof value !== 'object') {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
return JSON.parse(JSON.stringify(value));
|
|
15
|
+
}
|
|
16
|
+
function isExpired(entry, now = Date.now()) {
|
|
17
|
+
return entry.expiresAt !== undefined && now > entry.expiresAt;
|
|
18
|
+
}
|
|
19
|
+
function clearValue(entry) {
|
|
20
|
+
entry.hasValue = false;
|
|
21
|
+
entry.value = undefined;
|
|
22
|
+
entry.expiresAt = undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* In-memory implementation of {@link IVersionedCache} maintaining deep detachment parity
|
|
26
|
+
* with database/network caches via JSON cloning on both `set()` and `get()`.
|
|
27
|
+
*
|
|
28
|
+
* Implements revision-fenced coordination:
|
|
29
|
+
* - Every write and invalidation takes the next value of one cache-wide counter.
|
|
30
|
+
* - Retains coordination metadata on payload expiry to prevent ABA stale fills.
|
|
31
|
+
* - An absent key reports the absence revision, which advances to the counter
|
|
32
|
+
* whenever an entry is evicted or the cache is cleared, so dropping a key's
|
|
33
|
+
* metadata never returns it to a revision a reader observed before the drop.
|
|
34
|
+
*/
|
|
35
|
+
class MemoryCache {
|
|
36
|
+
isVersioned = true;
|
|
37
|
+
store = new Map();
|
|
38
|
+
lastRevision = 0n;
|
|
39
|
+
absentRevision = 0n;
|
|
40
|
+
defaultTtlMs;
|
|
41
|
+
maxEntries;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.defaultTtlMs = options?.defaultTtlMs ?? 60_000;
|
|
44
|
+
this.maxEntries = options?.maxEntries ?? exports.DEFAULT_MAX_ENTRIES;
|
|
45
|
+
}
|
|
46
|
+
nextRevision() {
|
|
47
|
+
this.lastRevision += 1n;
|
|
48
|
+
return this.lastRevision;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drops expired entries, then the oldest surviving ones until the store fits.
|
|
52
|
+
*/
|
|
53
|
+
evict() {
|
|
54
|
+
if (this.store.size <= this.maxEntries)
|
|
55
|
+
return;
|
|
56
|
+
// Invalidate every token observed before the drop, including absences
|
|
57
|
+
this.absentRevision = this.lastRevision;
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
for (const [key, entry] of this.store) {
|
|
60
|
+
if (isExpired(entry, now)) {
|
|
61
|
+
this.store.delete(key);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const key of this.store.keys()) {
|
|
65
|
+
if (this.store.size <= this.maxEntries)
|
|
66
|
+
break;
|
|
67
|
+
this.store.delete(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Observes current key state and opaque revision token.
|
|
72
|
+
*/
|
|
73
|
+
async readState(key) {
|
|
74
|
+
const entry = this.store.get(key);
|
|
75
|
+
if (!entry) {
|
|
76
|
+
return { status: 'miss', revision: this.absentRevision.toString() };
|
|
77
|
+
}
|
|
78
|
+
if (entry.hasValue) {
|
|
79
|
+
if (isExpired(entry)) {
|
|
80
|
+
return { status: 'expired', revision: entry.revision.toString() };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
status: 'hit',
|
|
84
|
+
value: detach(entry.value),
|
|
85
|
+
revision: entry.revision.toString(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { status: 'miss', revision: entry.revision.toString() };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Atomically advances the key revision and removes the value.
|
|
92
|
+
*/
|
|
93
|
+
async invalidate(key) {
|
|
94
|
+
const existing = this.store.get(key);
|
|
95
|
+
const revision = this.nextRevision();
|
|
96
|
+
if (existing) {
|
|
97
|
+
existing.revision = revision;
|
|
98
|
+
clearValue(existing);
|
|
99
|
+
return revision.toString();
|
|
100
|
+
}
|
|
101
|
+
this.store.set(key, { revision, hasValue: false });
|
|
102
|
+
this.evict();
|
|
103
|
+
return revision.toString();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Atomically checks that current revision matches `observedRevision`, writes
|
|
107
|
+
* value, and advances revision on match.
|
|
108
|
+
*/
|
|
109
|
+
async tryFill(key, observedRevision, value, options) {
|
|
110
|
+
const existing = this.store.get(key);
|
|
111
|
+
const currentRevision = (existing ? existing.revision : this.absentRevision).toString();
|
|
112
|
+
if (currentRevision !== observedRevision) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
this.write(key, value, options, existing);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Stores a value in memory, cloning it via JSON round-trip to guarantee detachment.
|
|
120
|
+
* Enforces TTL expiration and optional atomic CAS stale-write protection (`isNewer`).
|
|
121
|
+
*/
|
|
122
|
+
async set(key, value, options) {
|
|
123
|
+
const existing = this.store.get(key);
|
|
124
|
+
if (existing?.hasValue) {
|
|
125
|
+
if (isExpired(existing)) {
|
|
126
|
+
clearValue(existing);
|
|
127
|
+
}
|
|
128
|
+
else if (options?.isNewer?.(existing.value, value)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this.write(key, value, options, existing);
|
|
133
|
+
}
|
|
134
|
+
write(key, value, options, existing) {
|
|
135
|
+
const ttl = options?.ttl ?? this.defaultTtlMs;
|
|
136
|
+
const entry = {
|
|
137
|
+
revision: this.nextRevision(),
|
|
138
|
+
value: detach(value),
|
|
139
|
+
expiresAt: ttl > 0 ? Date.now() + ttl : undefined,
|
|
140
|
+
hasValue: true,
|
|
141
|
+
};
|
|
142
|
+
if (existing)
|
|
143
|
+
Object.assign(existing, entry);
|
|
144
|
+
else
|
|
145
|
+
this.store.set(key, entry);
|
|
146
|
+
this.evict();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Retrieves a cached value, checking TTL and returning a detached clone.
|
|
150
|
+
*/
|
|
151
|
+
async get(key) {
|
|
152
|
+
const entry = this.store.get(key);
|
|
153
|
+
if (!entry?.hasValue)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (isExpired(entry)) {
|
|
156
|
+
clearValue(entry);
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
return detach(entry.value);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Removes the value and advances the key's revision, as invalidate() does.
|
|
163
|
+
*/
|
|
164
|
+
async delete(key) {
|
|
165
|
+
await this.invalidate(key);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Purges all keys from the in-memory cache. Every revision observed before
|
|
169
|
+
* the purge, including an observed absence, is rejected by a later `tryFill`.
|
|
170
|
+
*/
|
|
171
|
+
async clear() {
|
|
172
|
+
this.absentRevision = this.nextRevision();
|
|
173
|
+
this.store.clear();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Current number of keys in the in-memory cache.
|
|
177
|
+
*/
|
|
178
|
+
get size() {
|
|
179
|
+
return this.store.size;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.MemoryCache = MemoryCache;
|
|
183
|
+
//# sourceMappingURL=memory.cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.cache.js","sourceRoot":"","sources":["../../../persistence/cache/memory.cache.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AASzD,QAAA,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;AAIjD;;GAEG;AACU,QAAA,mBAAmB,GAAG,MAAM,CAAC;AAE1C,SAAS,MAAM,CAAI,KAAQ;IACzB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAM,CAAC;AAChD,CAAC;AASD,SAAS,SAAS,CAChB,KAAgC,EAChC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,OAAO,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC;AAChE,CAAC;AAED,SAAS,UAAU,CAAC,KAAgC;IAClD,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;IACvB,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;IACxB,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAa,WAAW;IACb,WAAW,GAAG,IAAa,CAAC;IAE7B,KAAK,GAAqC,IAAI,GAAG,EAAE,CAAC;IACpD,YAAY,GAAG,EAAE,CAAC;IAClB,cAAc,GAAG,EAAE,CAAC;IACX,YAAY,CAAS;IACrB,UAAU,CAAS;IAEpC,YAAY,OAGX;QACC,IAAI,CAAC,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,MAAM,CAAC;QACpD,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,2BAAmB,CAAC;IAC/D,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;OAEG;IACK,KAAK;QACX,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAE/C,sEAAsE;QACtE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC;QAExC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU;gBAAE,MAAM;YAC9C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtE,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpE,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE;aACpC,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC;IACjE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,GAAW;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,UAAU,CAAC,QAAQ,CAAC,CAAC;YACrB,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CACX,GAAW,EACX,gBAAwB,EACxB,KAAQ,EACR,OAA0B;QAE1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,eAAe,GAAG,CACtB,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CACnD,CAAC,QAAQ,EAAE,CAAC;QAEb,IAAI,eAAe,KAAK,gBAAgB,EAAE,CAAC;YACzC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CACP,GAAW,EACX,KAAQ,EACR,OAA+B;QAE/B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,QAAQ,EAAE,QAAQ,EAAE,CAAC;YACvB,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,UAAU,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;gBACrD,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CACX,GAAW,EACX,KAAQ,EACR,OAAqC,EACrC,QAAyC;QAEzC,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC;QAC9C,MAAM,KAAK,GAAwB;YACjC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE;YAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;YACpB,SAAS,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS;YACjD,QAAQ,EAAE,IAAI;SACf,CAAC;QAEF,IAAI,QAAQ;YAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;;YACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAEhC,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,QAAQ;YAAE,OAAO,SAAS,CAAC;QACvC,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAvLD,kCAuLC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ICache } from '../application/ports/cache.port';
|
|
2
|
+
import { ICommandRepository } from '../application/ports/command-repository.port';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for write-side (command) repositories.
|
|
5
|
+
*
|
|
6
|
+
* Persists an entity or aggregate state via the abstract {@link save} method and receives an
|
|
7
|
+
* {@link ICache} instance. Subclasses annotate `save()` with the `@Cache` decorator
|
|
8
|
+
* to perform automatic write-through caching or cache eviction upon mutation.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam TEntity - The aggregate entity type accepted by {@link save}.
|
|
11
|
+
* @typeParam TResult - The persisted result type returned by {@link save}.
|
|
12
|
+
*
|
|
13
|
+
* @example Creating a command repository with @Cache
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Injectable()
|
|
16
|
+
* export class CreateUserCommandRepository extends CommandRepository<User, UserSnapshot> {
|
|
17
|
+
* constructor(
|
|
18
|
+
* @Inject(CACHE_TOKEN) protected readonly cache: ICache<UserSnapshot>,
|
|
19
|
+
* @Inject(MIKRO_ORM_CLIENT) private readonly store: MikroOrmStore,
|
|
20
|
+
* ) {
|
|
21
|
+
* super(cache);
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* @Cache<User, UserSnapshot>(
|
|
25
|
+
* (user) => cacheKey(User.aggregateName, { id: user.id }),
|
|
26
|
+
* )
|
|
27
|
+
* async save(user: User): Promise<UserSnapshot> {
|
|
28
|
+
* const created = await this.store.em.upsert(User, user);
|
|
29
|
+
* return created.toJSON();
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare abstract class CommandRepository<TEntity = unknown, TResult = unknown | null, TCache = unknown> implements ICommandRepository<TEntity, TResult> {
|
|
35
|
+
protected readonly cache: ICache<TCache>;
|
|
36
|
+
constructor(cache: ICache<TCache>);
|
|
37
|
+
abstract save(entity: TEntity): Promise<TResult | null>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=command-repository.abstract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-repository.abstract.d.ts","sourceRoot":"","sources":["../../persistence/command-repository.abstract.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,8BAAsB,iBAAiB,CACrC,OAAO,GAAG,OAAO,EACjB,OAAO,GAAG,OAAO,GAAG,IAAI,EACxB,MAAM,GAAG,OAAO,CAChB,YAAW,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC;IAEnC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;gBAArB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;IAEpD,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CACxD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.CommandRepository = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* Base class for write-side (command) repositories.
|
|
7
|
+
*
|
|
8
|
+
* Persists an entity or aggregate state via the abstract {@link save} method and receives an
|
|
9
|
+
* {@link ICache} instance. Subclasses annotate `save()` with the `@Cache` decorator
|
|
10
|
+
* to perform automatic write-through caching or cache eviction upon mutation.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam TEntity - The aggregate entity type accepted by {@link save}.
|
|
13
|
+
* @typeParam TResult - The persisted result type returned by {@link save}.
|
|
14
|
+
*
|
|
15
|
+
* @example Creating a command repository with @Cache
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Injectable()
|
|
18
|
+
* export class CreateUserCommandRepository extends CommandRepository<User, UserSnapshot> {
|
|
19
|
+
* constructor(
|
|
20
|
+
* @Inject(CACHE_TOKEN) protected readonly cache: ICache<UserSnapshot>,
|
|
21
|
+
* @Inject(MIKRO_ORM_CLIENT) private readonly store: MikroOrmStore,
|
|
22
|
+
* ) {
|
|
23
|
+
* super(cache);
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* @Cache<User, UserSnapshot>(
|
|
27
|
+
* (user) => cacheKey(User.aggregateName, { id: user.id }),
|
|
28
|
+
* )
|
|
29
|
+
* async save(user: User): Promise<UserSnapshot> {
|
|
30
|
+
* const created = await this.store.em.upsert(User, user);
|
|
31
|
+
* return created.toJSON();
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class CommandRepository {
|
|
37
|
+
cache;
|
|
38
|
+
constructor(cache) {
|
|
39
|
+
this.cache = cache;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.CommandRepository = CommandRepository;
|
|
43
|
+
//# sourceMappingURL=command-repository.abstract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-repository.abstract.js","sourceRoot":"","sources":["../../persistence/command-repository.abstract.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAKtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAsB,iBAAiB;IAMN;IAA/B,YAA+B,KAAqB;QAArB,UAAK,GAAL,KAAK,CAAgB;IAAG,CAAC;CAGzD;AATD,8CASC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract required for aggregates to participate in persistence version acknowledgment.
|
|
3
|
+
*
|
|
4
|
+
* Implemented automatically by {@link RootEntity}.
|
|
5
|
+
*/
|
|
6
|
+
export interface PersistedAggregate {
|
|
7
|
+
/** The current in-memory aggregate version. */
|
|
8
|
+
readonly version?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Advances the persisted version baseline (`_persistedVersion`) to match
|
|
11
|
+
* the durable version written to persistent storage.
|
|
12
|
+
*/
|
|
13
|
+
acknowledgePersisted(version?: number): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Method decorator that guarantees an aggregate's persisted version baseline is updated
|
|
17
|
+
* upon successful completion of a repository write operation.
|
|
18
|
+
*
|
|
19
|
+
* ### Lifecycle Semantics
|
|
20
|
+
* 1. **Argument Selection**: Extracts the aggregate from method arguments via `options.entity(args)`.
|
|
21
|
+
* Argument positions are never hardcoded or assumed.
|
|
22
|
+
* 2. **Version Capture**: Captures `const version = entity.version` before awaiting the method.
|
|
23
|
+
* 3. **Execution**: Invokes the wrapped persistence method (e.g. `save(entity)`).
|
|
24
|
+
* 4. **Acknowledgment on Success**: If the promise resolves, invokes `entity.acknowledgePersisted(version)`
|
|
25
|
+
* to align `_persistedVersion` with the newly written durable version.
|
|
26
|
+
* 5. **Preserved Baseline on Failure**: If the promise rejects (due to concurrency conflict, unique constraint,
|
|
27
|
+
* or connection error), `acknowledgePersisted()` is skipped, leaving `getExpectedVersion()` at its previous baseline.
|
|
28
|
+
*
|
|
29
|
+
* ### Canonical Decorator Ordering
|
|
30
|
+
* Always stack decorators in this outermost-to-innermost order:
|
|
31
|
+
* 1. `@Cache(...)` — Best-effort cache synchronization after acknowledgment.
|
|
32
|
+
* 2. `@AcknowledgePersisted(...)` — Acknowledges version baseline on successful write.
|
|
33
|
+
* 3. `@MapPersistenceErrors(...)` — Translates low-level DB driver errors to domain exceptions.
|
|
34
|
+
*
|
|
35
|
+
* > [!IMPORTANT]
|
|
36
|
+
* > This decorator assumes an autocommitted persistence operation where promise resolution means
|
|
37
|
+
* > durable storage commit. Externally managed transactions require transaction commit-hook contracts,
|
|
38
|
+
* > not method decorators.
|
|
39
|
+
*
|
|
40
|
+
* @param options Configuration object with an `entity` extractor function over the method's argument tuple.
|
|
41
|
+
*
|
|
42
|
+
* @example Usage in an update command repository
|
|
43
|
+
* ```typescript
|
|
44
|
+
* @Injectable()
|
|
45
|
+
* export class UpdateRoleCommandRepository extends CommandRepository<Role, RoleSnapshot> {
|
|
46
|
+
* @Cache<Role, RoleSnapshot>((role) => `roles:${role.id}`)
|
|
47
|
+
* @AcknowledgePersisted<[Role]>({ entity: ([role]) => role })
|
|
48
|
+
* @MapPersistenceErrors<[Role], Role>({
|
|
49
|
+
* entity: ([role]) => role,
|
|
50
|
+
* unique: [{ constraint: 'roles_name_unique', columns: 'roles.name', error: (r) => new UniqueRoleNameException(r) }],
|
|
51
|
+
* })
|
|
52
|
+
* async save(role: Role): Promise<RoleSnapshot> {
|
|
53
|
+
* const snapshot = role.toJSON();
|
|
54
|
+
* await optimisticUpdate(this.store.em, Role, role, { name: snapshot.name }, 'Role');
|
|
55
|
+
* return snapshot;
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example Usage in a create command repository
|
|
61
|
+
* ```typescript
|
|
62
|
+
* @Injectable()
|
|
63
|
+
* export class CreateRoleCommandRepository extends CommandRepository<Role, RoleSnapshot> {
|
|
64
|
+
* @Cache<Role, RoleSnapshot>((role) => `roles:${role.id}`)
|
|
65
|
+
* @AcknowledgePersisted<[Role]>({ entity: ([role]) => role })
|
|
66
|
+
* @MapPersistenceErrors<[Role], Role>({
|
|
67
|
+
* entity: ([role]) => role,
|
|
68
|
+
* unique: [{ constraint: 'roles_name_unique', columns: 'roles.name', error: (r) => new UniqueRoleNameException(r) }],
|
|
69
|
+
* })
|
|
70
|
+
* async save(role: Role): Promise<RoleSnapshot> {
|
|
71
|
+
* const created = this.store.em.create(Role, role);
|
|
72
|
+
* this.store.em.persist(created);
|
|
73
|
+
* await this.store.em.flush();
|
|
74
|
+
* return created.toJSON();
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function AcknowledgePersisted<TArgs extends unknown[]>(options: {
|
|
80
|
+
entity: (args: TArgs) => PersistedAggregate;
|
|
81
|
+
}): <TResult>(_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor<(...args: TArgs) => Promise<TResult>>) => void;
|
|
82
|
+
//# sourceMappingURL=acknowledge-persisted.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acknowledge-persisted.decorator.d.ts","sourceRoot":"","sources":["../../../persistence/decorators/acknowledge-persisted.decorator.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAAE;IACrE,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,kBAAkB,CAAC;CAC7C,IACS,OAAO,EACb,SAAS,MAAM,EACf,MAAM,MAAM,GAAG,MAAM,EACrB,YAAY,uBAAuB,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,KACxE,IAAI,CAYR"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AcknowledgePersisted = AcknowledgePersisted;
|
|
4
|
+
/**
|
|
5
|
+
* Method decorator that guarantees an aggregate's persisted version baseline is updated
|
|
6
|
+
* upon successful completion of a repository write operation.
|
|
7
|
+
*
|
|
8
|
+
* ### Lifecycle Semantics
|
|
9
|
+
* 1. **Argument Selection**: Extracts the aggregate from method arguments via `options.entity(args)`.
|
|
10
|
+
* Argument positions are never hardcoded or assumed.
|
|
11
|
+
* 2. **Version Capture**: Captures `const version = entity.version` before awaiting the method.
|
|
12
|
+
* 3. **Execution**: Invokes the wrapped persistence method (e.g. `save(entity)`).
|
|
13
|
+
* 4. **Acknowledgment on Success**: If the promise resolves, invokes `entity.acknowledgePersisted(version)`
|
|
14
|
+
* to align `_persistedVersion` with the newly written durable version.
|
|
15
|
+
* 5. **Preserved Baseline on Failure**: If the promise rejects (due to concurrency conflict, unique constraint,
|
|
16
|
+
* or connection error), `acknowledgePersisted()` is skipped, leaving `getExpectedVersion()` at its previous baseline.
|
|
17
|
+
*
|
|
18
|
+
* ### Canonical Decorator Ordering
|
|
19
|
+
* Always stack decorators in this outermost-to-innermost order:
|
|
20
|
+
* 1. `@Cache(...)` — Best-effort cache synchronization after acknowledgment.
|
|
21
|
+
* 2. `@AcknowledgePersisted(...)` — Acknowledges version baseline on successful write.
|
|
22
|
+
* 3. `@MapPersistenceErrors(...)` — Translates low-level DB driver errors to domain exceptions.
|
|
23
|
+
*
|
|
24
|
+
* > [!IMPORTANT]
|
|
25
|
+
* > This decorator assumes an autocommitted persistence operation where promise resolution means
|
|
26
|
+
* > durable storage commit. Externally managed transactions require transaction commit-hook contracts,
|
|
27
|
+
* > not method decorators.
|
|
28
|
+
*
|
|
29
|
+
* @param options Configuration object with an `entity` extractor function over the method's argument tuple.
|
|
30
|
+
*
|
|
31
|
+
* @example Usage in an update command repository
|
|
32
|
+
* ```typescript
|
|
33
|
+
* @Injectable()
|
|
34
|
+
* export class UpdateRoleCommandRepository extends CommandRepository<Role, RoleSnapshot> {
|
|
35
|
+
* @Cache<Role, RoleSnapshot>((role) => `roles:${role.id}`)
|
|
36
|
+
* @AcknowledgePersisted<[Role]>({ entity: ([role]) => role })
|
|
37
|
+
* @MapPersistenceErrors<[Role], Role>({
|
|
38
|
+
* entity: ([role]) => role,
|
|
39
|
+
* unique: [{ constraint: 'roles_name_unique', columns: 'roles.name', error: (r) => new UniqueRoleNameException(r) }],
|
|
40
|
+
* })
|
|
41
|
+
* async save(role: Role): Promise<RoleSnapshot> {
|
|
42
|
+
* const snapshot = role.toJSON();
|
|
43
|
+
* await optimisticUpdate(this.store.em, Role, role, { name: snapshot.name }, 'Role');
|
|
44
|
+
* return snapshot;
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Usage in a create command repository
|
|
50
|
+
* ```typescript
|
|
51
|
+
* @Injectable()
|
|
52
|
+
* export class CreateRoleCommandRepository extends CommandRepository<Role, RoleSnapshot> {
|
|
53
|
+
* @Cache<Role, RoleSnapshot>((role) => `roles:${role.id}`)
|
|
54
|
+
* @AcknowledgePersisted<[Role]>({ entity: ([role]) => role })
|
|
55
|
+
* @MapPersistenceErrors<[Role], Role>({
|
|
56
|
+
* entity: ([role]) => role,
|
|
57
|
+
* unique: [{ constraint: 'roles_name_unique', columns: 'roles.name', error: (r) => new UniqueRoleNameException(r) }],
|
|
58
|
+
* })
|
|
59
|
+
* async save(role: Role): Promise<RoleSnapshot> {
|
|
60
|
+
* const created = this.store.em.create(Role, role);
|
|
61
|
+
* this.store.em.persist(created);
|
|
62
|
+
* await this.store.em.flush();
|
|
63
|
+
* return created.toJSON();
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
function AcknowledgePersisted(options) {
|
|
69
|
+
return (_target, _key, descriptor) => {
|
|
70
|
+
const original = descriptor.value;
|
|
71
|
+
if (typeof original !== 'function')
|
|
72
|
+
throw new TypeError('Persistence decorators require a method.');
|
|
73
|
+
descriptor.value = async function (...args) {
|
|
74
|
+
const entity = options.entity(args);
|
|
75
|
+
const version = entity.version;
|
|
76
|
+
const result = await original.apply(this, args);
|
|
77
|
+
entity.acknowledgePersisted(version);
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=acknowledge-persisted.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acknowledge-persisted.decorator.js","sourceRoot":"","sources":["../../../persistence/decorators/acknowledge-persisted.decorator.ts"],"names":[],"mappings":";;AAgFA,oDAmBC;AAnFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,SAAgB,oBAAoB,CAA0B,OAE7D;IACC,OAAO,CACL,OAAe,EACf,IAAqB,EACrB,UAAyE,EACnE,EAAE;QACR,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;QAClC,IAAI,OAAO,QAAQ,KAAK,UAAU;YAChC,MAAM,IAAI,SAAS,CAAC,0CAA0C,CAAC,CAAC;QAClE,UAAU,CAAC,KAAK,GAAG,KAAK,WAAW,GAAG,IAAW;YAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAChD,MAAM,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YACrC,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ICacheLogger } from '../cache/cache-logger';
|
|
2
|
+
/**
|
|
3
|
+
* Options for configuring write-through and eviction caching via {@link Cache}.
|
|
4
|
+
*/
|
|
5
|
+
export interface CacheOptions<TEntity = unknown> {
|
|
6
|
+
/**
|
|
7
|
+
* Function deriving the cache key to write on success.
|
|
8
|
+
* Pass `null` or omit to skip write-through caching.
|
|
9
|
+
*/
|
|
10
|
+
setKey?: ((entity: TEntity) => string) | null;
|
|
11
|
+
/**
|
|
12
|
+
* Function deriving keys to evict when `save()` returns `null` or `undefined` (e.g. deletion).
|
|
13
|
+
*/
|
|
14
|
+
deleteKeys?: ((entity: TEntity) => string[]) | null;
|
|
15
|
+
/**
|
|
16
|
+
* Optional secondary keys to evict on every successful save before writing the new entry.
|
|
17
|
+
*/
|
|
18
|
+
invalidateKeys?: ((entity: TEntity) => string[]) | null;
|
|
19
|
+
/**
|
|
20
|
+
* Optional time-to-live in milliseconds for the write-through cache entry.
|
|
21
|
+
*/
|
|
22
|
+
ttl?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Optional version comparison function for CAS-safe write-through caching.
|
|
25
|
+
* If omitted, defaults to {@link isCacheNewer} which compares `version`, `__gen`, or `updatedAt`.
|
|
26
|
+
* Pass `null` to skip the isNewer comparison.
|
|
27
|
+
*/
|
|
28
|
+
isNewer?: ((cached: unknown, incoming: unknown) => boolean) | null;
|
|
29
|
+
/**
|
|
30
|
+
* Lifetime in milliseconds of the {@link CacheMutationBarrier} written on
|
|
31
|
+
* deletions and secondary invalidations.
|
|
32
|
+
*
|
|
33
|
+
* A barrier only has to outlive the in-flight reads/writes it guards against,
|
|
34
|
+
* so configure this above the expected maximum repository operation latency.
|
|
35
|
+
* Ignored by {@link IVersionedCache} adapters, which fence concurrent fills by
|
|
36
|
+
* revision instead of by barrier.
|
|
37
|
+
*
|
|
38
|
+
* @default {@link DEFAULT_BARRIER_TTL_MS}
|
|
39
|
+
*/
|
|
40
|
+
barrierTtl?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Receives cache-maintenance warnings. Defaults to `console.warn` with a
|
|
43
|
+
* `[CacheDecorator]` prefix. See {@link ICacheLogger}.
|
|
44
|
+
*/
|
|
45
|
+
logger?: ICacheLogger;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Default barrier lifetime: long enough to outlive an in-flight read or write
|
|
49
|
+
* that started before the mutation, short enough that deleted aggregates do not
|
|
50
|
+
* accumulate keys indefinitely.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEFAULT_BARRIER_TTL_MS = 60000;
|
|
53
|
+
/**
|
|
54
|
+
* Write-through cache decorator for a {@link CommandRepository} `save` method.
|
|
55
|
+
*
|
|
56
|
+
* Provides declarative cache synchronization on mutating database operations:
|
|
57
|
+
* - **Write-through update**: When `save()` resolves with a non-nullish result, the result is cached
|
|
58
|
+
* under the key generated by `setKey` (after invalidating any secondary keys via `invalidateKeys`).
|
|
59
|
+
* - **Cache eviction on delete**: When `save()` resolves with `null` or `undefined` (e.g. entity deletion),
|
|
60
|
+
* all keys returned by `deleteKeys` are evicted from the cache.
|
|
61
|
+
* - **Best-effort safety**: Cache write and eviction operations swallow errors internally so that an
|
|
62
|
+
* already-committed database transaction is never converted into an application error.
|
|
63
|
+
* - **Explicit keys**: Requires at least one of `setKey`, `deleteKeys` or `invalidateKeys`;
|
|
64
|
+
* scoping the key (for example with `cacheKey`) is the key function's job.
|
|
65
|
+
*
|
|
66
|
+
* @example Positional syntax on creation / update
|
|
67
|
+
* ```typescript
|
|
68
|
+
* @Cache<User, UserSnapshot>(
|
|
69
|
+
* (user) => cacheKey(User.aggregateName, { id: user.id }),
|
|
70
|
+
* null,
|
|
71
|
+
* (user) => [cacheKey(User.aggregateName, { email: user.email })],
|
|
72
|
+
* )
|
|
73
|
+
* async save(user: User): Promise<UserSnapshot> { ... }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Cache maintenance runs only after the wrapped persistence method succeeds and
|
|
77
|
+
* is best-effort: cache failures are logged rather than converting an already
|
|
78
|
+
* durable write into an application failure. Pair this decorator with
|
|
79
|
+
* `@AcknowledgePersisted` and `@MapPersistenceErrors` in the canonical order.
|
|
80
|
+
*
|
|
81
|
+
* @example Options object syntax on eviction / delete
|
|
82
|
+
* ```typescript
|
|
83
|
+
* @Cache<User, null>({
|
|
84
|
+
* deleteKeys: (user) => [
|
|
85
|
+
* cacheKey(User.aggregateName, { id: user.id }),
|
|
86
|
+
* cacheKey(User.aggregateName, { email: user.email }),
|
|
87
|
+
* ],
|
|
88
|
+
* })
|
|
89
|
+
* async save(user: User): Promise<null> { ... }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function Cache<TEntity = unknown, TResult = unknown | null>(setKeyOrOptions: ((entity: TEntity) => string) | CacheOptions<TEntity> | null, deleteKeysFn?: ((entity: TEntity) => string[]) | null, invalidateKeysFn?: ((entity: TEntity) => string[]) | null): MethodDecorator;
|
|
93
|
+
//# sourceMappingURL=cache.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.decorator.d.ts","sourceRoot":"","sources":["../../../persistence/decorators/cache.decorator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AA2C1D;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,GAAG,OAAO;IAC7C;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,GAAG,IAAI,CAAC;IAE9C;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IAEpD;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IAExD;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAEnE;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,EAC/D,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,EAC7E,YAAY,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,GAAG,IAAI,EACrD,gBAAgB,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,GAAG,IAAI,GACxD,eAAe,CAmKjB"}
|