@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.
Files changed (212) hide show
  1. package/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +707 -0
  4. package/dist/application/base.command.d.ts +44 -0
  5. package/dist/application/base.command.d.ts.map +1 -0
  6. package/dist/application/base.command.js +58 -0
  7. package/dist/application/base.command.js.map +1 -0
  8. package/dist/application/base.query.d.ts +42 -0
  9. package/dist/application/base.query.d.ts.map +1 -0
  10. package/dist/application/base.query.js +52 -0
  11. package/dist/application/base.query.js.map +1 -0
  12. package/dist/application/command-base.handler.d.ts +106 -0
  13. package/dist/application/command-base.handler.d.ts.map +1 -0
  14. package/dist/application/command-base.handler.js +119 -0
  15. package/dist/application/command-base.handler.js.map +1 -0
  16. package/dist/application/index.d.ts +16 -0
  17. package/dist/application/index.d.ts.map +1 -0
  18. package/dist/application/index.js +33 -0
  19. package/dist/application/index.js.map +1 -0
  20. package/dist/application/ports/cache.port.d.ts +104 -0
  21. package/dist/application/ports/cache.port.d.ts.map +1 -0
  22. package/dist/application/ports/cache.port.js +17 -0
  23. package/dist/application/ports/cache.port.js.map +1 -0
  24. package/dist/application/ports/command-repository.port.d.ts +28 -0
  25. package/dist/application/ports/command-repository.port.d.ts.map +1 -0
  26. package/dist/application/ports/command-repository.port.js +4 -0
  27. package/dist/application/ports/command-repository.port.js.map +1 -0
  28. package/dist/application/ports/domain-event-publisher.port.d.ts +18 -0
  29. package/dist/application/ports/domain-event-publisher.port.d.ts.map +1 -0
  30. package/dist/application/ports/domain-event-publisher.port.js +4 -0
  31. package/dist/application/ports/domain-event-publisher.port.js.map +1 -0
  32. package/dist/application/ports/query-repository.port.d.ts +26 -0
  33. package/dist/application/ports/query-repository.port.d.ts.map +1 -0
  34. package/dist/application/ports/query-repository.port.js +4 -0
  35. package/dist/application/ports/query-repository.port.js.map +1 -0
  36. package/dist/application/ports/write-side-aggregate-repository.port.d.ts +33 -0
  37. package/dist/application/ports/write-side-aggregate-repository.port.d.ts.map +1 -0
  38. package/dist/application/ports/write-side-aggregate-repository.port.js +4 -0
  39. package/dist/application/ports/write-side-aggregate-repository.port.js.map +1 -0
  40. package/dist/application/query.options.d.ts +22 -0
  41. package/dist/application/query.options.d.ts.map +1 -0
  42. package/dist/application/query.options.js +4 -0
  43. package/dist/application/query.options.js.map +1 -0
  44. package/dist/application/request-fields.helper.d.ts +5 -0
  45. package/dist/application/request-fields.helper.d.ts.map +1 -0
  46. package/dist/application/request-fields.helper.js +20 -0
  47. package/dist/application/request-fields.helper.js.map +1 -0
  48. package/dist/application/tenant-resolver.d.ts +56 -0
  49. package/dist/application/tenant-resolver.d.ts.map +1 -0
  50. package/dist/application/tenant-resolver.js +60 -0
  51. package/dist/application/tenant-resolver.js.map +1 -0
  52. package/dist/domain/decorators/apply-mutation.decorator.d.ts +64 -0
  53. package/dist/domain/decorators/apply-mutation.decorator.d.ts.map +1 -0
  54. package/dist/domain/decorators/apply-mutation.decorator.js +86 -0
  55. package/dist/domain/decorators/apply-mutation.decorator.js.map +1 -0
  56. package/dist/domain/decorators/mutable.decorator.d.ts +54 -0
  57. package/dist/domain/decorators/mutable.decorator.d.ts.map +1 -0
  58. package/dist/domain/decorators/mutable.decorator.js +76 -0
  59. package/dist/domain/decorators/mutable.decorator.js.map +1 -0
  60. package/dist/domain/events/domain.event.d.ts +13 -0
  61. package/dist/domain/events/domain.event.d.ts.map +1 -0
  62. package/dist/domain/events/domain.event.js +20 -0
  63. package/dist/domain/events/domain.event.js.map +1 -0
  64. package/dist/domain/events/event.interface.d.ts +9 -0
  65. package/dist/domain/events/event.interface.d.ts.map +1 -0
  66. package/dist/domain/events/event.interface.js +4 -0
  67. package/dist/domain/events/event.interface.js.map +1 -0
  68. package/dist/domain/events/root-domain.event.d.ts +50 -0
  69. package/dist/domain/events/root-domain.event.d.ts.map +1 -0
  70. package/dist/domain/events/root-domain.event.js +156 -0
  71. package/dist/domain/events/root-domain.event.js.map +1 -0
  72. package/dist/domain/exceptions/concurrency-conflict.error.d.ts +20 -0
  73. package/dist/domain/exceptions/concurrency-conflict.error.d.ts.map +1 -0
  74. package/dist/domain/exceptions/concurrency-conflict.error.js +32 -0
  75. package/dist/domain/exceptions/concurrency-conflict.error.js.map +1 -0
  76. package/dist/domain/exceptions/domain.exception.d.ts +30 -0
  77. package/dist/domain/exceptions/domain.exception.d.ts.map +1 -0
  78. package/dist/domain/exceptions/domain.exception.js +39 -0
  79. package/dist/domain/exceptions/domain.exception.js.map +1 -0
  80. package/dist/domain/exceptions/entity-not-found.exception.d.ts +14 -0
  81. package/dist/domain/exceptions/entity-not-found.exception.d.ts.map +1 -0
  82. package/dist/domain/exceptions/entity-not-found.exception.js +23 -0
  83. package/dist/domain/exceptions/entity-not-found.exception.js.map +1 -0
  84. package/dist/domain/exceptions/invalid-value.exception.d.ts +35 -0
  85. package/dist/domain/exceptions/invalid-value.exception.d.ts.map +1 -0
  86. package/dist/domain/exceptions/invalid-value.exception.js +67 -0
  87. package/dist/domain/exceptions/invalid-value.exception.js.map +1 -0
  88. package/dist/domain/exceptions/missing-tenant-context.exception.d.ts +24 -0
  89. package/dist/domain/exceptions/missing-tenant-context.exception.d.ts.map +1 -0
  90. package/dist/domain/exceptions/missing-tenant-context.exception.js +33 -0
  91. package/dist/domain/exceptions/missing-tenant-context.exception.js.map +1 -0
  92. package/dist/domain/exceptions/transient-operation.error.d.ts +17 -0
  93. package/dist/domain/exceptions/transient-operation.error.d.ts.map +1 -0
  94. package/dist/domain/exceptions/transient-operation.error.js +27 -0
  95. package/dist/domain/exceptions/transient-operation.error.js.map +1 -0
  96. package/dist/domain/exceptions/unknown-mutable-field.error.d.ts +14 -0
  97. package/dist/domain/exceptions/unknown-mutable-field.error.d.ts.map +1 -0
  98. package/dist/domain/exceptions/unknown-mutable-field.error.js +24 -0
  99. package/dist/domain/exceptions/unknown-mutable-field.error.js.map +1 -0
  100. package/dist/domain/index.d.ts +26 -0
  101. package/dist/domain/index.d.ts.map +1 -0
  102. package/dist/domain/index.js +43 -0
  103. package/dist/domain/index.js.map +1 -0
  104. package/dist/domain/interfaces/root-entity-snapshot.interface.d.ts +7 -0
  105. package/dist/domain/interfaces/root-entity-snapshot.interface.d.ts.map +1 -0
  106. package/dist/domain/interfaces/root-entity-snapshot.interface.js +4 -0
  107. package/dist/domain/interfaces/root-entity-snapshot.interface.js.map +1 -0
  108. package/dist/domain/models/aggregate-root.d.ts +87 -0
  109. package/dist/domain/models/aggregate-root.d.ts.map +1 -0
  110. package/dist/domain/models/aggregate-root.js +122 -0
  111. package/dist/domain/models/aggregate-root.js.map +1 -0
  112. package/dist/domain/models/root.entity.d.ts +208 -0
  113. package/dist/domain/models/root.entity.d.ts.map +1 -0
  114. package/dist/domain/models/root.entity.js +350 -0
  115. package/dist/domain/models/root.entity.js.map +1 -0
  116. package/dist/domain/rules/number.rule.d.ts +81 -0
  117. package/dist/domain/rules/number.rule.d.ts.map +1 -0
  118. package/dist/domain/rules/number.rule.js +81 -0
  119. package/dist/domain/rules/number.rule.js.map +1 -0
  120. package/dist/domain/rules/rule-support.d.ts +6 -0
  121. package/dist/domain/rules/rule-support.d.ts.map +1 -0
  122. package/dist/domain/rules/rule-support.js +19 -0
  123. package/dist/domain/rules/rule-support.js.map +1 -0
  124. package/dist/domain/rules/text.rule.d.ts +98 -0
  125. package/dist/domain/rules/text.rule.d.ts.map +1 -0
  126. package/dist/domain/rules/text.rule.js +103 -0
  127. package/dist/domain/rules/text.rule.js.map +1 -0
  128. package/dist/domain/rules/value-violation.d.ts +44 -0
  129. package/dist/domain/rules/value-violation.d.ts.map +1 -0
  130. package/dist/domain/rules/value-violation.js +4 -0
  131. package/dist/domain/rules/value-violation.js.map +1 -0
  132. package/dist/http/domain-error-http-status.d.ts +43 -0
  133. package/dist/http/domain-error-http-status.d.ts.map +1 -0
  134. package/dist/http/domain-error-http-status.js +27 -0
  135. package/dist/http/domain-error-http-status.js.map +1 -0
  136. package/dist/http/index.d.ts +9 -0
  137. package/dist/http/index.d.ts.map +1 -0
  138. package/dist/http/index.js +26 -0
  139. package/dist/http/index.js.map +1 -0
  140. package/dist/index.d.ts +13 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +30 -0
  143. package/dist/index.js.map +1 -0
  144. package/dist/persistence/cache/cache-logger.d.ts +29 -0
  145. package/dist/persistence/cache/cache-logger.d.ts.map +1 -0
  146. package/dist/persistence/cache/cache-logger.js +23 -0
  147. package/dist/persistence/cache/cache-logger.js.map +1 -0
  148. package/dist/persistence/cache/memory.cache.d.ts +72 -0
  149. package/dist/persistence/cache/memory.cache.d.ts.map +1 -0
  150. package/dist/persistence/cache/memory.cache.js +183 -0
  151. package/dist/persistence/cache/memory.cache.js.map +1 -0
  152. package/dist/persistence/command-repository.abstract.d.ts +39 -0
  153. package/dist/persistence/command-repository.abstract.d.ts.map +1 -0
  154. package/dist/persistence/command-repository.abstract.js +43 -0
  155. package/dist/persistence/command-repository.abstract.js.map +1 -0
  156. package/dist/persistence/decorators/acknowledge-persisted.decorator.d.ts +82 -0
  157. package/dist/persistence/decorators/acknowledge-persisted.decorator.d.ts.map +1 -0
  158. package/dist/persistence/decorators/acknowledge-persisted.decorator.js +82 -0
  159. package/dist/persistence/decorators/acknowledge-persisted.decorator.js.map +1 -0
  160. package/dist/persistence/decorators/cache.decorator.d.ts +93 -0
  161. package/dist/persistence/decorators/cache.decorator.d.ts.map +1 -0
  162. package/dist/persistence/decorators/cache.decorator.js +200 -0
  163. package/dist/persistence/decorators/cache.decorator.js.map +1 -0
  164. package/dist/persistence/decorators/from-cache.decorator.d.ts +65 -0
  165. package/dist/persistence/decorators/from-cache.decorator.d.ts.map +1 -0
  166. package/dist/persistence/decorators/from-cache.decorator.js +165 -0
  167. package/dist/persistence/decorators/from-cache.decorator.js.map +1 -0
  168. package/dist/persistence/decorators/map-persistence-errors.decorator.d.ts +88 -0
  169. package/dist/persistence/decorators/map-persistence-errors.decorator.d.ts.map +1 -0
  170. package/dist/persistence/decorators/map-persistence-errors.decorator.js +89 -0
  171. package/dist/persistence/decorators/map-persistence-errors.decorator.js.map +1 -0
  172. package/dist/persistence/decorators/persisted-write.decorator.d.ts +61 -0
  173. package/dist/persistence/decorators/persisted-write.decorator.d.ts.map +1 -0
  174. package/dist/persistence/decorators/persisted-write.decorator.js +57 -0
  175. package/dist/persistence/decorators/persisted-write.decorator.js.map +1 -0
  176. package/dist/persistence/helpers/cache-barrier.helper.d.ts +70 -0
  177. package/dist/persistence/helpers/cache-barrier.helper.d.ts.map +1 -0
  178. package/dist/persistence/helpers/cache-barrier.helper.js +57 -0
  179. package/dist/persistence/helpers/cache-barrier.helper.js.map +1 -0
  180. package/dist/persistence/helpers/cache-key.helper.d.ts +73 -0
  181. package/dist/persistence/helpers/cache-key.helper.d.ts.map +1 -0
  182. package/dist/persistence/helpers/cache-key.helper.js +132 -0
  183. package/dist/persistence/helpers/cache-key.helper.js.map +1 -0
  184. package/dist/persistence/helpers/cache-owner.helper.d.ts +9 -0
  185. package/dist/persistence/helpers/cache-owner.helper.d.ts.map +1 -0
  186. package/dist/persistence/helpers/cache-owner.helper.js +22 -0
  187. package/dist/persistence/helpers/cache-owner.helper.js.map +1 -0
  188. package/dist/persistence/helpers/cache-snapshot.helper.d.ts +28 -0
  189. package/dist/persistence/helpers/cache-snapshot.helper.d.ts.map +1 -0
  190. package/dist/persistence/helpers/cache-snapshot.helper.js +42 -0
  191. package/dist/persistence/helpers/cache-snapshot.helper.js.map +1 -0
  192. package/dist/persistence/helpers/cache-version.helper.d.ts +29 -0
  193. package/dist/persistence/helpers/cache-version.helper.d.ts.map +1 -0
  194. package/dist/persistence/helpers/cache-version.helper.js +71 -0
  195. package/dist/persistence/helpers/cache-version.helper.js.map +1 -0
  196. package/dist/persistence/index.d.ts +23 -0
  197. package/dist/persistence/index.d.ts.map +1 -0
  198. package/dist/persistence/index.js +40 -0
  199. package/dist/persistence/index.js.map +1 -0
  200. package/dist/persistence/persistence-dialect.d.ts +35 -0
  201. package/dist/persistence/persistence-dialect.d.ts.map +1 -0
  202. package/dist/persistence/persistence-dialect.js +31 -0
  203. package/dist/persistence/persistence-dialect.js.map +1 -0
  204. package/dist/persistence/query-repository.abstract.d.ts +76 -0
  205. package/dist/persistence/query-repository.abstract.d.ts.map +1 -0
  206. package/dist/persistence/query-repository.abstract.js +67 -0
  207. package/dist/persistence/query-repository.abstract.js.map +1 -0
  208. package/dist/types/Method.type.d.ts +2 -0
  209. package/dist/types/Method.type.d.ts.map +1 -0
  210. package/dist/types/Method.type.js +4 -0
  211. package/dist/types/Method.type.js.map +1 -0
  212. package/package.json +80 -0
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.DEFAULT_BARRIER_TTL_MS = void 0;
5
+ exports.Cache = Cache;
6
+ const cache_port_1 = require("../../application/ports/cache.port");
7
+ const cache_logger_1 = require("../cache/cache-logger");
8
+ const cache_barrier_helper_1 = require("../helpers/cache-barrier.helper");
9
+ const cache_owner_helper_1 = require("../helpers/cache-owner.helper");
10
+ const cache_snapshot_helper_1 = require("../helpers/cache-snapshot.helper");
11
+ const cache_version_helper_1 = require("../helpers/cache-version.helper");
12
+ const defaultLogger = (0, cache_logger_1.consoleCacheLogger)('CacheDecorator');
13
+ function errorMessage(err) {
14
+ return err instanceof Error ? err.message : String(err);
15
+ }
16
+ /**
17
+ * Evicts one key using the strongest coordination the adapter supports.
18
+ *
19
+ * A {@link IVersionedCache} advances the key revision, which already fences any
20
+ * fill that started before this mutation, so no barrier value is stored: leaving
21
+ * one behind would make the next read hydrate a sentinel instead of a snapshot.
22
+ * Unversioned adapters have no revision to compare, so they receive a
23
+ * {@link CacheMutationBarrier} instead. {@link FromCache} bypasses such adapters,
24
+ * and wherever it meets a barrier it treats it as a miss, never as a snapshot.
25
+ */
26
+ async function evictKey(cache, key, reason, entity, barrierTtl) {
27
+ if ((0, cache_port_1.isVersionedCache)(cache)) {
28
+ await cache.invalidate(key);
29
+ return;
30
+ }
31
+ const barrier = (0, cache_barrier_helper_1.createCacheMutationBarrier)(reason, entity);
32
+ await cache.set(key, barrier, { ttl: barrierTtl });
33
+ }
34
+ /**
35
+ * Default barrier lifetime: long enough to outlive an in-flight read or write
36
+ * that started before the mutation, short enough that deleted aggregates do not
37
+ * accumulate keys indefinitely.
38
+ */
39
+ exports.DEFAULT_BARRIER_TTL_MS = 60_000;
40
+ /**
41
+ * Write-through cache decorator for a {@link CommandRepository} `save` method.
42
+ *
43
+ * Provides declarative cache synchronization on mutating database operations:
44
+ * - **Write-through update**: When `save()` resolves with a non-nullish result, the result is cached
45
+ * under the key generated by `setKey` (after invalidating any secondary keys via `invalidateKeys`).
46
+ * - **Cache eviction on delete**: When `save()` resolves with `null` or `undefined` (e.g. entity deletion),
47
+ * all keys returned by `deleteKeys` are evicted from the cache.
48
+ * - **Best-effort safety**: Cache write and eviction operations swallow errors internally so that an
49
+ * already-committed database transaction is never converted into an application error.
50
+ * - **Explicit keys**: Requires at least one of `setKey`, `deleteKeys` or `invalidateKeys`;
51
+ * scoping the key (for example with `cacheKey`) is the key function's job.
52
+ *
53
+ * @example Positional syntax on creation / update
54
+ * ```typescript
55
+ * @Cache<User, UserSnapshot>(
56
+ * (user) => cacheKey(User.aggregateName, { id: user.id }),
57
+ * null,
58
+ * (user) => [cacheKey(User.aggregateName, { email: user.email })],
59
+ * )
60
+ * async save(user: User): Promise<UserSnapshot> { ... }
61
+ * ```
62
+ *
63
+ * Cache maintenance runs only after the wrapped persistence method succeeds and
64
+ * is best-effort: cache failures are logged rather than converting an already
65
+ * durable write into an application failure. Pair this decorator with
66
+ * `@AcknowledgePersisted` and `@MapPersistenceErrors` in the canonical order.
67
+ *
68
+ * @example Options object syntax on eviction / delete
69
+ * ```typescript
70
+ * @Cache<User, null>({
71
+ * deleteKeys: (user) => [
72
+ * cacheKey(User.aggregateName, { id: user.id }),
73
+ * cacheKey(User.aggregateName, { email: user.email }),
74
+ * ],
75
+ * })
76
+ * async save(user: User): Promise<null> { ... }
77
+ * ```
78
+ */
79
+ function Cache(setKeyOrOptions, deleteKeysFn, invalidateKeysFn) {
80
+ const options = typeof setKeyOrOptions === 'function' || setKeyOrOptions === null
81
+ ? {
82
+ setKey: setKeyOrOptions,
83
+ deleteKeys: deleteKeysFn,
84
+ invalidateKeys: invalidateKeysFn,
85
+ }
86
+ : setKeyOrOptions;
87
+ if (!options || typeof options !== 'object') {
88
+ throw new Error('@Cache decorator requires an explicit key derivation function or options object.');
89
+ }
90
+ const resolvedSetKey = options.setKey ?? null;
91
+ const resolvedDeleteKeys = options.deleteKeys ?? null;
92
+ const resolvedInvalidateKeys = options.invalidateKeys ?? null;
93
+ const resolvedTtl = options.ttl;
94
+ const resolvedBarrierTtl = options.barrierTtl ?? exports.DEFAULT_BARRIER_TTL_MS;
95
+ const logger = options.logger ?? defaultLogger;
96
+ const resolvedIsNewer = options.isNewer === null ? undefined : (options.isNewer ?? cache_version_helper_1.isCacheNewer);
97
+ if (!resolvedSetKey && !resolvedDeleteKeys && !resolvedInvalidateKeys) {
98
+ throw new Error('@Cache decorator requires at least one of setKey, deleteKeys, or invalidateKeys to be specified.');
99
+ }
100
+ return (_target, _propertyKey, descriptor) => {
101
+ const original = descriptor.value;
102
+ descriptor.value = async function (entity) {
103
+ // Observed before persistence so a write-through can tell whether anyone
104
+ // mutated the key while this write was in flight. A versioned adapter has
105
+ // no barrier value to outrank a late snapshot, so the revision it hands
106
+ // out here is what keeps a deleted aggregate from being resurrected.
107
+ let observedKey;
108
+ let observedRevision;
109
+ let observedValue;
110
+ if (this.cache && resolvedSetKey && (0, cache_port_1.isVersionedCache)(this.cache)) {
111
+ try {
112
+ observedKey = resolvedSetKey(entity);
113
+ const state = await this.cache.readState(observedKey);
114
+ observedRevision = state.revision;
115
+ observedValue = state.status === 'hit' ? state.value : undefined;
116
+ }
117
+ catch {
118
+ observedKey = undefined;
119
+ observedRevision = undefined;
120
+ }
121
+ }
122
+ const result = await original.call(this, entity);
123
+ if (!this.cache) {
124
+ (0, cache_owner_helper_1.reportMissingCacheProperty)(this, '@Cache', options.logger);
125
+ return result;
126
+ }
127
+ // Cache maintenance is best-effort: the DB write already succeeded,
128
+ // so a cache failure must not turn a success into an error.
129
+ try {
130
+ const deleting = result === null || result === undefined;
131
+ const keysFn = deleting ? resolvedDeleteKeys : resolvedInvalidateKeys;
132
+ if (keysFn) {
133
+ let keys = [];
134
+ try {
135
+ keys = keysFn(entity) ?? [];
136
+ }
137
+ catch (err) {
138
+ const keyOption = deleting ? 'deleteKeys' : 'invalidateKeys';
139
+ const operation = deleting ? 'cache eviction' : 'cache maintenance';
140
+ (0, cache_logger_1.safeWarn)(logger, `Failed resolving ${keyOption} during ${operation}: ${errorMessage(err)}`);
141
+ }
142
+ for (const key of keys) {
143
+ try {
144
+ await evictKey(this.cache, key, deleting ? 'deleted' : 'invalidated', entity, resolvedBarrierTtl);
145
+ }
146
+ catch (err) {
147
+ const action = deleting ? 'evicting deletion' : 'invalidating';
148
+ (0, cache_logger_1.safeWarn)(logger, `Failed ${action} key "${key}": ${errorMessage(err)}`);
149
+ }
150
+ }
151
+ }
152
+ if (deleting)
153
+ return result;
154
+ if (resolvedSetKey) {
155
+ let setKey;
156
+ try {
157
+ setKey = resolvedSetKey(entity);
158
+ }
159
+ catch (err) {
160
+ (0, cache_logger_1.safeWarn)(logger, `Failed resolving setKey during cache maintenance: ${errorMessage(err)}`);
161
+ }
162
+ if (setKey) {
163
+ try {
164
+ const snapshot = (0, cache_snapshot_helper_1.toCacheSnapshot)(result);
165
+ if ((0, cache_port_1.isVersionedCache)(this.cache)) {
166
+ if (observedRevision !== undefined && setKey === observedKey) {
167
+ // Skip the write-through when the snapshot observed before
168
+ // persistence is already newer; otherwise commit it only if
169
+ // the revision still matches, so a concurrent mutation wins.
170
+ if (!resolvedIsNewer?.(observedValue, snapshot)) {
171
+ await this.cache.tryFill(setKey, observedRevision, snapshot, { ttl: resolvedTtl });
172
+ }
173
+ }
174
+ else {
175
+ // No revision was observed for this key before the write, so
176
+ // a fill cannot be fenced; drop the pre-write value instead.
177
+ await this.cache.invalidate(setKey);
178
+ }
179
+ }
180
+ else {
181
+ await this.cache.set(setKey, snapshot, {
182
+ ttl: resolvedTtl,
183
+ isNewer: resolvedIsNewer,
184
+ });
185
+ }
186
+ }
187
+ catch (err) {
188
+ (0, cache_logger_1.safeWarn)(logger, `Failed setting cache key "${setKey}": ${errorMessage(err)}`);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ catch (err) {
194
+ (0, cache_logger_1.safeWarn)(logger, `Unexpected error during cache maintenance: ${errorMessage(err)}`);
195
+ }
196
+ return result;
197
+ };
198
+ };
199
+ }
200
+ //# sourceMappingURL=cache.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.decorator.js","sourceRoot":"","sources":["../../../persistence/decorators/cache.decorator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { IQueryOptions } from '../../application/query.options';
2
+ import type { ICacheLogger } from '../cache/cache-logger';
3
+ import { isCacheNewer } from '../helpers/cache-version.helper';
4
+ export { isCacheNewer };
5
+ /**
6
+ * Options for configuring read-through caching and rehydration via {@link FromCache}.
7
+ */
8
+ export interface FromCacheOptions<TQuery = unknown, TResult = unknown> {
9
+ /**
10
+ * Function deriving the cache key from query options.
11
+ * When it returns `null`, caching is skipped for that call.
12
+ */
13
+ keyFn?: ((query: TQuery) => string | null) | null;
14
+ /**
15
+ * Function to rehydrate a raw cached snapshot back into a domain entity.
16
+ * When omitted, the repository's {@link QueryRepositoryHydration} applies;
17
+ * `null` opts this method out of it.
18
+ */
19
+ hydrateFn?: ((cached: unknown) => TResult) | null;
20
+ /**
21
+ * Optional serializer function to convert a domain entity or complex result into
22
+ * a pure, detached snapshot for storage in cache.
23
+ * If omitted, the repository's {@link QueryRepositoryHydration} serializer
24
+ * applies, and otherwise {@link toCacheSnapshot} extracts a snapshot via
25
+ * `result.toJSON()` or deep JSON cloning. `null` skips the repository default.
26
+ */
27
+ serializeFn?: ((result: TResult) => unknown) | null;
28
+ /**
29
+ * Requires `hydrateFn` at decoration time. Every hit is rehydrated whenever a
30
+ * hydrator applies, so this flag does not change the result.
31
+ */
32
+ alwaysHydrate?: boolean;
33
+ /**
34
+ * Optional time-to-live in milliseconds for cached entries.
35
+ */
36
+ ttl?: number;
37
+ /**
38
+ * Optional version comparator for stale-read prevention during concurrent cache population.
39
+ * Defaults to {@link isCacheNewer}.
40
+ */
41
+ isNewer?: (cached: unknown, incoming: unknown) => boolean;
42
+ /**
43
+ * Receives read-through warnings (unversioned adapter, uncacheable result, missing
44
+ * `cache` property). Defaults to `console.warn` with a `[FromCacheDecorator]`
45
+ * prefix. See {@link ICacheLogger}.
46
+ */
47
+ logger?: ICacheLogger;
48
+ }
49
+ /**
50
+ * Read-through cache decorator for a {@link QueryRepository} `find` method.
51
+ *
52
+ * Provides declarative read-through caching with revision-fenced coordination:
53
+ * - **Key derivation**: Generates a cache key via `keyFn`. If `keyFn` returns `null`, caching is skipped.
54
+ * - **Snapshot storage contract**: Stores strictly serializable snapshots in the cache, never live domain aggregates.
55
+ * - **Consistent result shape**: When a method or repository `hydrateFn` applies, every hit is rehydrated, so hits
56
+ * and misses return the same type. Without a hydrator only plain-data results are cached; any other result
57
+ * (a class instance, or a result reshaped by `serializeFn`) is returned uncached and reported once.
58
+ * - **Revision-fenced consistency**: When coordinated via {@link IVersionedCache}, cache fill attempts verify the
59
+ * observed revision token atomically (`tryFill`). A fill started before an observed invalidation cannot
60
+ * repopulate the key afterwards.
61
+ * - **Bounded retry**: Contended fills inspect current cache state and boundedly retry authoritative reads before
62
+ * falling back to un-cached database results.
63
+ */
64
+ export declare function FromCache<TQuery extends IQueryOptions = IQueryOptions, TResult = unknown>(keyFnOrOptions: ((query: TQuery) => string | null) | FromCacheOptions<TQuery, TResult>, hydrateFnOrOptions?: ((cached: unknown) => TResult) | FromCacheOptions<TQuery, TResult>, extraOptions?: FromCacheOptions<TQuery, TResult>): MethodDecorator;
65
+ //# sourceMappingURL=from-cache.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-cache.decorator.d.ts","sourceRoot":"","sources":["../../../persistence/decorators/from-cache.decorator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAM/D,OAAO,EAAE,YAAY,EAAE,CAAC;AAsBxB;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;IACnE;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAElD;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAElD;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAEpD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IAE1D;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AA8BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,aAAa,GAAG,aAAa,EAC5C,OAAO,GAAG,OAAO,EAEjB,cAAc,EACV,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,GAClC,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,kBAAkB,CAAC,EACf,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,GAC9B,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,YAAY,CAAC,EAAE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/C,eAAe,CAyIjB"}
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.isCacheNewer = void 0;
5
+ exports.FromCache = FromCache;
6
+ const cache_port_1 = require("../../application/ports/cache.port");
7
+ const cache_logger_1 = require("../cache/cache-logger");
8
+ const cache_barrier_helper_1 = require("../helpers/cache-barrier.helper");
9
+ const cache_owner_helper_1 = require("../helpers/cache-owner.helper");
10
+ const cache_snapshot_helper_1 = require("../helpers/cache-snapshot.helper");
11
+ const cache_version_helper_1 = require("../helpers/cache-version.helper");
12
+ Object.defineProperty(exports, "isCacheNewer", { enumerable: true, get: function () { return cache_version_helper_1.isCacheNewer; } });
13
+ const defaultLogger = (0, cache_logger_1.consoleCacheLogger)('FromCacheDecorator');
14
+ const warnedUnversionedAdapters = new WeakSet();
15
+ /**
16
+ * Reports an unversioned adapter once per instance: a silent loss of caching is
17
+ * harder to diagnose than the race the bypass avoids.
18
+ */
19
+ function warnUnversionedAdapterOnce(cache, logger) {
20
+ if (warnedUnversionedAdapters.has(cache))
21
+ return;
22
+ warnedUnversionedAdapters.add(cache);
23
+ (0, cache_logger_1.safeWarn)(logger, `${cache.constructor?.name ?? 'Cache adapter'} implements only get/set/delete, ` +
24
+ 'so @FromCache read-through is disabled for repositories using it. ' +
25
+ 'Implement IVersionedCache (readState/invalidate/tryFill) to enable ' +
26
+ 'revision-fenced caching.');
27
+ }
28
+ /**
29
+ * True for values whose cached JSON form equals the value itself: primitives,
30
+ * arrays and plain objects of such values. Class instances and `Date` are not.
31
+ */
32
+ function isPlainData(value, ancestors = new WeakSet()) {
33
+ if (value === null || typeof value !== 'object') {
34
+ return typeof value !== 'function' && typeof value !== 'bigint';
35
+ }
36
+ const prototype = Object.getPrototypeOf(value);
37
+ if (ancestors.has(value) ||
38
+ (!Array.isArray(value) &&
39
+ prototype !== Object.prototype &&
40
+ prototype !== null)) {
41
+ return false;
42
+ }
43
+ ancestors.add(value);
44
+ const plain = Object.values(value).every((item) => isPlainData(item, ancestors));
45
+ ancestors.delete(value);
46
+ return plain;
47
+ }
48
+ /**
49
+ * Read-through cache decorator for a {@link QueryRepository} `find` method.
50
+ *
51
+ * Provides declarative read-through caching with revision-fenced coordination:
52
+ * - **Key derivation**: Generates a cache key via `keyFn`. If `keyFn` returns `null`, caching is skipped.
53
+ * - **Snapshot storage contract**: Stores strictly serializable snapshots in the cache, never live domain aggregates.
54
+ * - **Consistent result shape**: When a method or repository `hydrateFn` applies, every hit is rehydrated, so hits
55
+ * and misses return the same type. Without a hydrator only plain-data results are cached; any other result
56
+ * (a class instance, or a result reshaped by `serializeFn`) is returned uncached and reported once.
57
+ * - **Revision-fenced consistency**: When coordinated via {@link IVersionedCache}, cache fill attempts verify the
58
+ * observed revision token atomically (`tryFill`). A fill started before an observed invalidation cannot
59
+ * repopulate the key afterwards.
60
+ * - **Bounded retry**: Contended fills inspect current cache state and boundedly retry authoritative reads before
61
+ * falling back to un-cached database results.
62
+ */
63
+ function FromCache(keyFnOrOptions, hydrateFnOrOptions, extraOptions) {
64
+ const positionalKey = typeof keyFnOrOptions === 'function';
65
+ const positionalHydrator = positionalKey && typeof hydrateFnOrOptions === 'function';
66
+ let resolvedOptions = positionalKey ? hydrateFnOrOptions : keyFnOrOptions;
67
+ if (typeof resolvedOptions === 'function')
68
+ resolvedOptions = extraOptions;
69
+ const resolvedKeyFn = positionalKey ? keyFnOrOptions : keyFnOrOptions.keyFn;
70
+ const resolvedHydrateFn = positionalHydrator
71
+ ? hydrateFnOrOptions
72
+ : (resolvedOptions?.hydrateFn ?? undefined);
73
+ const declaresHydrateFn = positionalHydrator ||
74
+ (resolvedOptions !== undefined && 'hydrateFn' in resolvedOptions);
75
+ const declaresSerializeFn = resolvedOptions !== undefined && 'serializeFn' in resolvedOptions;
76
+ if (resolvedOptions?.alwaysHydrate && !resolvedHydrateFn) {
77
+ throw new TypeError('FromCache: alwaysHydrate requires a hydrateFn');
78
+ }
79
+ const logger = resolvedOptions?.logger ?? defaultLogger;
80
+ const MAX_FILL_RETRIES = 2;
81
+ let reportedUncacheable = false;
82
+ return (_target, _propertyKey, descriptor) => {
83
+ const original = descriptor.value;
84
+ descriptor.value = async function (query) {
85
+ if (!this.cache) {
86
+ (0, cache_owner_helper_1.reportMissingCacheProperty)(this, '@FromCache', resolvedOptions?.logger);
87
+ return original.call(this, query);
88
+ }
89
+ if (query.refresh)
90
+ return original.call(this, query);
91
+ if (!resolvedKeyFn)
92
+ throw new TypeError('FromCache requires a keyFn');
93
+ const key = resolvedKeyFn(query);
94
+ if (key === null) {
95
+ return original.call(this, query);
96
+ }
97
+ // Method-level options win; otherwise the repository policy applies,
98
+ // and a repository hydrator rehydrates every hit.
99
+ const repositoryHydration = this.hydration;
100
+ const hydrateFn = declaresHydrateFn
101
+ ? resolvedHydrateFn
102
+ : repositoryHydration?.hydrateFn;
103
+ const serializeFn = declaresSerializeFn
104
+ ? resolvedOptions?.serializeFn
105
+ : repositoryHydration?.serializeFn;
106
+ const hydrateCached = (cachedValue) => hydrateFn ? hydrateFn(cachedValue) : cachedValue;
107
+ // A revision-fenced key can still hold a barrier written by an
108
+ // unversioned writer against the same store; it is a sentinel, never a
109
+ // snapshot, so it counts as a miss rather than something to hydrate. The
110
+ // same holds for `null`: this decorator never fills one, so a stored null
111
+ // is a leftover from another writer, not a cached absence.
112
+ const isUsableSnapshot = (state) => state.status === 'hit' &&
113
+ state.value !== undefined &&
114
+ state.value !== null &&
115
+ !(0, cache_barrier_helper_1.isCacheMutationBarrier)(state.value);
116
+ // 1. Versioned coordination path (capable adapters)
117
+ if ((0, cache_port_1.isVersionedCache)(this.cache)) {
118
+ let observedState = await this.cache.readState(key);
119
+ if (isUsableSnapshot(observedState)) {
120
+ return hydrateCached(observedState.value);
121
+ }
122
+ for (let attempt = 0;; attempt++) {
123
+ const result = await original.call(this, query);
124
+ if (result === null || result === undefined) {
125
+ const postState = await this.cache.readState(key);
126
+ if (isUsableSnapshot(postState)) {
127
+ return hydrateCached(postState.value);
128
+ }
129
+ return result;
130
+ }
131
+ // A hit without a hydrator returns the cached data as is, so only a
132
+ // result that already is that data may be cached.
133
+ if (!hydrateFn && (serializeFn || !isPlainData(result))) {
134
+ if (!reportedUncacheable) {
135
+ reportedUncacheable = true;
136
+ (0, cache_logger_1.safeWarn)(logger, `${this.constructor.name} caches a result that a hit could not reproduce without a hydrateFn; ` +
137
+ 'the result is returned uncached. Configure a hydrateFn or return plain data.');
138
+ }
139
+ return result;
140
+ }
141
+ const snapshot = (0, cache_snapshot_helper_1.toCacheSnapshot)(result, serializeFn);
142
+ const committed = await this.cache.tryFill(key, observedState.revision, snapshot, { ttl: resolvedOptions?.ttl });
143
+ if (committed) {
144
+ return result;
145
+ }
146
+ const currentState = await this.cache.readState(key);
147
+ if (isUsableSnapshot(currentState)) {
148
+ const newerCheck = resolvedOptions?.isNewer ?? cache_version_helper_1.isCacheNewer;
149
+ if (newerCheck(currentState.value, snapshot)) {
150
+ return hydrateCached(currentState.value);
151
+ }
152
+ }
153
+ if (attempt === MAX_FILL_RETRIES)
154
+ return result;
155
+ observedState = currentState;
156
+ }
157
+ }
158
+ // 2. Unversioned adapter: with no revision to fence a fill against a
159
+ // concurrent invalidation, the cache is bypassed for both reads and fills.
160
+ warnUnversionedAdapterOnce(this.cache, logger);
161
+ return original.call(this, query);
162
+ };
163
+ };
164
+ }
165
+ //# sourceMappingURL=from-cache.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-cache.decorator.js","sourceRoot":"","sources":["../../../persistence/decorators/from-cache.decorator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { type IPersistenceDialect } from '../persistence-dialect';
2
+ /**
3
+ * Domain errors per violated unique constraint, keyed by the entity property the
4
+ * constraint covers. A multi-column constraint has no single property: key it by
5
+ * its declared name, passed as `TConstraint` from a constant declared beside the
6
+ * ORM mapping. No database name or column string appears in a repository.
7
+ */
8
+ export type UniqueErrors<TEntity, TConstraint extends string = never> = {
9
+ readonly [K in Extract<keyof TEntity, string> | TConstraint]?: (entity: TEntity) => Error;
10
+ };
11
+ /**
12
+ * Method decorator that intercepts persistence failures from the wrapped method and
13
+ * translates identifiable database unique constraint violations into domain exceptions.
14
+ *
15
+ * ### Error Matching Mechanics
16
+ * - **Unique violations**: the persistence dialect ({@link IPersistenceDialect}) names the
17
+ * violated constraint by the entity property it covers; `unique[key]` builds the
18
+ * domain error. The dialect comes from `options.dialect`, else from
19
+ * {@link setPersistenceDialect}. A method that declares `unique` with no dialect
20
+ * available throws a `TypeError` before it runs, so a violation never escapes as a
21
+ * raw driver error.
22
+ * - **Residual translation**: Any error not matching a configured constraint is passed to the
23
+ * optional `otherwise` translator, which is the declarative place to convert driver/network
24
+ * failures into the neutral {@link TransientOperationError} retry signal.
25
+ * - **Passthrough**: Without `otherwise` — or when `otherwise` returns the error unchanged — the
26
+ * error is rethrown with its original identity, class, and stack trace completely preserved.
27
+ *
28
+ * Use `otherwise` for application-level translation of unmatched driver or
29
+ * network failures. Keep persistence translation in this decorator so the
30
+ * lifecycle order relative to `@AcknowledgePersisted` and `@Cache` remains
31
+ * explicit in the decorator stack.
32
+ *
33
+ * ### Canonical Decorator Ordering
34
+ * Always stack decorators in this outermost-to-innermost order:
35
+ * 1. `@Cache(...)` — Best-effort cache synchronization after acknowledgment.
36
+ * 2. `@AcknowledgePersisted(...)` — Acknowledges version baseline on successful write.
37
+ * 3. `@MapPersistenceErrors(...)` — Translates low-level DB driver errors to domain exceptions.
38
+ *
39
+ * @param options The `entity` extractor, the `unique` errors, an optional `dialect`
40
+ * overriding the registered one (for a repository on another store), and an
41
+ * optional `otherwise` translator.
42
+ *
43
+ * @example Mapping unique constraints in a create repository
44
+ * ```typescript
45
+ * @Injectable()
46
+ * export class CreateUserCommandRepository extends CommandRepository<User, UserSnapshot> {
47
+ * @Cache<User, UserSnapshot>((u) => `users:${u.id}`, null, (u) => [`users:email:${u.email}`])
48
+ * @AcknowledgePersisted<[User]>({ entity: ([user]) => user })
49
+ * @MapPersistenceErrors<[User], User>({
50
+ * entity: ([user]) => user,
51
+ * unique: { email: (user) => new UniqueEmailException(user) },
52
+ * })
53
+ * async save(user: User): Promise<UserSnapshot> {
54
+ * const created = this.store.em.create(User, user);
55
+ * this.store.em.persist(created);
56
+ * await this.store.em.flush();
57
+ * return created.toJSON();
58
+ * }
59
+ * }
60
+ * ```
61
+ */
62
+ export declare function MapPersistenceErrors<TArgs extends unknown[], TEntity, TConstraint extends string = never, TThis = unknown>(options: {
63
+ entity: (args: TArgs) => TEntity;
64
+ unique?: UniqueErrors<TEntity, TConstraint>;
65
+ dialect?: IPersistenceDialect;
66
+ /**
67
+ * Optional translator applied to any error that did not match a unique constraint
68
+ * mapping. Return the error unchanged to preserve its identity, or return a
69
+ * replacement to express it in application terms.
70
+ *
71
+ * The canonical use is transient-failure classification with
72
+ * {@link mapPersistenceError}, so that retry policies consume
73
+ * `TransientOperationError` rather than driver codes:
74
+ *
75
+ * ```typescript
76
+ * otherwise: (error, user) => mapPersistenceError(error, `deleting User ${user.id}`),
77
+ * ```
78
+ *
79
+ * Deliberate domain errors thrown inside the method (`ConcurrencyConflictError`,
80
+ * `EntityNotFoundException`) also pass through this hook. `mapPersistenceError`
81
+ * returns non-transient errors unchanged, so no explicit re-throw guard is needed.
82
+ *
83
+ * Written as a method, it runs with `this` bound to the repository instance
84
+ * (typed by `TThis`), for a message that names instance state.
85
+ */
86
+ otherwise?: (this: TThis, error: unknown, entity: TEntity) => unknown;
87
+ }): <TResult>(_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor<(...args: TArgs) => Promise<TResult>>) => void;
88
+ //# sourceMappingURL=map-persistence-errors.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-persistence-errors.decorator.d.ts","sourceRoot":"","sources":["../../../persistence/decorators/map-persistence-errors.decorator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,wBAAwB,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,MAAM,YAAY,CAAC,OAAO,EAAE,WAAW,SAAS,MAAM,GAAG,KAAK,IAAI;IACtE,QAAQ,EAAE,CAAC,IAAI,OAAO,CAAC,MAAM,OAAO,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAC7D,MAAM,EAAE,OAAO,KACZ,KAAK;CACX,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,SAAS,OAAO,EAAE,EACvB,OAAO,EACP,WAAW,SAAS,MAAM,GAAG,KAAK,EAClC,KAAK,GAAG,OAAO,EACf,OAAO,EAAE;IACT,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CACvE,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,CA+BR"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MapPersistenceErrors = MapPersistenceErrors;
4
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
5
+ const persistence_dialect_1 = require("../persistence-dialect");
6
+ /**
7
+ * Method decorator that intercepts persistence failures from the wrapped method and
8
+ * translates identifiable database unique constraint violations into domain exceptions.
9
+ *
10
+ * ### Error Matching Mechanics
11
+ * - **Unique violations**: the persistence dialect ({@link IPersistenceDialect}) names the
12
+ * violated constraint by the entity property it covers; `unique[key]` builds the
13
+ * domain error. The dialect comes from `options.dialect`, else from
14
+ * {@link setPersistenceDialect}. A method that declares `unique` with no dialect
15
+ * available throws a `TypeError` before it runs, so a violation never escapes as a
16
+ * raw driver error.
17
+ * - **Residual translation**: Any error not matching a configured constraint is passed to the
18
+ * optional `otherwise` translator, which is the declarative place to convert driver/network
19
+ * failures into the neutral {@link TransientOperationError} retry signal.
20
+ * - **Passthrough**: Without `otherwise` — or when `otherwise` returns the error unchanged — the
21
+ * error is rethrown with its original identity, class, and stack trace completely preserved.
22
+ *
23
+ * Use `otherwise` for application-level translation of unmatched driver or
24
+ * network failures. Keep persistence translation in this decorator so the
25
+ * lifecycle order relative to `@AcknowledgePersisted` and `@Cache` remains
26
+ * explicit in the decorator stack.
27
+ *
28
+ * ### Canonical Decorator Ordering
29
+ * Always stack decorators in this outermost-to-innermost order:
30
+ * 1. `@Cache(...)` — Best-effort cache synchronization after acknowledgment.
31
+ * 2. `@AcknowledgePersisted(...)` — Acknowledges version baseline on successful write.
32
+ * 3. `@MapPersistenceErrors(...)` — Translates low-level DB driver errors to domain exceptions.
33
+ *
34
+ * @param options The `entity` extractor, the `unique` errors, an optional `dialect`
35
+ * overriding the registered one (for a repository on another store), and an
36
+ * optional `otherwise` translator.
37
+ *
38
+ * @example Mapping unique constraints in a create repository
39
+ * ```typescript
40
+ * @Injectable()
41
+ * export class CreateUserCommandRepository extends CommandRepository<User, UserSnapshot> {
42
+ * @Cache<User, UserSnapshot>((u) => `users:${u.id}`, null, (u) => [`users:email:${u.email}`])
43
+ * @AcknowledgePersisted<[User]>({ entity: ([user]) => user })
44
+ * @MapPersistenceErrors<[User], User>({
45
+ * entity: ([user]) => user,
46
+ * unique: { email: (user) => new UniqueEmailException(user) },
47
+ * })
48
+ * async save(user: User): Promise<UserSnapshot> {
49
+ * const created = this.store.em.create(User, user);
50
+ * this.store.em.persist(created);
51
+ * await this.store.em.flush();
52
+ * return created.toJSON();
53
+ * }
54
+ * }
55
+ * ```
56
+ */
57
+ function MapPersistenceErrors(options) {
58
+ return (_target, _key, descriptor) => {
59
+ const original = descriptor.value;
60
+ if (typeof original !== 'function')
61
+ throw new TypeError('Persistence decorators require a method.');
62
+ const unique = options.unique ?? {};
63
+ const hasUnique = Object.keys(unique).length > 0;
64
+ descriptor.value = async function (...args) {
65
+ const dialect = options.dialect ?? (0, persistence_dialect_1.persistenceDialect)();
66
+ if (hasUnique && !dialect) {
67
+ throw new TypeError('Unique-constraint mapping needs a persistence dialect: call setPersistenceDialect() or pass { dialect }.');
68
+ }
69
+ try {
70
+ return await original.apply(this, args);
71
+ }
72
+ catch (error) {
73
+ const entity = options.entity(args);
74
+ const key = hasUnique
75
+ ? dialect?.uniqueViolation(error, entity)
76
+ : undefined;
77
+ const toError = key === undefined
78
+ ? undefined
79
+ : unique[key];
80
+ if (toError)
81
+ throw toError(entity);
82
+ if (options.otherwise)
83
+ throw options.otherwise.call(this, error, entity);
84
+ throw error;
85
+ }
86
+ };
87
+ };
88
+ }
89
+ //# sourceMappingURL=map-persistence-errors.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-persistence-errors.decorator.js","sourceRoot":"","sources":["../../../persistence/decorators/map-persistence-errors.decorator.ts"],"names":[],"mappings":";;AAqEA,oDAkEC;AAvID,sEAAsE;AACtE,gEAGgC;AAchC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,SAAgB,oBAAoB,CAKlC,OAyBD;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,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACjD,UAAU,CAAC,KAAK,GAAG,KAAK,WAAW,GAAG,IAAW;YAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAA,wCAAkB,GAAE,CAAC;YACxD,IAAI,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC1B,MAAM,IAAI,SAAS,CACjB,0GAA0G,CAC3G,CAAC;YACJ,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpC,MAAM,GAAG,GAAG,SAAS;oBACnB,CAAC,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,MAAgB,CAAC;oBACnD,CAAC,CAAC,SAAS,CAAC;gBACd,MAAM,OAAO,GACX,GAAG,KAAK,SAAS;oBACf,CAAC,CAAC,SAAS;oBACX,CAAC,CAAE,MAAqD,CAAC,GAAG,CAAC,CAAC;gBAClE,IAAI,OAAO;oBAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBACnC,IAAI,OAAO,CAAC,SAAS;oBACnB,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAa,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;gBAC7D,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}