@cratis/chronicle 3.0.0 → 3.1.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 (319) hide show
  1. package/EventStore.ts +17 -1
  2. package/IEventStore.ts +16 -0
  3. package/auditing/CausationType.ts +10 -0
  4. package/compliance/ComplianceContracts.ts +84 -0
  5. package/compliance/IPIIManager.ts +17 -0
  6. package/compliance/PIIManager.ts +31 -0
  7. package/compliance/index.ts +2 -0
  8. package/connection/ChronicleConnection.ts +7 -1
  9. package/connection/ChronicleServices.ts +2 -0
  10. package/connection/Guid.ts +18 -0
  11. package/dist/EventStore.d.ts +8 -0
  12. package/dist/EventStore.d.ts.map +1 -1
  13. package/dist/EventStore.js +13 -1
  14. package/dist/EventStore.js.map +1 -1
  15. package/dist/IEventStore.d.ts +12 -0
  16. package/dist/IEventStore.d.ts.map +1 -1
  17. package/dist/auditing/CausationType.d.ts +8 -0
  18. package/dist/auditing/CausationType.d.ts.map +1 -1
  19. package/dist/auditing/CausationType.js +8 -0
  20. package/dist/auditing/CausationType.js.map +1 -1
  21. package/dist/compliance/ComplianceContracts.d.ts +40 -0
  22. package/dist/compliance/ComplianceContracts.d.ts.map +1 -1
  23. package/dist/compliance/ComplianceContracts.js +71 -0
  24. package/dist/compliance/ComplianceContracts.js.map +1 -1
  25. package/dist/compliance/IPIIManager.d.ts +15 -0
  26. package/dist/compliance/IPIIManager.d.ts.map +1 -0
  27. package/dist/compliance/IPIIManager.js +4 -0
  28. package/dist/compliance/IPIIManager.js.map +1 -0
  29. package/dist/compliance/PIIManager.d.ts +20 -0
  30. package/dist/compliance/PIIManager.d.ts.map +1 -0
  31. package/dist/compliance/PIIManager.js +30 -0
  32. package/dist/compliance/PIIManager.js.map +1 -0
  33. package/dist/compliance/index.d.ts +2 -0
  34. package/dist/compliance/index.d.ts.map +1 -1
  35. package/dist/compliance/index.js +1 -0
  36. package/dist/compliance/index.js.map +1 -1
  37. package/dist/connection/ChronicleConnection.d.ts +1 -0
  38. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  39. package/dist/connection/ChronicleConnection.js +6 -1
  40. package/dist/connection/ChronicleConnection.js.map +1 -1
  41. package/dist/connection/ChronicleServices.d.ts +2 -0
  42. package/dist/connection/ChronicleServices.d.ts.map +1 -1
  43. package/dist/connection/Guid.d.ts +5 -0
  44. package/dist/connection/Guid.d.ts.map +1 -1
  45. package/dist/connection/Guid.js +15 -0
  46. package/dist/connection/Guid.js.map +1 -1
  47. package/dist/eventSequences/AppendOperationsBroadcaster.d.ts +16 -0
  48. package/dist/eventSequences/AppendOperationsBroadcaster.d.ts.map +1 -0
  49. package/dist/eventSequences/AppendOperationsBroadcaster.js +77 -0
  50. package/dist/eventSequences/AppendOperationsBroadcaster.js.map +1 -0
  51. package/dist/eventSequences/AppendOptions.d.ts +7 -0
  52. package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
  53. package/dist/eventSequences/AppendResult.d.ts +14 -0
  54. package/dist/eventSequences/AppendResult.d.ts.map +1 -1
  55. package/dist/eventSequences/AppendedEventWithResult.d.ts +13 -0
  56. package/dist/eventSequences/AppendedEventWithResult.d.ts.map +1 -0
  57. package/dist/eventSequences/AppendedEventWithResult.js +4 -0
  58. package/dist/eventSequences/AppendedEventWithResult.js.map +1 -0
  59. package/dist/eventSequences/CompleteStreamError.d.ts +17 -0
  60. package/dist/eventSequences/CompleteStreamError.d.ts.map +1 -0
  61. package/dist/eventSequences/CompleteStreamError.js +20 -0
  62. package/dist/eventSequences/CompleteStreamError.js.map +1 -0
  63. package/dist/eventSequences/CompleteStreamResult.d.ts +14 -0
  64. package/dist/eventSequences/CompleteStreamResult.d.ts.map +1 -0
  65. package/dist/eventSequences/CompleteStreamResult.js +4 -0
  66. package/dist/eventSequences/CompleteStreamResult.js.map +1 -0
  67. package/dist/eventSequences/ConcurrencyViolation.d.ts +13 -0
  68. package/dist/eventSequences/ConcurrencyViolation.d.ts.map +1 -0
  69. package/dist/eventSequences/ConcurrencyViolation.js +4 -0
  70. package/dist/eventSequences/ConcurrencyViolation.js.map +1 -0
  71. package/dist/eventSequences/EventForEventSourceId.d.ts +8 -0
  72. package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
  73. package/dist/eventSequences/EventSequence.d.ts +35 -1
  74. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  75. package/dist/eventSequences/EventSequence.js +350 -19
  76. package/dist/eventSequences/EventSequence.js.map +1 -1
  77. package/dist/eventSequences/IEventSequence.d.ts +85 -1
  78. package/dist/eventSequences/IEventSequence.d.ts.map +1 -1
  79. package/dist/eventSequences/WaitForCompletionResult.d.ts +11 -0
  80. package/dist/eventSequences/WaitForCompletionResult.d.ts.map +1 -0
  81. package/dist/eventSequences/WaitForCompletionResult.js +4 -0
  82. package/dist/eventSequences/WaitForCompletionResult.js.map +1 -0
  83. package/dist/eventSequences/index.d.ts +5 -0
  84. package/dist/eventSequences/index.d.ts.map +1 -1
  85. package/dist/eventSequences/index.js +1 -0
  86. package/dist/eventSequences/index.js.map +1 -1
  87. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts +3 -0
  88. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts.map +1 -1
  89. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js +9 -0
  90. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js.map +1 -1
  91. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.d.ts +6 -0
  92. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.d.ts.map +1 -1
  93. package/dist/eventStoreSubscriptions/contracts.d.ts +18 -0
  94. package/dist/eventStoreSubscriptions/contracts.d.ts.map +1 -1
  95. package/dist/eventStoreSubscriptions/contracts.js +106 -0
  96. package/dist/eventStoreSubscriptions/contracts.js.map +1 -1
  97. package/dist/events/constraints/Constraints.js +1 -1
  98. package/dist/events/constraints/Constraints.js.map +1 -1
  99. package/dist/externalServices/ExternalServiceBuilder.d.ts +42 -0
  100. package/dist/externalServices/ExternalServiceBuilder.d.ts.map +1 -0
  101. package/dist/externalServices/ExternalServiceBuilder.js +110 -0
  102. package/dist/externalServices/ExternalServiceBuilder.js.map +1 -0
  103. package/dist/externalServices/ExternalServices.d.ts +19 -0
  104. package/dist/externalServices/ExternalServices.d.ts.map +1 -0
  105. package/dist/externalServices/ExternalServices.js +30 -0
  106. package/dist/externalServices/ExternalServices.js.map +1 -0
  107. package/dist/externalServices/ExternalServicesContracts.d.ts +137 -0
  108. package/dist/externalServices/ExternalServicesContracts.d.ts.map +1 -0
  109. package/dist/externalServices/ExternalServicesContracts.js +654 -0
  110. package/dist/externalServices/ExternalServicesContracts.js.map +1 -0
  111. package/dist/externalServices/IExternalServiceBuilder.d.ts +67 -0
  112. package/dist/externalServices/IExternalServiceBuilder.d.ts.map +1 -0
  113. package/dist/externalServices/IExternalServiceBuilder.js +4 -0
  114. package/dist/externalServices/IExternalServiceBuilder.js.map +1 -0
  115. package/dist/externalServices/IExternalServices.d.ts +13 -0
  116. package/dist/externalServices/IExternalServices.d.ts.map +1 -0
  117. package/dist/externalServices/IExternalServices.js +4 -0
  118. package/dist/externalServices/IExternalServices.js.map +1 -0
  119. package/dist/externalServices/index.d.ts +5 -0
  120. package/dist/externalServices/index.d.ts.map +1 -0
  121. package/dist/externalServices/index.js +5 -0
  122. package/dist/externalServices/index.js.map +1 -0
  123. package/dist/identities/IIdentityManager.d.ts +17 -0
  124. package/dist/identities/IIdentityManager.d.ts.map +1 -0
  125. package/dist/identities/IIdentityManager.js +4 -0
  126. package/dist/identities/IIdentityManager.js.map +1 -0
  127. package/dist/identities/IdentityManager.d.ts +20 -0
  128. package/dist/identities/IdentityManager.d.ts.map +1 -0
  129. package/dist/identities/IdentityManager.js +31 -0
  130. package/dist/identities/IdentityManager.js.map +1 -0
  131. package/dist/identities/index.d.ts +3 -0
  132. package/dist/identities/index.d.ts.map +1 -0
  133. package/dist/identities/index.js +4 -0
  134. package/dist/identities/index.js.map +1 -0
  135. package/dist/index.d.ts +5 -1
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +5 -1
  138. package/dist/index.js.map +1 -1
  139. package/dist/observation/FailedPartition.d.ts +15 -0
  140. package/dist/observation/FailedPartition.d.ts.map +1 -0
  141. package/dist/observation/FailedPartition.js +4 -0
  142. package/dist/observation/FailedPartition.js.map +1 -0
  143. package/dist/observation/FailedPartitionAttempt.d.ts +15 -0
  144. package/dist/observation/FailedPartitionAttempt.d.ts.map +1 -0
  145. package/dist/observation/FailedPartitionAttempt.js +4 -0
  146. package/dist/observation/FailedPartitionAttempt.js.map +1 -0
  147. package/dist/observation/FailedPartitions.d.ts +24 -0
  148. package/dist/observation/FailedPartitions.d.ts.map +1 -0
  149. package/dist/observation/FailedPartitions.js +39 -0
  150. package/dist/observation/FailedPartitions.js.map +1 -0
  151. package/dist/observation/ICanBeNotifiedWhenPartitionReplayed.d.ts +19 -0
  152. package/dist/observation/ICanBeNotifiedWhenPartitionReplayed.d.ts.map +1 -0
  153. package/dist/observation/ICanBeNotifiedWhenPartitionReplayed.js +4 -0
  154. package/dist/observation/ICanBeNotifiedWhenPartitionReplayed.js.map +1 -0
  155. package/dist/observation/ICanBeNotifiedWhenReplay.d.ts +17 -0
  156. package/dist/observation/ICanBeNotifiedWhenReplay.d.ts.map +1 -0
  157. package/dist/observation/ICanBeNotifiedWhenReplay.js +4 -0
  158. package/dist/observation/ICanBeNotifiedWhenReplay.js.map +1 -0
  159. package/dist/observation/IFailedPartitions.d.ts +18 -0
  160. package/dist/observation/IFailedPartitions.d.ts.map +1 -0
  161. package/dist/observation/IFailedPartitions.js +4 -0
  162. package/dist/observation/IFailedPartitions.js.map +1 -0
  163. package/dist/observation/index.d.ts +6 -0
  164. package/dist/observation/index.d.ts.map +1 -1
  165. package/dist/observation/index.js +1 -0
  166. package/dist/observation/index.js.map +1 -1
  167. package/dist/observation/notifyReplayLifecycle.d.ts +13 -0
  168. package/dist/observation/notifyReplayLifecycle.d.ts.map +1 -0
  169. package/dist/observation/notifyReplayLifecycle.js +54 -0
  170. package/dist/observation/notifyReplayLifecycle.js.map +1 -0
  171. package/dist/observation/toClientFailedPartition.d.ts +9 -0
  172. package/dist/observation/toClientFailedPartition.d.ts.map +1 -0
  173. package/dist/observation/toClientFailedPartition.js +23 -0
  174. package/dist/observation/toClientFailedPartition.js.map +1 -0
  175. package/dist/projections/Projections.d.ts +0 -4
  176. package/dist/projections/Projections.d.ts.map +1 -1
  177. package/dist/projections/Projections.js +25 -103
  178. package/dist/projections/Projections.js.map +1 -1
  179. package/dist/projections/declarative/AddBuilder.d.ts +17 -0
  180. package/dist/projections/declarative/AddBuilder.d.ts.map +1 -0
  181. package/dist/projections/declarative/AddBuilder.js +28 -0
  182. package/dist/projections/declarative/AddBuilder.js.map +1 -0
  183. package/dist/projections/declarative/AddChildBuilder.d.ts +56 -0
  184. package/dist/projections/declarative/AddChildBuilder.d.ts.map +1 -0
  185. package/dist/projections/declarative/AddChildBuilder.js +72 -0
  186. package/dist/projections/declarative/AddChildBuilder.js.map +1 -0
  187. package/dist/projections/declarative/ChildrenBuilder.d.ts +28 -0
  188. package/dist/projections/declarative/ChildrenBuilder.d.ts.map +1 -0
  189. package/dist/projections/declarative/ChildrenBuilder.js +72 -0
  190. package/dist/projections/declarative/ChildrenBuilder.js.map +1 -0
  191. package/dist/projections/declarative/CompositeKeyBuilder.d.ts +16 -0
  192. package/dist/projections/declarative/CompositeKeyBuilder.d.ts.map +1 -0
  193. package/dist/projections/declarative/CompositeKeyBuilder.js +29 -0
  194. package/dist/projections/declarative/CompositeKeyBuilder.js.map +1 -0
  195. package/dist/projections/declarative/FromBuilder.d.ts +8 -6
  196. package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
  197. package/dist/projections/declarative/FromBuilder.js +44 -14
  198. package/dist/projections/declarative/FromBuilder.js.map +1 -1
  199. package/dist/projections/declarative/ICompositeKeyBuilder.d.ts +5 -0
  200. package/dist/projections/declarative/ICompositeKeyBuilder.d.ts.map +1 -1
  201. package/dist/projections/declarative/JoinBuilder.d.ts +7 -5
  202. package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
  203. package/dist/projections/declarative/JoinBuilder.js +50 -16
  204. package/dist/projections/declarative/JoinBuilder.js.map +1 -1
  205. package/dist/projections/declarative/NestedBuilder.d.ts +24 -0
  206. package/dist/projections/declarative/NestedBuilder.d.ts.map +1 -0
  207. package/dist/projections/declarative/NestedBuilder.js +60 -0
  208. package/dist/projections/declarative/NestedBuilder.js.map +1 -0
  209. package/dist/projections/declarative/ProjectionBuilderCore.d.ts +127 -0
  210. package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -0
  211. package/dist/projections/declarative/ProjectionBuilderCore.js +180 -0
  212. package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -0
  213. package/dist/projections/declarative/ProjectionBuilderFor.d.ts +6 -32
  214. package/dist/projections/declarative/ProjectionBuilderFor.d.ts.map +1 -1
  215. package/dist/projections/declarative/ProjectionBuilderFor.js +24 -118
  216. package/dist/projections/declarative/ProjectionBuilderFor.js.map +1 -1
  217. package/dist/projections/declarative/SubtractBuilder.d.ts +17 -0
  218. package/dist/projections/declarative/SubtractBuilder.d.ts.map +1 -0
  219. package/dist/projections/declarative/SubtractBuilder.js +28 -0
  220. package/dist/projections/declarative/SubtractBuilder.js.map +1 -0
  221. package/dist/projections/declarative/index.d.ts +9 -0
  222. package/dist/projections/declarative/index.d.ts.map +1 -1
  223. package/dist/projections/declarative/index.js +8 -0
  224. package/dist/projections/declarative/index.js.map +1 -1
  225. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +90 -0
  226. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -0
  227. package/dist/projections/modelBound/childrenAndNestedBuilder.js +290 -0
  228. package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -0
  229. package/dist/reactors/ReactorSideEffects.d.ts +29 -0
  230. package/dist/reactors/ReactorSideEffects.d.ts.map +1 -0
  231. package/dist/reactors/ReactorSideEffects.js +70 -0
  232. package/dist/reactors/ReactorSideEffects.js.map +1 -0
  233. package/dist/reactors/Reactors.d.ts +4 -1
  234. package/dist/reactors/Reactors.d.ts.map +1 -1
  235. package/dist/reactors/Reactors.js +23 -4
  236. package/dist/reactors/Reactors.js.map +1 -1
  237. package/dist/reducers/Reducers.d.ts.map +1 -1
  238. package/dist/reducers/Reducers.js +15 -3
  239. package/dist/reducers/Reducers.js.map +1 -1
  240. package/dist/reducers/reducer.d.ts +9 -1
  241. package/dist/reducers/reducer.d.ts.map +1 -1
  242. package/dist/reducers/reducer.js +4 -2
  243. package/dist/reducers/reducer.js.map +1 -1
  244. package/dist/transactions/IUnitOfWork.d.ts +18 -0
  245. package/dist/transactions/IUnitOfWork.d.ts.map +1 -1
  246. package/dist/transactions/UnitOfWork.d.ts +9 -0
  247. package/dist/transactions/UnitOfWork.d.ts.map +1 -1
  248. package/dist/transactions/UnitOfWork.js +14 -0
  249. package/dist/transactions/UnitOfWork.js.map +1 -1
  250. package/dist/tsconfig.tsbuildinfo +1 -1
  251. package/eventSequences/AppendOperationsBroadcaster.ts +83 -0
  252. package/eventSequences/AppendOptions.ts +8 -0
  253. package/eventSequences/AppendResult.ts +16 -0
  254. package/eventSequences/AppendedEventWithResult.ts +17 -0
  255. package/eventSequences/CompleteStreamError.ts +20 -0
  256. package/eventSequences/CompleteStreamResult.ts +13 -0
  257. package/eventSequences/ConcurrencyViolation.ts +18 -0
  258. package/eventSequences/EventForEventSourceId.ts +12 -0
  259. package/eventSequences/EventSequence.spec.ts +588 -0
  260. package/eventSequences/EventSequence.ts +406 -20
  261. package/eventSequences/IEventSequence.ts +109 -1
  262. package/eventSequences/WaitForCompletionResult.ts +15 -0
  263. package/eventSequences/index.ts +5 -0
  264. package/eventStoreSubscriptions/EventStoreSubscriptions.spec.ts +52 -0
  265. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +19 -1
  266. package/eventStoreSubscriptions/IEventStoreSubscriptions.ts +7 -0
  267. package/eventStoreSubscriptions/contracts.ts +126 -0
  268. package/events/constraints/Constraints.ts +1 -1
  269. package/externalServices/ExternalServiceBuilder.ts +128 -0
  270. package/externalServices/ExternalServices.ts +34 -0
  271. package/externalServices/ExternalServicesContracts.ts +709 -0
  272. package/externalServices/IExternalServiceBuilder.ts +76 -0
  273. package/externalServices/IExternalServices.ts +16 -0
  274. package/externalServices/index.ts +7 -0
  275. package/identities/IIdentityManager.ts +19 -0
  276. package/identities/IdentityManager.ts +32 -0
  277. package/identities/index.ts +5 -0
  278. package/index.ts +6 -0
  279. package/observation/FailedPartition.ts +21 -0
  280. package/observation/FailedPartitionAttempt.ts +21 -0
  281. package/observation/FailedPartitions.spec.ts +75 -0
  282. package/observation/FailedPartitions.ts +44 -0
  283. package/observation/ICanBeNotifiedWhenPartitionReplayed.ts +22 -0
  284. package/observation/ICanBeNotifiedWhenReplay.ts +20 -0
  285. package/observation/IFailedPartitions.ts +22 -0
  286. package/observation/index.ts +6 -0
  287. package/observation/notifyReplayLifecycle.spec.ts +98 -0
  288. package/observation/notifyReplayLifecycle.ts +61 -0
  289. package/observation/toClientFailedPartition.ts +26 -0
  290. package/package.json +12 -2
  291. package/projections/Projections.childrenAndNested.spec.ts +188 -0
  292. package/projections/Projections.spec.ts +131 -0
  293. package/projections/Projections.ts +37 -119
  294. package/projections/declarative/AddBuilder.ts +28 -0
  295. package/projections/declarative/AddChildBuilder.ts +97 -0
  296. package/projections/declarative/ChildrenBuilder.spec.ts +157 -0
  297. package/projections/declarative/ChildrenBuilder.ts +90 -0
  298. package/projections/declarative/CompositeKeyBuilder.spec.ts +117 -0
  299. package/projections/declarative/CompositeKeyBuilder.ts +38 -0
  300. package/projections/declarative/FromBuilder.ts +60 -15
  301. package/projections/declarative/ICompositeKeyBuilder.ts +6 -0
  302. package/projections/declarative/JoinBuilder.ts +66 -17
  303. package/projections/declarative/NestedBuilder.ts +76 -0
  304. package/projections/declarative/ProjectionBuilderCore.ts +257 -0
  305. package/projections/declarative/ProjectionBuilderFor.spec.ts +115 -0
  306. package/projections/declarative/ProjectionBuilderFor.ts +29 -168
  307. package/projections/declarative/SubtractBuilder.ts +28 -0
  308. package/projections/declarative/index.ts +9 -0
  309. package/projections/modelBound/childrenAndNestedBuilder.ts +354 -0
  310. package/reactors/ReactorSideEffects.spec.ts +182 -0
  311. package/reactors/ReactorSideEffects.ts +97 -0
  312. package/reactors/Reactors.ts +29 -4
  313. package/reducers/Reducers.spec.ts +87 -0
  314. package/reducers/Reducers.ts +15 -3
  315. package/reducers/reducer.spec.ts +40 -0
  316. package/reducers/reducer.ts +11 -2
  317. package/transactions/IUnitOfWork.ts +21 -0
  318. package/transactions/UnitOfWork.spec.ts +82 -0
  319. package/transactions/UnitOfWork.ts +20 -0
