@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,208 @@
1
+ import type { MutationPatch } from '../decorators/apply-mutation.decorator';
2
+ import { RootEntitySnapshot } from '../interfaces/root-entity-snapshot.interface';
3
+ import { AggregateRoot } from './aggregate-root';
4
+ /**
5
+ * Abstract base entity for DDD domain aggregates and entities.
6
+ *
7
+ * Inherits from {@link AggregateRoot} to manage domain events
8
+ * internally:
9
+ * - **Domain Event Recording**: Call `this.apply(new SomeEvent(this))` to buffer uncommitted events.
10
+ * - **UUID v7 Identity**: Automatically generates time-ordered UUID v7 identifiers for new instances.
11
+ * - **Lifecycle Timestamps**: Enforces invariant-checked `createdAt` and `updatedAt` tracking.
12
+ * - **Accessor-Driven Persistence**: `id`, `createdAt` and `updatedAt` have public getters and
13
+ * private setters. An ORM hydrates through the setters (MikroORM: `accessor: true`); application code
14
+ * cannot assign them and changes state through factories and domain methods.
15
+ * - **Optimistic Concurrency Control**: Tracks integer aggregate versioning (`_version`, {@link getExpectedVersion}),
16
+ * incremented automatically on mutations to prevent concurrent lost updates.
17
+ * - **Polymorphic Rehydration**: Static `RootEntity.from()` transparently handles instances, plain snapshots,
18
+ * or nullish database results while enforcing strict aggregate type safety (throws `TypeError` on incompatible aggregates).
19
+ * - **Mutation Tracking**: Automatically updates `updatedAt`, increments `_version` on `@ApplyMutation()`-decorated methods,
20
+ * and triggers the `afterUpdate()` lifecycle hook.
21
+ *
22
+ * @example Defining a domain aggregate
23
+ * ```typescript
24
+ * interface UserSnapshot extends Partial<RootEntitySnapshot> {
25
+ * readonly username: string;
26
+ * readonly email: string;
27
+ * readonly version?: number;
28
+ * }
29
+ *
30
+ * export class User extends RootEntity<UserSnapshot> {
31
+ * @Mutable<string>()
32
+ * private _username: string;
33
+ * readonly email: string;
34
+ *
35
+ * private constructor(snapshot: UserSnapshot) {
36
+ * super(snapshot);
37
+ * this._username = snapshot.username!;
38
+ * this.email = snapshot.email!;
39
+ * }
40
+ *
41
+ * static create(username: string, email: string): User {
42
+ * const user = new User({ username, email });
43
+ * user.apply(new UserCreatedEvent(user));
44
+ * return user;
45
+ * }
46
+ *
47
+ * static fromJSON(snapshot: UserSnapshot): User {
48
+ * return new User(snapshot);
49
+ * }
50
+ *
51
+ * @ApplyMutation<User>({ event: (user) => new UserRenamedEvent(user) })
52
+ * rename(newUsername: string): this {
53
+ * this.applyPatch({ username: newUsername });
54
+ * return this;
55
+ * }
56
+ *
57
+ * toJSON(): RootEntitySnapshot & UserSnapshot {
58
+ * return this.freezeState({
59
+ * id: this.id,
60
+ * username: this._username,
61
+ * email: this.email,
62
+ * createdAt: this.createdAt,
63
+ * updatedAt: this.updatedAt,
64
+ * version: this._version,
65
+ * });
66
+ * }
67
+ * }
68
+ * ```
69
+ */
70
+ export declare abstract class RootEntity<TSnapshot extends Partial<RootEntitySnapshot> = RootEntitySnapshot> extends AggregateRoot implements RootEntitySnapshot {
71
+ private _id;
72
+ private _createdAt;
73
+ private _updatedAt;
74
+ protected _version: number;
75
+ protected _persistedVersion: number;
76
+ constructor(snapshot?: Partial<RootEntitySnapshot>);
77
+ protected static normalizeId(id?: string): string;
78
+ protected static normalizeDate(value?: Date | string): Date;
79
+ /**
80
+ * Rehydrates or returns an already-hydrated entity instance.
81
+ *
82
+ * If `candidate` is already an instance of the target entity class, it is returned as-is.
83
+ * If `candidate` is an instance of an incompatible `RootEntity`, a `TypeError` is thrown.
84
+ * Otherwise, if `candidate` is a snapshot object, it is rehydrated via the class's `fromJSON()` factory.
85
+ */
86
+ static from<T extends RootEntity<TSnapshot>, TSnapshot extends Partial<RootEntitySnapshot>>(this: {
87
+ fromJSON(snapshot: TSnapshot): T;
88
+ } | (abstract new (...args: unknown[]) => T), candidate: T | TSnapshot | null | undefined): T | null;
89
+ get id(): string;
90
+ /**
91
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
92
+ * cannot assign it and changes state through domain methods and factories.
93
+ */
94
+ private set id(value);
95
+ get createdAt(): Date;
96
+ /**
97
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
98
+ * cannot assign it and changes state through domain methods and factories.
99
+ */
100
+ private set createdAt(value);
101
+ get updatedAt(): Date;
102
+ /**
103
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
104
+ * cannot assign it and changes state through domain methods and factories.
105
+ */
106
+ private set updatedAt(value);
107
+ /**
108
+ * The current in-memory version: 1 for a new entity, advanced by each
109
+ * `@ApplyMutation()` method. Persistence writes store it; the version a write
110
+ * expects to find in storage is {@link getExpectedVersion}.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const user = User.create('ada', 'ada@example.com'); // user.version === 1
115
+ * user.rename('ada.l'); // user.version === 2
116
+ * user.getExpectedVersion(); // still 1 until persisted
117
+ * ```
118
+ */
119
+ get version(): number;
120
+ /**
121
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
122
+ * cannot assign it and changes state through domain methods and factories.
123
+ * A loaded version is also the persisted baseline; a value that is not a
124
+ * positive integer is ignored.
125
+ */
126
+ private set version(value);
127
+ /**
128
+ * Returns the expected persistence version baseline (`_persistedVersion`).
129
+ *
130
+ * This baseline represents the last confirmed durable version successfully written
131
+ * to persistent storage. Persistence adapters use this value in version predicates
132
+ * (e.g. `WHERE id = ? AND version = expectedVersion`) to detect concurrent updates.
133
+ *
134
+ * @returns The positive integer version expected in persistent storage.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const expected = user.getExpectedVersion(); // e.g. 1
139
+ * await em.nativeUpdate(User, { id: user.id, version: expected }, { ...data, version: user.version });
140
+ * ```
141
+ */
142
+ getExpectedVersion(): number;
143
+ /**
144
+ * Acknowledges that a version has been successfully persisted to durable storage.
145
+ * Advances the expected version baseline (`_persistedVersion`) to match the version
146
+ * actually written (by default `this._version`), without recording a domain event
147
+ * or advancing the in-memory version.
148
+ *
149
+ * This method is owned by persistence repositories and is typically invoked
150
+ * automatically by the `@AcknowledgePersisted` method decorator upon successful
151
+ * write resolution. Application code must never call this method directly; state
152
+ * mutations belong in domain methods.
153
+ *
154
+ * @param version Optional specific version that was persisted. If omitted,
155
+ * `this._version` is used. Must be a positive integer (> 0).
156
+ * @throws {Error} If `version` is provided but is not a positive integer.
157
+ *
158
+ * @example Manual invocation in a custom persistence repository
159
+ * ```typescript
160
+ * async save(user: User): Promise<UserSnapshot> {
161
+ * const snapshot = user.toJSON();
162
+ * await this.db.update(user.id, snapshot);
163
+ * user.acknowledgePersisted(); // advances _persistedVersion to user.version
164
+ * return snapshot;
165
+ * }
166
+ * ```
167
+ *
168
+ * @example Declarative invocation via `@AcknowledgePersisted` (recommended)
169
+ * ```typescript
170
+ * @AcknowledgePersisted<[User]>({ entity: ([user]) => user })
171
+ * async save(user: User): Promise<UserSnapshot> {
172
+ * // Decorator captures user.version before execution and calls
173
+ * // user.acknowledgePersisted() when the returned promise resolves.
174
+ * return snapshot;
175
+ * }
176
+ * ```
177
+ */
178
+ acknowledgePersisted(version?: number): void;
179
+ /**
180
+ * Applies a patch to fields registered with `@Mutable()`, ignoring undefined values.
181
+ * Validates all keys and normalizes all values before writing any field.
182
+ * Does not advance version/timestamps or record events; use inside a decorated mutation.
183
+ *
184
+ * @param patch - Public field names and proposed values; keys must be registered with `@Mutable()`.
185
+ * @throws {UnknownMutableFieldError} When a defined patch key is not registered.
186
+ * @throws Propagates field normalization errors without applying the patch.
187
+ *
188
+ * @example Inside a User aggregate with a mutable username field
189
+ * ```ts
190
+ * @ApplyMutation<User>({ event: (user) => new UserUpdatedEvent(user) })
191
+ * update(username: string): this {
192
+ * this.applyPatch<User>({ username });
193
+ * return this;
194
+ * }
195
+ * ```
196
+ */
197
+ protected applyPatch<TEntity extends object>(patch: MutationPatch<TEntity>): void;
198
+ protected onUpdate(): void;
199
+ protected freezeState<S extends object>(state: S): Readonly<S>;
200
+ /**
201
+ * Post-mutation lifecycle hook, invoked after the version and `updatedAt`
202
+ * advance and before the mutation's event is recorded. The default does
203
+ * nothing; override it only when the aggregate has post-mutation work.
204
+ */
205
+ protected afterUpdate(): void;
206
+ abstract toJSON(): RootEntitySnapshot & TSnapshot;
207
+ }
208
+ //# sourceMappingURL=root.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.entity.d.ts","sourceRoot":"","sources":["../../../domain/models/root.entity.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,8BAAsB,UAAU,CAC5B,SAAS,SAAS,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAEpE,SAAQ,aACR,YAAW,kBAAkB;IAE7B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,UAAU,CAAO;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC;gBAExB,QAAQ,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;IAwClD,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAOjD,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI;IAgB3D;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CACT,CAAC,SAAS,UAAU,CAAC,SAAS,CAAC,EAC/B,SAAS,SAAS,OAAO,CAAC,kBAAkB,CAAC,EAE7C,IAAI,EACA;QAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAA;KAAE,GACpC,CAAC,QAAQ,MACP,GAAG,IAAI,EAAE,OAAO,EAAE,KACf,CAAC,CAAC,EACX,SAAS,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,GAC1C,CAAC,GAAG,IAAI;IA4BX,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;;OAGG;IACH,OAAO,KAAK,EAAE,QAEb;IAED,IAAI,SAAS,IAAI,IAAI,CAEpB;IAED;;;OAGG;IACH,OAAO,KAAK,SAAS,QAEpB;IAED,IAAI,SAAS,IAAI,IAAI,CAEpB;IAED;;;OAGG;IACH,OAAO,KAAK,SAAS,QAEpB;IAED;;;;;;;;;;;OAWG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;;;OAKG;IACH,OAAO,KAAK,OAAO,QAKlB;IAED;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAc5C;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,UAAU,CAAC,OAAO,SAAS,MAAM,EACzC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAC5B,IAAI;IAwCP,SAAS,CAAC,QAAQ,IAAI,IAAI;IAM1B,SAAS,CAAC,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAI9D;;;;OAIG;IACH,SAAS,CAAC,WAAW,IAAI,IAAI;IAE7B,QAAQ,CAAC,MAAM,IAAI,kBAAkB,GAAG,SAAS;CAClD"}
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RootEntity = void 0;
5
+ const uuidv7_1 = require("@cqrs-ddd/uuidv7");
6
+ const mutable_decorator_1 = require("../decorators/mutable.decorator");
7
+ const unknown_mutable_field_error_1 = require("../exceptions/unknown-mutable-field.error");
8
+ const aggregate_root_1 = require("./aggregate-root");
9
+ /**
10
+ * Abstract base entity for DDD domain aggregates and entities.
11
+ *
12
+ * Inherits from {@link AggregateRoot} to manage domain events
13
+ * internally:
14
+ * - **Domain Event Recording**: Call `this.apply(new SomeEvent(this))` to buffer uncommitted events.
15
+ * - **UUID v7 Identity**: Automatically generates time-ordered UUID v7 identifiers for new instances.
16
+ * - **Lifecycle Timestamps**: Enforces invariant-checked `createdAt` and `updatedAt` tracking.
17
+ * - **Accessor-Driven Persistence**: `id`, `createdAt` and `updatedAt` have public getters and
18
+ * private setters. An ORM hydrates through the setters (MikroORM: `accessor: true`); application code
19
+ * cannot assign them and changes state through factories and domain methods.
20
+ * - **Optimistic Concurrency Control**: Tracks integer aggregate versioning (`_version`, {@link getExpectedVersion}),
21
+ * incremented automatically on mutations to prevent concurrent lost updates.
22
+ * - **Polymorphic Rehydration**: Static `RootEntity.from()` transparently handles instances, plain snapshots,
23
+ * or nullish database results while enforcing strict aggregate type safety (throws `TypeError` on incompatible aggregates).
24
+ * - **Mutation Tracking**: Automatically updates `updatedAt`, increments `_version` on `@ApplyMutation()`-decorated methods,
25
+ * and triggers the `afterUpdate()` lifecycle hook.
26
+ *
27
+ * @example Defining a domain aggregate
28
+ * ```typescript
29
+ * interface UserSnapshot extends Partial<RootEntitySnapshot> {
30
+ * readonly username: string;
31
+ * readonly email: string;
32
+ * readonly version?: number;
33
+ * }
34
+ *
35
+ * export class User extends RootEntity<UserSnapshot> {
36
+ * @Mutable<string>()
37
+ * private _username: string;
38
+ * readonly email: string;
39
+ *
40
+ * private constructor(snapshot: UserSnapshot) {
41
+ * super(snapshot);
42
+ * this._username = snapshot.username!;
43
+ * this.email = snapshot.email!;
44
+ * }
45
+ *
46
+ * static create(username: string, email: string): User {
47
+ * const user = new User({ username, email });
48
+ * user.apply(new UserCreatedEvent(user));
49
+ * return user;
50
+ * }
51
+ *
52
+ * static fromJSON(snapshot: UserSnapshot): User {
53
+ * return new User(snapshot);
54
+ * }
55
+ *
56
+ * @ApplyMutation<User>({ event: (user) => new UserRenamedEvent(user) })
57
+ * rename(newUsername: string): this {
58
+ * this.applyPatch({ username: newUsername });
59
+ * return this;
60
+ * }
61
+ *
62
+ * toJSON(): RootEntitySnapshot & UserSnapshot {
63
+ * return this.freezeState({
64
+ * id: this.id,
65
+ * username: this._username,
66
+ * email: this.email,
67
+ * createdAt: this.createdAt,
68
+ * updatedAt: this.updatedAt,
69
+ * version: this._version,
70
+ * });
71
+ * }
72
+ * }
73
+ * ```
74
+ */
75
+ class RootEntity extends aggregate_root_1.AggregateRoot {
76
+ _id;
77
+ _createdAt;
78
+ _updatedAt;
79
+ _version;
80
+ _persistedVersion;
81
+ constructor(snapshot) {
82
+ super();
83
+ const id = snapshot?.id;
84
+ const createdAt = snapshot?.createdAt;
85
+ const updatedAt = snapshot?.updatedAt;
86
+ const version = snapshot?.version;
87
+ this._version =
88
+ typeof version === 'number' && Number.isInteger(version) && version > 0
89
+ ? version
90
+ : 1;
91
+ this._persistedVersion = this._version;
92
+ if (id !== undefined &&
93
+ createdAt !== undefined &&
94
+ updatedAt !== undefined) {
95
+ this._id = RootEntity.normalizeId(id);
96
+ this._createdAt = RootEntity.normalizeDate(createdAt);
97
+ this._updatedAt = RootEntity.normalizeDate(updatedAt);
98
+ return;
99
+ }
100
+ if (id !== undefined ||
101
+ createdAt !== undefined ||
102
+ updatedAt !== undefined) {
103
+ throw new Error('id, createdAt, and updatedAt must be provided together when rehydrating an entity.');
104
+ }
105
+ const now = new Date();
106
+ this._id = (0, uuidv7_1.uuidv7)();
107
+ this._createdAt = now;
108
+ this._updatedAt = now;
109
+ }
110
+ static normalizeId(id) {
111
+ if (typeof id !== 'string' || !(0, uuidv7_1.isUuidV7)(id)) {
112
+ throw new Error('id must be a valid UUID v7.');
113
+ }
114
+ return id.trim();
115
+ }
116
+ static normalizeDate(value) {
117
+ if (value === undefined || value === null) {
118
+ throw new Error('Date is empty.');
119
+ }
120
+ if (typeof value === 'string' && value.trim().length === 0) {
121
+ throw new Error('Date is empty.');
122
+ }
123
+ const parsed = new Date(value);
124
+ if (Number.isNaN(parsed.getTime()) || parsed.getUTCFullYear() <= 1) {
125
+ throw new Error('Date must be a valid non-empty date.');
126
+ }
127
+ return parsed;
128
+ }
129
+ /**
130
+ * Rehydrates or returns an already-hydrated entity instance.
131
+ *
132
+ * If `candidate` is already an instance of the target entity class, it is returned as-is.
133
+ * If `candidate` is an instance of an incompatible `RootEntity`, a `TypeError` is thrown.
134
+ * Otherwise, if `candidate` is a snapshot object, it is rehydrated via the class's `fromJSON()` factory.
135
+ */
136
+ static from(candidate) {
137
+ if (candidate === null || candidate === undefined) {
138
+ return null;
139
+ }
140
+ // biome-ignore lint/complexity/noThisInStatic: Polymorphic static rehydration
141
+ const targetClass = this;
142
+ if (typeof targetClass === 'function') {
143
+ if (candidate instanceof targetClass) {
144
+ return candidate;
145
+ }
146
+ if (candidate instanceof RootEntity) {
147
+ const expectedName = targetClass.name || 'TargetEntity';
148
+ const actualName = candidate.constructor?.name || 'RootEntity';
149
+ throw new TypeError(`Cannot rehydrate entity: expected instance of ${expectedName}, received incompatible aggregate ${actualName}.`);
150
+ }
151
+ }
152
+ else if (candidate instanceof RootEntity) {
153
+ return candidate;
154
+ }
155
+ // biome-ignore lint/complexity/noThisInStatic: Polymorphic static rehydration
156
+ const ctor = this;
157
+ if (typeof ctor.fromJSON === 'function') {
158
+ return ctor.fromJSON(candidate);
159
+ }
160
+ throw new Error('Cannot rehydrate entity: missing fromJSON factory.');
161
+ }
162
+ get id() {
163
+ return this._id;
164
+ }
165
+ /**
166
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
167
+ * cannot assign it and changes state through domain methods and factories.
168
+ */
169
+ set id(value) {
170
+ this._id = RootEntity.normalizeId(value);
171
+ }
172
+ get createdAt() {
173
+ return new Date(this._createdAt);
174
+ }
175
+ /**
176
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
177
+ * cannot assign it and changes state through domain methods and factories.
178
+ */
179
+ set createdAt(value) {
180
+ this._createdAt = RootEntity.normalizeDate(value);
181
+ }
182
+ get updatedAt() {
183
+ return new Date(this._updatedAt);
184
+ }
185
+ /**
186
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
187
+ * cannot assign it and changes state through domain methods and factories.
188
+ */
189
+ set updatedAt(value) {
190
+ this._updatedAt = RootEntity.normalizeDate(value);
191
+ }
192
+ /**
193
+ * The current in-memory version: 1 for a new entity, advanced by each
194
+ * `@ApplyMutation()` method. Persistence writes store it; the version a write
195
+ * expects to find in storage is {@link getExpectedVersion}.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const user = User.create('ada', 'ada@example.com'); // user.version === 1
200
+ * user.rename('ada.l'); // user.version === 2
201
+ * user.getExpectedVersion(); // still 1 until persisted
202
+ * ```
203
+ */
204
+ get version() {
205
+ return this._version;
206
+ }
207
+ /**
208
+ * For ORM hydration only (MikroORM: `accessor: true`). Private, so application code
209
+ * cannot assign it and changes state through domain methods and factories.
210
+ * A loaded version is also the persisted baseline; a value that is not a
211
+ * positive integer is ignored.
212
+ */
213
+ set version(value) {
214
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
215
+ this._version = value;
216
+ this._persistedVersion = value;
217
+ }
218
+ }
219
+ /**
220
+ * Returns the expected persistence version baseline (`_persistedVersion`).
221
+ *
222
+ * This baseline represents the last confirmed durable version successfully written
223
+ * to persistent storage. Persistence adapters use this value in version predicates
224
+ * (e.g. `WHERE id = ? AND version = expectedVersion`) to detect concurrent updates.
225
+ *
226
+ * @returns The positive integer version expected in persistent storage.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const expected = user.getExpectedVersion(); // e.g. 1
231
+ * await em.nativeUpdate(User, { id: user.id, version: expected }, { ...data, version: user.version });
232
+ * ```
233
+ */
234
+ getExpectedVersion() {
235
+ return this._persistedVersion;
236
+ }
237
+ /**
238
+ * Acknowledges that a version has been successfully persisted to durable storage.
239
+ * Advances the expected version baseline (`_persistedVersion`) to match the version
240
+ * actually written (by default `this._version`), without recording a domain event
241
+ * or advancing the in-memory version.
242
+ *
243
+ * This method is owned by persistence repositories and is typically invoked
244
+ * automatically by the `@AcknowledgePersisted` method decorator upon successful
245
+ * write resolution. Application code must never call this method directly; state
246
+ * mutations belong in domain methods.
247
+ *
248
+ * @param version Optional specific version that was persisted. If omitted,
249
+ * `this._version` is used. Must be a positive integer (> 0).
250
+ * @throws {Error} If `version` is provided but is not a positive integer.
251
+ *
252
+ * @example Manual invocation in a custom persistence repository
253
+ * ```typescript
254
+ * async save(user: User): Promise<UserSnapshot> {
255
+ * const snapshot = user.toJSON();
256
+ * await this.db.update(user.id, snapshot);
257
+ * user.acknowledgePersisted(); // advances _persistedVersion to user.version
258
+ * return snapshot;
259
+ * }
260
+ * ```
261
+ *
262
+ * @example Declarative invocation via `@AcknowledgePersisted` (recommended)
263
+ * ```typescript
264
+ * @AcknowledgePersisted<[User]>({ entity: ([user]) => user })
265
+ * async save(user: User): Promise<UserSnapshot> {
266
+ * // Decorator captures user.version before execution and calls
267
+ * // user.acknowledgePersisted() when the returned promise resolves.
268
+ * return snapshot;
269
+ * }
270
+ * ```
271
+ */
272
+ acknowledgePersisted(version) {
273
+ if (typeof version === 'number') {
274
+ if (!Number.isInteger(version) || version <= 0) {
275
+ throw new Error('Persisted version must be a positive integer.');
276
+ }
277
+ this._persistedVersion = version;
278
+ if (this._version < version) {
279
+ this._version = version;
280
+ }
281
+ return;
282
+ }
283
+ this._persistedVersion = this._version;
284
+ }
285
+ /**
286
+ * Applies a patch to fields registered with `@Mutable()`, ignoring undefined values.
287
+ * Validates all keys and normalizes all values before writing any field.
288
+ * Does not advance version/timestamps or record events; use inside a decorated mutation.
289
+ *
290
+ * @param patch - Public field names and proposed values; keys must be registered with `@Mutable()`.
291
+ * @throws {UnknownMutableFieldError} When a defined patch key is not registered.
292
+ * @throws Propagates field normalization errors without applying the patch.
293
+ *
294
+ * @example Inside a User aggregate with a mutable username field
295
+ * ```ts
296
+ * @ApplyMutation<User>({ event: (user) => new UserUpdatedEvent(user) })
297
+ * update(username: string): this {
298
+ * this.applyPatch<User>({ username });
299
+ * return this;
300
+ * }
301
+ * ```
302
+ */
303
+ applyPatch(patch) {
304
+ if (patch === undefined || patch === null) {
305
+ return;
306
+ }
307
+ if (typeof patch !== 'object') {
308
+ throw new TypeError('applyPatch() requires a field patch object, or nothing.');
309
+ }
310
+ const fields = (0, mutable_decorator_1.getMutableFields)(this);
311
+ const writes = [];
312
+ // Phase 1: validate all keys and normalize values before any state changes
313
+ for (const [key, value] of Object.entries(patch)) {
314
+ if (value === undefined) {
315
+ continue;
316
+ }
317
+ const field = fields.get(key);
318
+ if (!field) {
319
+ throw new unknown_mutable_field_error_1.UnknownMutableFieldError(this.constructor.name, key, [
320
+ ...fields.keys(),
321
+ ]);
322
+ }
323
+ const normalizedValue = field.normalize ? field.normalize(value) : value;
324
+ writes.push({
325
+ propertyKey: field.propertyKey,
326
+ value: normalizedValue,
327
+ });
328
+ }
329
+ // Phase 2: apply normalized writes to backing properties
330
+ for (const { propertyKey, value } of writes) {
331
+ this[propertyKey] = value;
332
+ }
333
+ }
334
+ onUpdate() {
335
+ this._version += 1;
336
+ this._updatedAt = new Date();
337
+ this.afterUpdate();
338
+ }
339
+ freezeState(state) {
340
+ return Object.freeze(state);
341
+ }
342
+ /**
343
+ * Post-mutation lifecycle hook, invoked after the version and `updatedAt`
344
+ * advance and before the mutation's event is recorded. The default does
345
+ * nothing; override it only when the aggregate has post-mutation work.
346
+ */
347
+ afterUpdate() { }
348
+ }
349
+ exports.RootEntity = RootEntity;
350
+ //# sourceMappingURL=root.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.entity.js","sourceRoot":"","sources":["../../../domain/models/root.entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import type { ValueViolationError } from './value-violation';
2
+ /** Options of {@link numberRule}. */
3
+ export interface NumberRuleOptions {
4
+ /** Field name reported in the violation and in the default message. */
5
+ readonly field: string;
6
+ /**
7
+ * `false` accepts `null` and `undefined`, and normalizes them to `null`.
8
+ * Defaults to `true`: they are a `required` violation.
9
+ */
10
+ readonly required?: boolean;
11
+ /**
12
+ * `true` accepts safe integers only (`Number.isSafeInteger`), so the value
13
+ * survives a round trip through JSON and a `bigint` column.
14
+ */
15
+ readonly integer?: boolean;
16
+ /** Smallest accepted value, inclusive. */
17
+ readonly min?: number;
18
+ /** Largest accepted value, inclusive. */
19
+ readonly max?: number;
20
+ /**
21
+ * Builds the error thrown for a violation. Defaults to
22
+ * `InvalidValueException`.
23
+ */
24
+ readonly error?: ValueViolationError;
25
+ }
26
+ /** What {@link NumberRule.parse} returns: `number | null` when not required. */
27
+ export type NumberRuleValue<TOptions extends NumberRuleOptions> = TOptions extends {
28
+ readonly required: true;
29
+ } ? number : TOptions extends {
30
+ readonly required: boolean;
31
+ } ? number | null : number;
32
+ /** A number rule: its options, frozen, and the parser that enforces them. */
33
+ export type NumberRule<TOptions extends NumberRuleOptions = NumberRuleOptions> = Readonly<TOptions> & {
34
+ /**
35
+ * Returns the value unchanged, or `null` for a missing value the rule
36
+ * does not require. A standalone function: pass it without binding.
37
+ *
38
+ * @throws The `error` option's result, by default `InvalidValueException`.
39
+ */
40
+ readonly parse: (value: unknown) => NumberRuleValue<TOptions>;
41
+ };
42
+ /**
43
+ * Defines the rule for one number field of an aggregate: declared once, used
44
+ * by the aggregate to parse the value, and read by other layers (such as a
45
+ * request schema) for its limits.
46
+ *
47
+ * `parse(value)` checks, in this order:
48
+ * 1. `null` or `undefined`: `null`, or a `required` violation;
49
+ * 2. not a number: a `type` violation (a numeric string is not converted);
50
+ * 3. `NaN` or an infinity: a `finite` violation;
51
+ * 4. with `integer`, not a safe integer: an `integer` violation;
52
+ * 5. `min`, then `max`.
53
+ *
54
+ * @param options - The field name, its constraints and its error.
55
+ * @returns The frozen rule.
56
+ * @throws TypeError when an option is invalid: an empty `field`, a bound that
57
+ * is not a finite number, or `min` above `max`.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * export class Order extends RootEntity<OrderSnapshot> {
62
+ * static readonly rules = {
63
+ * quantity: numberRule({
64
+ * field: 'quantity',
65
+ * integer: true,
66
+ * min: 1,
67
+ * max: 100,
68
+ * error: (violation) => new InvalidQuantityException(violation),
69
+ * }),
70
+ * } as const;
71
+ *
72
+ * @Mutable<number>({ normalize: (value) => Order.rules.quantity.parse(value) })
73
+ * private _quantity: number;
74
+ * }
75
+ *
76
+ * Order.rules.quantity.parse(0);
77
+ * // throws InvalidQuantityException: "quantity must be at least 1."
78
+ * ```
79
+ */
80
+ export declare function numberRule<const TOptions extends NumberRuleOptions>(options: TOptions): NumberRule<TOptions>;
81
+ //# sourceMappingURL=number.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number.rule.d.ts","sourceRoot":"","sources":["../../../domain/rules/number.rule.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,gFAAgF;AAChF,MAAM,MAAM,eAAe,CAAC,QAAQ,SAAS,iBAAiB,IAC5D,QAAQ,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GACxC,MAAM,GACN,QAAQ,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC7C,MAAM,GAAG,IAAI,GACb,MAAM,CAAC;AAEf,6EAA6E;AAC7E,MAAM,MAAM,UAAU,CAAC,QAAQ,SAAS,iBAAiB,GAAG,iBAAiB,IAC3E,QAAQ,CAAC,QAAQ,CAAC,GAAG;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,eAAe,CAAC,QAAQ,CAAC,CAAC;CAC/D,CAAC;AAQJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,UAAU,CAAC,KAAK,CAAC,QAAQ,SAAS,iBAAiB,EACjE,OAAO,EAAE,QAAQ,GAChB,UAAU,CAAC,QAAQ,CAAC,CAkCtB"}