@@ -0,0 +1,188 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import 'reflect-metadata';
5
+ import { field } from '@cratis/fundamentals';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import { IClientArtifactsProvider } from '../artifacts';
8
+ import { ChronicleConnection } from '../connection';
9
+ import { eventType } from '../events/eventTypeDecorator';
10
+ import { readModel } from '../readModels/readModel';
11
+ import { childrenFrom } from './modelBound/childrenFrom';
12
+ import { clearWith } from './modelBound/clearWith';
13
+ import { fromEvent } from './modelBound/fromEvent';
14
+ import { nested } from './modelBound/nested';
15
+ import { setFrom } from './modelBound/setFrom';
16
+ import { Projections } from './Projections';
17
+
18
+ // Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
19
+ // fixtures don't depend on the test runner's decorator-syntax support.
20
+
21
+ class LineAdded {
22
+ productId!: string;
23
+ quantity!: number;
24
+ }
25
+ eventType()(LineAdded);
26
+
27
+ class LineQuantityChanged {
28
+ quantity!: number;
29
+ }
30
+ eventType()(LineQuantityChanged);
31
+
32
+ class SummaryUpdated {
33
+ total!: number;
34
+ }
35
+ eventType()(SummaryUpdated);
36
+
37
+ class SummaryCleared {}
38
+ eventType()(SummaryCleared);
39
+
40
+ class NoteAdded {
41
+ text!: string;
42
+ }
43
+ eventType()(NoteAdded);
44
+
45
+ class NoteCleared {}
46
+ eventType()(NoteCleared);
47
+
48
+ class TagAdded {
49
+ name!: string;
50
+ }
51
+ eventType()(TagAdded);
52
+
53
+ class OrderCreated {}
54
+ eventType()(OrderCreated);
55
+
56
+ class OrderLine {
57
+ productId!: string;
58
+ quantity!: number;
59
+ }
60
+ setFrom(LineQuantityChanged)(OrderLine.prototype, 'quantity');
61
+
62
+ class OrderSummary {
63
+ total!: number;
64
+ }
65
+ setFrom(SummaryUpdated)(OrderSummary.prototype, 'total');
66
+ clearWith(SummaryCleared)(OrderSummary);
67
+
68
+ class OrderNote {
69
+ text!: string;
70
+ }
71
+ setFrom(NoteAdded)(OrderNote.prototype, 'text');
72
+
73
+ class Order {
74
+ id!: string;
75
+ lines!: OrderLine[];
76
+ summary!: OrderSummary | undefined;
77
+ note!: OrderNote | undefined;
78
+ tags!: string[];
79
+ }
80
+ childrenFrom(LineAdded, undefined, 'productId', undefined)(Order.prototype, 'lines');
81
+ field(Array, { enumerable: true, genericArguments: [OrderLine] })(Order.prototype, 'lines');
82
+ nested(Order.prototype, 'summary');
83
+ field(OrderSummary)(Order.prototype, 'summary');
84
+ nested(Order.prototype, 'note');
85
+ field(OrderNote)(Order.prototype, 'note');
86
+ clearWith(NoteCleared)(Order.prototype, 'note');
87
+ childrenFrom(TagAdded)(Order.prototype, 'tags');
88
+ fromEvent(OrderCreated)(Order);
89
+ readModel()(Order);
90
+
91
+ interface FromRecord {
92
+ Key: { Id: string };
93
+ Value: { Properties: Record<string, string>; Key: string; ParentKey: string };
94
+ }
95
+
96
+ interface ChildrenDefinition {
97
+ IdentifiedBy: string;
98
+ From: FromRecord[];
99
+ RemovedWith: Array<{ Key: { Id: string } }>;
100
+ }
101
+
102
+ interface BuiltDefinition {
103
+ Children: Record<string, ChildrenDefinition>;
104
+ Nested: Record<string, ChildrenDefinition>;
105
+ }
106
+
107
+ function createProjections(readModels: (new (...args: unknown[]) => unknown)[]) {
108
+ const registerMock = vi.fn().mockResolvedValue(undefined);
109
+ const registerManyMock = vi.fn().mockResolvedValue(undefined);
110
+ const connection = {
111
+ readModels: { registerMany: registerManyMock },
112
+ projections: { register: registerMock }
113
+ } as unknown as ChronicleConnection;
114
+
115
+ const clientArtifacts: IClientArtifactsProvider = {
116
+ eventTypes: [],
117
+ readModels: readModels as unknown as IClientArtifactsProvider['readModels'],
118
+ reactors: [],
119
+ reducers: [],
120
+ seeders: [],
121
+ constraints: [],
122
+ projections: [],
123
+ webhooks: [],
124
+ eventTypeMigrations: []
125
+ };
126
+
127
+ const projections = new Projections('test-store', connection, clientArtifacts, 'test-sink');
128
+ return { projections, registerMock };
129
+ }
130
+
131
+ describe('Projections with childrenFrom, nested and clearWith', () => {
132
+ describe('when registering a model-bound projection using them', () => {
133
+ it('should not throw', async () => {
134
+ const { projections } = createProjections([Order]);
135
+ await expect(projections.register()).resolves.not.toThrow();
136
+ });
137
+
138
+ it('should build a children definition with the resolved child type wired up', async () => {
139
+ const { projections, registerMock } = createProjections([Order]);
140
+ await projections.register();
141
+
142
+ const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
143
+ const lines = definition.Children.lines;
144
+
145
+ expect(lines.IdentifiedBy).toBe('productId');
146
+ const creationEntry = lines.From.find(candidate => candidate.Key.Id === 'LineAdded')!;
147
+ expect(creationEntry.Value.Key).toBe('$eventSourceId');
148
+ expect(creationEntry.Value.ParentKey).toBe('$eventSourceId');
149
+
150
+ const updateEntry = lines.From.find(candidate => candidate.Key.Id === 'LineQuantityChanged')!;
151
+ expect(updateEntry.Value.Properties.quantity).toBe('quantity');
152
+ });
153
+
154
+ it('should build a children definition even when the child element type cannot be resolved', async () => {
155
+ const { projections, registerMock } = createProjections([Order]);
156
+ await projections.register();
157
+
158
+ const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
159
+ const tags = definition.Children.tags;
160
+
161
+ expect(tags.From.some(candidate => candidate.Key.Id === 'TagAdded')).toBe(true);
162
+ });
163
+
164
+ it('should build a nested definition honoring a class-level clearWith on the nested type', async () => {
165
+ const { projections, registerMock } = createProjections([Order]);
166
+ await projections.register();
167
+
168
+ const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
169
+ const summary = definition.Nested.summary;
170
+
171
+ const updateEntry = summary.From.find(candidate => candidate.Key.Id === 'SummaryUpdated')!;
172
+ expect(updateEntry.Value.Properties.total).toBe('total');
173
+ expect(summary.RemovedWith.some(candidate => candidate.Key.Id === 'SummaryCleared')).toBe(true);
174
+ });
175
+
176
+ it('should build a nested definition honoring a property-level clearWith', async () => {
177
+ const { projections, registerMock } = createProjections([Order]);
178
+ await projections.register();
179
+
180
+ const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
181
+ const note = definition.Nested.note;
182
+
183
+ const updateEntry = note.From.find(candidate => candidate.Key.Id === 'NoteAdded')!;
184
+ expect(updateEntry.Value.Properties.text).toBe('text');
185
+ expect(note.RemovedWith.some(candidate => candidate.Key.Id === 'NoteCleared')).toBe(true);
186
+ });
187
+ });
188
+ });
@@ -0,0 +1,131 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import 'reflect-metadata';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import { IClientArtifactsProvider } from '../artifacts';
7
+ import { ChronicleConnection } from '../connection';
8
+ import { eventType } from '../events/eventTypeDecorator';
9
+ import { readModel } from '../readModels/readModel';
10
+ import { addFrom } from './modelBound/addFrom';
11
+ import { count } from './modelBound/count';
12
+ import { decrement } from './modelBound/decrement';
13
+ import { fromEvent } from './modelBound/fromEvent';
14
+ import { increment } from './modelBound/increment';
15
+ import { subtractFrom } from './modelBound/subtractFrom';
16
+ import { Projections } from './Projections';
17
+
18
+ // Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
19
+ // fixtures don't depend on the test runner's decorator-syntax support - they exercise exactly
20
+ // the same decorator functions and metadata storage that `@decorator` syntax would invoke.
21
+
22
+ class ItemAdded {
23
+ quantity!: number;
24
+ }
25
+ eventType()(ItemAdded);
26
+
27
+ class ItemRemoved {
28
+ amount!: number;
29
+ }
30
+ eventType()(ItemRemoved);
31
+
32
+ class CounterIncremented {}
33
+ eventType()(CounterIncremented);
34
+
35
+ class CounterDecremented {}
36
+ eventType()(CounterDecremented);
37
+
38
+ class ThingHappened {}
39
+ eventType()(ThingHappened);
40
+
41
+ class KeyedThingHappened {}
42
+ eventType()(KeyedThingHappened);
43
+
44
+ class Inventory {
45
+ id!: string;
46
+ total!: number;
47
+ totalFromNamedProperty!: number;
48
+ removedTotal!: number;
49
+ incrementedCount!: number;
50
+ decrementedCount!: number;
51
+ thingsHappenedCount!: number;
52
+ constantKeyedCount!: number;
53
+ }
54
+ addFrom(ItemAdded)(Inventory.prototype, 'total');
55
+ addFrom(ItemAdded, 'quantity')(Inventory.prototype, 'totalFromNamedProperty');
56
+ subtractFrom(ItemRemoved, 'amount')(Inventory.prototype, 'removedTotal');
57
+ increment(CounterIncremented)(Inventory.prototype, 'incrementedCount');
58
+ decrement(CounterDecremented)(Inventory.prototype, 'decrementedCount');
59
+ count(ThingHappened)(Inventory.prototype, 'thingsHappenedCount');
60
+ count(KeyedThingHappened, 'all-things')(Inventory.prototype, 'constantKeyedCount');
61
+ fromEvent(ItemAdded)(Inventory);
62
+ readModel()(Inventory);
63
+
64
+ /**
65
+ * Builds a {@link Projections} instance wired to capture the registration payload sent
66
+ * to the kernel, without requiring a real gRPC connection.
67
+ */
68
+ function createProjections(readModels: (new (...args: unknown[]) => unknown)[]) {
69
+ const registerMock = vi.fn().mockResolvedValue(undefined);
70
+ const registerManyMock = vi.fn().mockResolvedValue(undefined);
71
+ const connection = {
72
+ readModels: { registerMany: registerManyMock },
73
+ projections: { register: registerMock }
74
+ } as unknown as ChronicleConnection;
75
+
76
+ const clientArtifacts: IClientArtifactsProvider = {
77
+ eventTypes: [],
78
+ readModels: readModels as unknown as IClientArtifactsProvider['readModels'],
79
+ reactors: [],
80
+ reducers: [],
81
+ seeders: [],
82
+ constraints: [],
83
+ projections: [],
84
+ webhooks: [],
85
+ eventTypeMigrations: []
86
+ };
87
+
88
+ const projections = new Projections('test-store', connection, clientArtifacts, 'test-sink');
89
+ return { projections, registerMock };
90
+ }
91
+
92
+ function findFromEntry(definition: { From: Array<{ Key: { Id: string }; Value: { Properties: Record<string, string>; Key: string } }> }, eventTypeId: string) {
93
+ const entry = definition.From.find(candidate => candidate.Key.Id === eventTypeId);
94
+ if (!entry) {
95
+ throw new Error(`No From entry found for event type '${eventTypeId}'.`);
96
+ }
97
+ return entry;
98
+ }
99
+
100
+ describe('Projections', () => {
101
+ describe('when registering a model-bound projection with arithmetic decorators', () => {
102
+ it('should not throw and should produce the correct wire expressions', async () => {
103
+ const { projections, registerMock } = createProjections([Inventory]);
104
+
105
+ await expect(projections.register()).resolves.not.toThrow();
106
+
107
+ const request = registerMock.mock.calls[0][0] as { Projections: unknown[] };
108
+ const definition = request.Projections[0] as { From: Array<{ Key: { Id: string }; Value: { Properties: Record<string, string>; Key: string } }> };
109
+
110
+ const itemAdded = findFromEntry(definition, 'ItemAdded');
111
+ expect(itemAdded.Value.Properties.total).toBe('$add(total)');
112
+ expect(itemAdded.Value.Properties.totalFromNamedProperty).toBe('$add(quantity)');
113
+
114
+ const itemRemoved = findFromEntry(definition, 'ItemRemoved');
115
+ expect(itemRemoved.Value.Properties.removedTotal).toBe('$subtract(amount)');
116
+
117
+ const counterIncremented = findFromEntry(definition, 'CounterIncremented');
118
+ expect(counterIncremented.Value.Properties.incrementedCount).toBe('$increment');
119
+
120
+ const counterDecremented = findFromEntry(definition, 'CounterDecremented');
121
+ expect(counterDecremented.Value.Properties.decrementedCount).toBe('$decrement');
122
+
123
+ const thingHappened = findFromEntry(definition, 'ThingHappened');
124
+ expect(thingHappened.Value.Properties.thingsHappenedCount).toBe('$count');
125
+
126
+ const keyedThingHappened = findFromEntry(definition, 'KeyedThingHappened');
127
+ expect(keyedThingHappened.Value.Properties.constantKeyedCount).toBe('$count');
128
+ expect(keyedThingHappened.Value.Key).toBe('$value(all-things)');
129
+ });
130
+ });
131
+ });
@@ -12,21 +12,25 @@ import { ChronicleConnection } from '../connection';
12
12
  import { toContractsGuid } from '../connection/Guid';
13
13
  import { WellKnownSinks } from '../sinks';
14
14
  import { EventSequenceId } from '../eventSequences/EventSequenceId';
15
- import { getEventTypeFor } from '../events/eventTypeDecorator';
16
15
  import { getReadModelMetadata } from '../readModels';
17
16
  import { TypeIntrospector } from '../types';
18
17
  import { IProjections } from './IProjections';
19
18
  import { getProjectionMetadata } from './declarative/projection';
20
19
  import { ProjectionBuilderFor } from './declarative/ProjectionBuilderFor';
21
20
  import type { IProjectionFor } from './declarative/IProjectionFor';
22
- import { getAddFromMetadata } from './modelBound/addFrom';
21
+ import {
22
+ applyPropertyMappings,
23
+ buildChildrenEntry,
24
+ buildNestedEntry,
25
+ ChildrenDefinitionLike,
26
+ ContractEventType,
27
+ FromRecord,
28
+ getEventTypeMapKey,
29
+ toContractEventType
30
+ } from './modelBound/childrenAndNestedBuilder';
23
31
  import { getChildrenFromMetadata } from './modelBound/childrenFrom';
24
- import { getClearWithClassMetadata, getClearWithPropertyMetadata } from './modelBound/clearWith';
25
- import { getCountMetadata } from './modelBound/count';
26
- import { getDecrementMetadata } from './modelBound/decrement';
27
32
  import { getFromEveryMetadata } from './modelBound/fromEvery';
28
33
  import { getFromEventMetadata, hasFromEventMetadata } from './modelBound/fromEvent';
29
- import { getIncrementMetadata } from './modelBound/increment';
30
34
  import { getJoinMetadata } from './modelBound/join';
31
35
  import { ProjectionId } from './ProjectionId';
32
36
  import { isNested } from './modelBound/nested';
@@ -34,12 +38,6 @@ import { isNotRewindable } from './modelBound/notRewindable';
34
38
  import { isPassive } from './modelBound/passive';
35
39
  import { getRemovedWithClassMetadata, getRemovedWithPropertyMetadata } from './modelBound/removedWith';
36
40
  import { getRemovedWithJoinClassMetadata, getRemovedWithJoinPropertyMetadata } from './modelBound/removedWithJoin';
37
- import { getSetFromMetadata } from './modelBound/setFrom';
38
- import { getSetFromContextMetadata } from './modelBound/setFromContext';
39
- import { getSetValueMetadata } from './modelBound/setValue';
40
- import { getSubtractFromMetadata } from './modelBound/subtractFrom';
41
-
42
- type ContractEventType = { Id: string; Generation: number; Tombstone: boolean };
43
41
 
44
42
  interface ResolvedModelBoundMetadata {
45
43
  id: ProjectionId;
@@ -277,15 +275,17 @@ export class Projections implements IProjections {
277
275
  const properties = TypeIntrospector.getTrackedProperties(type);
278
276
  const prototype = type.prototype;
279
277
 
280
- const fromByEventType = new Map<string, { Key: ContractEventType; Value: { Properties: Record<string, string>; Key: string; ParentKey: string } }>();
278
+ const fromByEventType = new Map<string, FromRecord>();
281
279
  const joinByEventType = new Map<string, { Key: ContractEventType; Value: { On: string; Properties: Record<string, string>; Key: string } }>();
282
280
  const removedWithByEventType = new Map<string, { Key: ContractEventType; Value: { Key: string; ParentKey: string } }>();
283
281
  const removedWithJoinByEventType = new Map<string, { Key: ContractEventType; Value: { Key: string } }>();
282
+ const childrenByProperty: Record<string, ChildrenDefinitionLike> = {};
283
+ const nestedByProperty: Record<string, ChildrenDefinitionLike> = {};
284
284
 
285
285
  const fromEvents = getFromEventMetadata(type);
286
286
  for (const fromEvent of fromEvents) {
287
- const eventType = this.toContractEventType(fromEvent.eventType);
288
- const eventKey = this.getEventTypeMapKey(eventType);
287
+ const eventType = toContractEventType(fromEvent.eventType);
288
+ const eventKey = getEventTypeMapKey(eventType);
289
289
  fromByEventType.set(eventKey, {
290
290
  Key: eventType,
291
291
  Value: {
@@ -296,15 +296,10 @@ export class Projections implements IProjections {
296
296
  });
297
297
  }
298
298
 
299
- const clearWithClass = getClearWithClassMetadata(type);
300
- if (clearWithClass.length > 0) {
301
- throw new Error(`Model-bound projection '${type.name}' uses @clearWith on class level, which is not implemented yet.`);
302
- }
303
-
304
299
  const removedWithClass = getRemovedWithClassMetadata(type);
305
300
  for (const removed of removedWithClass) {
306
- const eventType = this.toContractEventType(removed.eventType);
307
- removedWithByEventType.set(this.getEventTypeMapKey(eventType), {
301
+ const eventType = toContractEventType(removed.eventType);
302
+ removedWithByEventType.set(getEventTypeMapKey(eventType), {
308
303
  Key: eventType,
309
304
  Value: { Key: removed.key ?? '$eventSourceId', ParentKey: removed.parentKey ?? '' }
310
305
  });
@@ -312,30 +307,15 @@ export class Projections implements IProjections {
312
307
 
313
308
  const removedWithJoinClass = getRemovedWithJoinClassMetadata(type);
314
309
  for (const removed of removedWithJoinClass) {
315
- const eventType = this.toContractEventType(removed.eventType);
316
- removedWithJoinByEventType.set(this.getEventTypeMapKey(eventType), {
310
+ const eventType = toContractEventType(removed.eventType);
311
+ removedWithJoinByEventType.set(getEventTypeMapKey(eventType), {
317
312
  Key: eventType,
318
313
  Value: { Key: removed.key ?? '$eventSourceId' }
319
314
  });
320
315
  }
321
316
 
322
317
  for (const property of properties) {
323
- this.throwIfUnsupportedModelBoundDecorators(type.name, prototype, property);
324
-
325
- for (const mapping of getSetFromMetadata(prototype, property)) {
326
- const entry = this.ensureFromEntry(fromByEventType, mapping.eventType);
327
- entry.Value.Properties[property] = mapping.eventPropertyName ?? property;
328
- }
329
-
330
- for (const mapping of getSetFromContextMetadata(prototype, property)) {
331
- const entry = this.ensureFromEntry(fromByEventType, mapping.eventType);
332
- entry.Value.Properties[property] = mapping.contextPropertyName ?? property;
333
- }
334
-
335
- for (const mapping of getSetValueMetadata(prototype, property)) {
336
- const entry = this.ensureFromEntry(fromByEventType, mapping.eventType);
337
- entry.Value.Properties[property] = JSON.stringify(mapping.value);
338
- }
318
+ applyPropertyMappings(prototype, property, fromByEventType);
339
319
 
340
320
  for (const mapping of getJoinMetadata(prototype, property)) {
341
321
  const entry = this.ensureJoinEntry(joinByEventType, mapping.eventType);
@@ -343,26 +323,30 @@ export class Projections implements IProjections {
343
323
  entry.Value.Properties[property] = mapping.eventPropertyName ?? property;
344
324
  }
345
325
 
346
- const fromEvery = getFromEveryMetadata(prototype, property);
347
- if (fromEvery) {
348
- // Applied later to projection.All
349
- }
350
-
351
326
  for (const removed of getRemovedWithPropertyMetadata(prototype, property)) {
352
- const eventType = this.toContractEventType(removed.eventType);
353
- removedWithByEventType.set(this.getEventTypeMapKey(eventType), {
327
+ const eventType = toContractEventType(removed.eventType);
328
+ removedWithByEventType.set(getEventTypeMapKey(eventType), {
354
329
  Key: eventType,
355
330
  Value: { Key: removed.key ?? '$eventSourceId', ParentKey: removed.parentKey ?? '' }
356
331
  });
357
332
  }
358
333
 
359
334
  for (const removed of getRemovedWithJoinPropertyMetadata(prototype, property)) {
360
- const eventType = this.toContractEventType(removed.eventType);
361
- removedWithJoinByEventType.set(this.getEventTypeMapKey(eventType), {
335
+ const eventType = toContractEventType(removed.eventType);
336
+ removedWithJoinByEventType.set(getEventTypeMapKey(eventType), {
362
337
  Key: eventType,
363
338
  Value: { Key: removed.key ?? '$eventSourceId' }
364
339
  });
365
340
  }
341
+
342
+ const childrenFromList = getChildrenFromMetadata(prototype, property);
343
+ if (childrenFromList.length > 0) {
344
+ childrenByProperty[property] = buildChildrenEntry(type, property, childrenFromList);
345
+ }
346
+
347
+ if (isNested(prototype, property)) {
348
+ nestedByProperty[property] = buildNestedEntry(type, property);
349
+ }
366
350
  }
367
351
 
368
352
  const allProperties: Record<string, string> = {};
@@ -384,7 +368,7 @@ export class Projections implements IProjections {
384
368
  InitialModelState: '{}',
385
369
  From: Array.from(fromByEventType.values()),
386
370
  Join: Array.from(joinByEventType.values()),
387
- Children: {},
371
+ Children: childrenByProperty,
388
372
  FromEvery: [],
389
373
  All: {
390
374
  Properties: allProperties,
@@ -396,7 +380,7 @@ export class Projections implements IProjections {
396
380
  LastUpdated: { Value: '' },
397
381
  Tags: [],
398
382
  AutoMap: AutoMap.Enabled,
399
- Nested: {}
383
+ Nested: nestedByProperty
400
384
  };
401
385
  definition.LastUpdated = { Value: this.computeStableLastUpdated(definition) };
402
386
  return definition;
@@ -416,62 +400,12 @@ export class Projections implements IProjections {
416
400
  return undefined;
417
401
  }
418
402
 
419
- private throwIfUnsupportedModelBoundDecorators(typeName: string, prototype: object, property: string): void {
420
- if (getAddFromMetadata(prototype, property).length > 0) {
421
- throw new Error(`Model-bound projection '${typeName}' uses @addFrom on '${property}', which is not implemented yet.`);
422
- }
423
- if (getSubtractFromMetadata(prototype, property).length > 0) {
424
- throw new Error(`Model-bound projection '${typeName}' uses @subtractFrom on '${property}', which is not implemented yet.`);
425
- }
426
- if (getIncrementMetadata(prototype, property).length > 0) {
427
- throw new Error(`Model-bound projection '${typeName}' uses @increment on '${property}', which is not implemented yet.`);
428
- }
429
- if (getDecrementMetadata(prototype, property).length > 0) {
430
- throw new Error(`Model-bound projection '${typeName}' uses @decrement on '${property}', which is not implemented yet.`);
431
- }
432
- if (getCountMetadata(prototype, property).length > 0) {
433
- throw new Error(`Model-bound projection '${typeName}' uses @count on '${property}', which is not implemented yet.`);
434
- }
435
- if (getChildrenFromMetadata(prototype, property).length > 0) {
436
- throw new Error(`Model-bound projection '${typeName}' uses @childrenFrom on '${property}', which is not implemented yet.`);
437
- }
438
- if (isNested(prototype, property)) {
439
- throw new Error(`Model-bound projection '${typeName}' uses @nested on '${property}', which is not implemented yet.`);
440
- }
441
- if (getClearWithPropertyMetadata(prototype, property).length > 0) {
442
- throw new Error(`Model-bound projection '${typeName}' uses @clearWith on '${property}', which is not implemented yet.`);
443
- }
444
- }
445
-
446
- private ensureFromEntry(
447
- fromByEventType: Map<string, { Key: ContractEventType; Value: { Properties: Record<string, string>; Key: string; ParentKey: string } }>,
448
- eventTypeConstructor: Function
449
- ): { Key: ContractEventType; Value: { Properties: Record<string, string>; Key: string; ParentKey: string } } {
450
- const eventType = this.toContractEventType(eventTypeConstructor);
451
- const key = this.getEventTypeMapKey(eventType);
452
- const existing = fromByEventType.get(key);
453
- if (existing) {
454
- return existing;
455
- }
456
-
457
- const created = {
458
- Key: eventType,
459
- Value: {
460
- Properties: {},
461
- Key: '$eventSourceId',
462
- ParentKey: ''
463
- }
464
- };
465
- fromByEventType.set(key, created);
466
- return created;
467
- }
468
-
469
403
  private ensureJoinEntry(
470
404
  joinByEventType: Map<string, { Key: ContractEventType; Value: { On: string; Properties: Record<string, string>; Key: string } }>,
471
405
  eventTypeConstructor: Function
472
406
  ): { Key: ContractEventType; Value: { On: string; Properties: Record<string, string>; Key: string } } {
473
- const eventType = this.toContractEventType(eventTypeConstructor);
474
- const key = this.getEventTypeMapKey(eventType);
407
+ const eventType = toContractEventType(eventTypeConstructor);
408
+ const key = getEventTypeMapKey(eventType);
475
409
  const existing = joinByEventType.get(key);
476
410
  if (existing) {
477
411
  return existing;
@@ -489,22 +423,6 @@ export class Projections implements IProjections {
489
423
  return created;
490
424
  }
491
425
 
492
- private toContractEventType(eventTypeConstructor: Function): ContractEventType {
493
- const eventType = getEventTypeFor(eventTypeConstructor);
494
- if (eventType.id.value === '') {
495
- throw new Error(`Event type '${eventTypeConstructor.name}' is not decorated with @eventType().`);
496
- }
497
- return {
498
- Id: eventType.id.value,
499
- Generation: eventType.generation.value,
500
- Tombstone: false
501
- };
502
- }
503
-
504
- private getEventTypeMapKey(eventType: ContractEventType): string {
505
- return `${eventType.Id}:${eventType.Generation}:${eventType.Tombstone ? '1' : '0'}`;
506
- }
507
-
508
426
  /**
509
427
  * Computes a stable, deterministic ISO timestamp from the projection definition content,
510
428
  * excluding the LastUpdated field itself. This ensures the server does not interpret
@@ -0,0 +1,28 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
+ import { IAddBuilder } from './IAddBuilder';
6
+
7
+ /**
8
+ * Concrete implementation of {@link IAddBuilder} that records an add expression
9
+ * into the owning from/join builder.
10
+ * @template TEvent - The event type.
11
+ * @template TParentBuilder - The parent builder type.
12
+ */
13
+ export class AddBuilder<TEvent, TParentBuilder> implements IAddBuilder<TEvent, TParentBuilder> {
14
+ constructor(
15
+ private readonly _readModelProperty: string,
16
+ private readonly _setProperty: (readModelProp: string, expression: string) => void,
17
+ private readonly _parent: TParentBuilder
18
+ ) {}
19
+
20
+ /** @inheritdoc */
21
+ with(eventPropertyAccessor: PropertyAccessor<TEvent>): TParentBuilder {
22
+ const handler = new PropertyPathResolverProxyHandler();
23
+ const proxy = new Proxy({}, handler);
24
+ eventPropertyAccessor(proxy as TEvent);
25
+ this._setProperty(this._readModelProperty, `$add(${handler.property})`);
26
+ return this._parent;
27
+ }
28
+ }