@cratis/chronicle 6.9.1 → 6.11.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 (288) hide show
  1. package/ChronicleClient.ts +52 -13
  2. package/ChronicleOptions.spec.ts +54 -0
  3. package/ChronicleOptions.ts +20 -5
  4. package/EventStore.ts +7 -1
  5. package/README.md +2 -3
  6. package/artifacts/DefaultClientArtifactsProvider.ts +4 -1
  7. package/connection/ChronicleConnection.auth.spec.ts +270 -0
  8. package/connection/ChronicleConnection.ts +90 -31
  9. package/connection/ChronicleServices.ts +1 -1
  10. package/connection/CompatibilityPreflight.ts +1 -1
  11. package/connection/RejectedChronicleCredentials.ts +10 -0
  12. package/connection/TokenProvider.spec.ts +5 -6
  13. package/connection/TokenProvider.ts +26 -17
  14. package/connection/clientVersion.spec.ts +27 -0
  15. package/connection/clientVersion.ts +9 -0
  16. package/connection/describeRequestError.spec.ts +22 -0
  17. package/connection/fetchOAuthAccessToken.ts +33 -2
  18. package/dist/ChronicleClient.d.ts +3 -0
  19. package/dist/ChronicleClient.d.ts.map +1 -1
  20. package/dist/ChronicleClient.js +55 -13
  21. package/dist/ChronicleClient.js.map +1 -1
  22. package/dist/ChronicleOptions.d.ts +5 -1
  23. package/dist/ChronicleOptions.d.ts.map +1 -1
  24. package/dist/ChronicleOptions.js +20 -5
  25. package/dist/ChronicleOptions.js.map +1 -1
  26. package/dist/EventStore.d.ts +2 -0
  27. package/dist/EventStore.d.ts.map +1 -1
  28. package/dist/EventStore.js +6 -1
  29. package/dist/EventStore.js.map +1 -1
  30. package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
  31. package/dist/artifacts/DefaultClientArtifactsProvider.js +4 -1
  32. package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
  33. package/dist/connection/ChronicleConnection.d.ts +3 -3
  34. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  35. package/dist/connection/ChronicleConnection.js +98 -26
  36. package/dist/connection/ChronicleConnection.js.map +1 -1
  37. package/dist/connection/ChronicleServices.d.ts +1 -2
  38. package/dist/connection/ChronicleServices.d.ts.map +1 -1
  39. package/dist/connection/CompatibilityPreflight.js +1 -1
  40. package/dist/connection/CompatibilityPreflight.js.map +1 -1
  41. package/dist/connection/RejectedChronicleCredentials.d.ts +5 -0
  42. package/dist/connection/RejectedChronicleCredentials.d.ts.map +1 -0
  43. package/dist/connection/RejectedChronicleCredentials.js +10 -0
  44. package/dist/connection/RejectedChronicleCredentials.js.map +1 -0
  45. package/dist/connection/TokenProvider.d.ts +8 -6
  46. package/dist/connection/TokenProvider.d.ts.map +1 -1
  47. package/dist/connection/TokenProvider.js +25 -11
  48. package/dist/connection/TokenProvider.js.map +1 -1
  49. package/dist/connection/clientVersion.d.ts +3 -0
  50. package/dist/connection/clientVersion.d.ts.map +1 -0
  51. package/dist/connection/clientVersion.js +7 -0
  52. package/dist/connection/clientVersion.js.map +1 -0
  53. package/dist/connection/fetchOAuthAccessToken.d.ts +12 -0
  54. package/dist/connection/fetchOAuthAccessToken.d.ts.map +1 -1
  55. package/dist/connection/fetchOAuthAccessToken.js +37 -2
  56. package/dist/connection/fetchOAuthAccessToken.js.map +1 -1
  57. package/dist/eventSequences/AppendResult.d.ts +7 -0
  58. package/dist/eventSequences/AppendResult.d.ts.map +1 -1
  59. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  60. package/dist/eventSequences/EventSequence.js +26 -17
  61. package/dist/eventSequences/EventSequence.js.map +1 -1
  62. package/dist/eventSequences/WaitForCompletionOptions.d.ts +8 -0
  63. package/dist/eventSequences/WaitForCompletionOptions.d.ts.map +1 -0
  64. package/dist/eventSequences/WaitForCompletionOptions.js +4 -0
  65. package/dist/eventSequences/WaitForCompletionOptions.js.map +1 -0
  66. package/dist/eventSequences/index.d.ts +1 -0
  67. package/dist/eventSequences/index.d.ts.map +1 -1
  68. package/dist/eventSequences/index.js.map +1 -1
  69. package/dist/externalServices/ExternalServiceBuilder.d.ts +1 -1
  70. package/dist/externalServices/ExternalServiceBuilder.d.ts.map +1 -1
  71. package/dist/externalServices/ExternalServiceBuilder.js +1 -1
  72. package/dist/externalServices/ExternalServiceBuilder.js.map +1 -1
  73. package/dist/externalServices/ExternalServices.d.ts.map +1 -1
  74. package/dist/externalServices/ExternalServices.js +3 -1
  75. package/dist/externalServices/ExternalServices.js.map +1 -1
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/projections/Projections.d.ts.map +1 -1
  81. package/dist/projections/Projections.js +9 -7
  82. package/dist/projections/Projections.js.map +1 -1
  83. package/dist/projections/constantValueExpression.d.ts +3 -0
  84. package/dist/projections/constantValueExpression.d.ts.map +1 -0
  85. package/dist/projections/constantValueExpression.js +26 -0
  86. package/dist/projections/constantValueExpression.js.map +1 -0
  87. package/dist/projections/declarative/ChildrenBuilder.d.ts +2 -0
  88. package/dist/projections/declarative/ChildrenBuilder.d.ts.map +1 -1
  89. package/dist/projections/declarative/ChildrenBuilder.js +5 -0
  90. package/dist/projections/declarative/ChildrenBuilder.js.map +1 -1
  91. package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
  92. package/dist/projections/declarative/FromBuilder.js +3 -2
  93. package/dist/projections/declarative/FromBuilder.js.map +1 -1
  94. package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
  95. package/dist/projections/declarative/JoinBuilder.js +2 -1
  96. package/dist/projections/declarative/JoinBuilder.js.map +1 -1
  97. package/dist/projections/declarative/NestedBuilder.d.ts.map +1 -1
  98. package/dist/projections/declarative/NestedBuilder.js +3 -1
  99. package/dist/projections/declarative/NestedBuilder.js.map +1 -1
  100. package/dist/projections/declarative/ProjectionBuilderCore.d.ts +5 -0
  101. package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
  102. package/dist/projections/declarative/ProjectionBuilderCore.js +18 -2
  103. package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
  104. package/dist/projections/declarative/ProjectionBuilderFor.d.ts +6 -0
  105. package/dist/projections/declarative/ProjectionBuilderFor.d.ts.map +1 -1
  106. package/dist/projections/declarative/ProjectionBuilderFor.js +5 -0
  107. package/dist/projections/declarative/ProjectionBuilderFor.js.map +1 -1
  108. package/dist/projections/declarative/SetBuilder.d.ts.map +1 -1
  109. package/dist/projections/declarative/SetBuilder.js +2 -1
  110. package/dist/projections/declarative/SetBuilder.js.map +1 -1
  111. package/dist/projections/modelBound/addFrom.d.ts.map +1 -1
  112. package/dist/projections/modelBound/addFrom.js +3 -2
  113. package/dist/projections/modelBound/addFrom.js.map +1 -1
  114. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +20 -0
  115. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
  116. package/dist/projections/modelBound/childrenAndNestedBuilder.js +14 -11
  117. package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
  118. package/dist/projections/modelBound/childrenFrom.d.ts +4 -2
  119. package/dist/projections/modelBound/childrenFrom.d.ts.map +1 -1
  120. package/dist/projections/modelBound/childrenFrom.js +8 -4
  121. package/dist/projections/modelBound/childrenFrom.js.map +1 -1
  122. package/dist/projections/modelBound/clearWith.d.ts.map +1 -1
  123. package/dist/projections/modelBound/clearWith.js +3 -2
  124. package/dist/projections/modelBound/clearWith.js.map +1 -1
  125. package/dist/projections/modelBound/count.d.ts.map +1 -1
  126. package/dist/projections/modelBound/count.js +3 -2
  127. package/dist/projections/modelBound/count.js.map +1 -1
  128. package/dist/projections/modelBound/decrement.d.ts.map +1 -1
  129. package/dist/projections/modelBound/decrement.js +3 -2
  130. package/dist/projections/modelBound/decrement.js.map +1 -1
  131. package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
  132. package/dist/projections/modelBound/fromAll.js +3 -2
  133. package/dist/projections/modelBound/fromAll.js.map +1 -1
  134. package/dist/projections/modelBound/fromEvery.d.ts.map +1 -1
  135. package/dist/projections/modelBound/fromEvery.js +3 -2
  136. package/dist/projections/modelBound/fromEvery.js.map +1 -1
  137. package/dist/projections/modelBound/increment.d.ts.map +1 -1
  138. package/dist/projections/modelBound/increment.js +3 -2
  139. package/dist/projections/modelBound/increment.js.map +1 -1
  140. package/dist/projections/modelBound/join.d.ts.map +1 -1
  141. package/dist/projections/modelBound/join.js +3 -2
  142. package/dist/projections/modelBound/join.js.map +1 -1
  143. package/dist/projections/modelBound/nested.d.ts.map +1 -1
  144. package/dist/projections/modelBound/nested.js +3 -2
  145. package/dist/projections/modelBound/nested.js.map +1 -1
  146. package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -1
  147. package/dist/projections/modelBound/noAutoMap.js +3 -2
  148. package/dist/projections/modelBound/noAutoMap.js.map +1 -1
  149. package/dist/projections/modelBound/removedWith.d.ts.map +1 -1
  150. package/dist/projections/modelBound/removedWith.js +3 -2
  151. package/dist/projections/modelBound/removedWith.js.map +1 -1
  152. package/dist/projections/modelBound/removedWithJoin.d.ts.map +1 -1
  153. package/dist/projections/modelBound/removedWithJoin.js +3 -2
  154. package/dist/projections/modelBound/removedWithJoin.js.map +1 -1
  155. package/dist/projections/modelBound/setFrom.d.ts.map +1 -1
  156. package/dist/projections/modelBound/setFrom.js +3 -2
  157. package/dist/projections/modelBound/setFrom.js.map +1 -1
  158. package/dist/projections/modelBound/setFromContext.d.ts.map +1 -1
  159. package/dist/projections/modelBound/setFromContext.js +3 -2
  160. package/dist/projections/modelBound/setFromContext.js.map +1 -1
  161. package/dist/projections/modelBound/setValue.d.ts.map +1 -1
  162. package/dist/projections/modelBound/setValue.js +3 -2
  163. package/dist/projections/modelBound/setValue.js.map +1 -1
  164. package/dist/projections/modelBound/subtractFrom.d.ts.map +1 -1
  165. package/dist/projections/modelBound/subtractFrom.js +3 -2
  166. package/dist/projections/modelBound/subtractFrom.js.map +1 -1
  167. package/dist/projections/notSetPropertyPath.d.ts +3 -0
  168. package/dist/projections/notSetPropertyPath.d.ts.map +1 -0
  169. package/dist/projections/notSetPropertyPath.js +5 -0
  170. package/dist/projections/notSetPropertyPath.js.map +1 -0
  171. package/dist/reactors/Reactors.d.ts +4 -0
  172. package/dist/reactors/Reactors.d.ts.map +1 -1
  173. package/dist/reactors/Reactors.js +24 -6
  174. package/dist/reactors/Reactors.js.map +1 -1
  175. package/dist/reactors/reactor.d.ts +2 -2
  176. package/dist/reactors/reactor.js +2 -2
  177. package/dist/readModels/IReadModels.d.ts +3 -2
  178. package/dist/readModels/IReadModels.d.ts.map +1 -1
  179. package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
  180. package/dist/readModels/MaterializedReadModels.js +2 -4
  181. package/dist/readModels/MaterializedReadModels.js.map +1 -1
  182. package/dist/readModels/ReadModels.d.ts +3 -5
  183. package/dist/readModels/ReadModels.d.ts.map +1 -1
  184. package/dist/readModels/ReadModels.js +17 -14
  185. package/dist/readModels/ReadModels.js.map +1 -1
  186. package/dist/readModels/deserializeReadModel.d.ts +5 -0
  187. package/dist/readModels/deserializeReadModel.d.ts.map +1 -0
  188. package/dist/readModels/deserializeReadModel.js +84 -0
  189. package/dist/readModels/deserializeReadModel.js.map +1 -0
  190. package/dist/readModels/rootReadModelTypes.d.ts +5 -0
  191. package/dist/readModels/rootReadModelTypes.d.ts.map +1 -0
  192. package/dist/readModels/rootReadModelTypes.js +69 -0
  193. package/dist/readModels/rootReadModelTypes.js.map +1 -0
  194. package/dist/reducers/Reducers.d.ts +4 -0
  195. package/dist/reducers/Reducers.d.ts.map +1 -1
  196. package/dist/reducers/Reducers.js +24 -6
  197. package/dist/reducers/Reducers.js.map +1 -1
  198. package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
  199. package/dist/schemas/JsonSchemaGenerator.js +4 -4
  200. package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/dist/types/TypeDiscoverer.d.ts +8 -1
  203. package/dist/types/TypeDiscoverer.d.ts.map +1 -1
  204. package/dist/types/TypeDiscoverer.js +25 -5
  205. package/dist/types/TypeDiscoverer.js.map +1 -1
  206. package/dist/types/TypeIntrospector.d.ts.map +1 -1
  207. package/dist/types/TypeIntrospector.js +3 -2
  208. package/dist/types/TypeIntrospector.js.map +1 -1
  209. package/dist/types/conceptValueType.d.ts +4 -0
  210. package/dist/types/conceptValueType.d.ts.map +1 -0
  211. package/dist/types/conceptValueType.js +10 -0
  212. package/dist/types/conceptValueType.js.map +1 -0
  213. package/dist/types/modelBoundProperty.d.ts +6 -0
  214. package/dist/types/modelBoundProperty.d.ts.map +1 -0
  215. package/dist/types/modelBoundProperty.js +53 -0
  216. package/dist/types/modelBoundProperty.js.map +1 -0
  217. package/eventSequences/AppendResult.ts +8 -0
  218. package/eventSequences/EventSequence.spec.ts +76 -1
  219. package/eventSequences/EventSequence.ts +28 -20
  220. package/eventSequences/WaitForCompletionOptions.ts +11 -0
  221. package/eventSequences/index.ts +1 -0
  222. package/externalServices/ExternalServiceBuilder.ts +4 -4
  223. package/externalServices/ExternalServices.spec.ts +87 -0
  224. package/externalServices/ExternalServices.ts +3 -1
  225. package/for_ChronicleClient/connectionAuthentication.spec.ts +67 -0
  226. package/index.ts +1 -0
  227. package/observation/observationDisposal.spec.ts +74 -0
  228. package/package.json +2 -1
  229. package/projections/Projections.childrenAndNested.spec.ts +2 -0
  230. package/projections/Projections.modelBoundCompleteness.spec.ts +47 -0
  231. package/projections/Projections.ts +9 -7
  232. package/projections/constantValueExpression.spec.ts +31 -0
  233. package/projections/constantValueExpression.ts +28 -0
  234. package/projections/declarative/ChildrenBuilder.spec.ts +46 -1
  235. package/projections/declarative/ChildrenBuilder.ts +6 -0
  236. package/projections/declarative/FromBuilder.ts +3 -2
  237. package/projections/declarative/JoinBuilder.ts +2 -1
  238. package/projections/declarative/NestedBuilder.ts +3 -1
  239. package/projections/declarative/ProjectionBuilderCore.ts +20 -2
  240. package/projections/declarative/ProjectionBuilderFor.spec.ts +61 -4
  241. package/projections/declarative/ProjectionBuilderFor.ts +7 -0
  242. package/projections/declarative/SetBuilder.ts +2 -1
  243. package/projections/modelBound/addFrom.ts +3 -2
  244. package/projections/modelBound/childrenAndNestedBuilder.ts +14 -11
  245. package/projections/modelBound/childrenFrom.ts +11 -5
  246. package/projections/modelBound/clearWith.ts +3 -2
  247. package/projections/modelBound/count.ts +3 -2
  248. package/projections/modelBound/decorators.standard.spec.ts +45 -0
  249. package/projections/modelBound/decrement.ts +3 -2
  250. package/projections/modelBound/fromAll.ts +3 -2
  251. package/projections/modelBound/fromEvery.ts +3 -2
  252. package/projections/modelBound/increment.ts +3 -2
  253. package/projections/modelBound/join.ts +3 -2
  254. package/projections/modelBound/nested.ts +3 -2
  255. package/projections/modelBound/noAutoMap.ts +3 -2
  256. package/projections/modelBound/removedWith.ts +3 -2
  257. package/projections/modelBound/removedWithJoin.ts +3 -2
  258. package/projections/modelBound/setFrom.ts +3 -2
  259. package/projections/modelBound/setFromContext.ts +3 -2
  260. package/projections/modelBound/setValue.ts +3 -2
  261. package/projections/modelBound/subtractFrom.ts +3 -2
  262. package/projections/notSetPropertyPath.ts +5 -0
  263. package/reactors/Reactors.ts +22 -7
  264. package/reactors/reactor.ts +2 -2
  265. package/readModels/IReadModels.ts +3 -2
  266. package/readModels/MaterializedReadModels.spec.ts +22 -0
  267. package/readModels/MaterializedReadModels.ts +2 -4
  268. package/readModels/ReadModels.spec.ts +71 -6
  269. package/readModels/ReadModels.ts +18 -15
  270. package/readModels/conceptValueType.spec.ts +20 -0
  271. package/readModels/deserializeReadModel.spec.ts +84 -0
  272. package/readModels/deserializeReadModel.ts +82 -0
  273. package/readModels/discovery.spec.ts +27 -0
  274. package/readModels/rootReadModelTypes.spec.ts +115 -0
  275. package/readModels/rootReadModelTypes.ts +65 -0
  276. package/reducers/Reducers.ts +22 -7
  277. package/schemas/JsonSchemaGenerator.ts +4 -4
  278. package/schemas/legacyDesignMetadata.spec.ts +24 -2
  279. package/types/TypeDiscoverer.spec.ts +57 -0
  280. package/types/TypeDiscoverer.ts +27 -6
  281. package/types/TypeIntrospector.ts +3 -2
  282. package/types/conceptValueType.ts +11 -0
  283. package/types/modelBoundProperty.ts +53 -0
  284. package/dist/externalServices/ExternalServicesContracts.d.ts +0 -137
  285. package/dist/externalServices/ExternalServicesContracts.d.ts.map +0 -1
  286. package/dist/externalServices/ExternalServicesContracts.js +0 -654
  287. package/dist/externalServices/ExternalServicesContracts.js.map +0 -1
  288. package/externalServices/ExternalServicesContracts.ts +0 -709
@@ -3,12 +3,15 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the childrenFrom property decorator. */
9
10
  export interface ChildrenFromMetadata {
10
11
  /** The event constructor that adds children to the collection. */
11
12
  readonly eventType: Function;
13
+ /** Optional child type for TypeScript runtimes without emitted generic metadata. */
14
+ readonly childType?: Function;
12
15
  /** The event property name used as the key for children. Defaults to the event source identifier. */
13
16
  readonly key?: string;
14
17
  /** The child model property name used to uniquely identify instances in the collection. */
@@ -22,22 +25,25 @@ const METADATA_KEY = 'chronicle:projection:childrenFrom';
22
25
  /**
23
26
  * Property decorator that configures a children collection sub-projection from an event type.
24
27
  * @param eventType - The event constructor.
25
- * @param key - Optional event property name to use as the key for children.
28
+ * @param key - Optional event property name to use as the key for children, or the child constructor.
26
29
  * @param identifiedBy - Optional child model property name used to identify instances.
27
30
  * @param parentKey - Optional event property name used as the parent key.
28
31
  * @returns A property decorator.
29
32
  */
30
33
  export function childrenFrom(
31
34
  eventType: Function,
32
- key?: string,
35
+ key?: string | Function,
33
36
  identifiedBy?: string,
34
37
  parentKey?: string
35
38
  ): ChroniclePropertyDecorator {
36
- return decorateProperty((target: object, propertyKey: string | symbol) => {
39
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
37
40
  const propKey = propertyKey.toString();
38
41
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
39
42
  const existing: ChildrenFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, propKey) ?? [];
40
- const metadata: ChildrenFromMetadata = { eventType, key, identifiedBy, parentKey };
43
+ const metadata: ChildrenFromMetadata = {
44
+ eventType, key: typeof key === 'string' ? key : undefined,
45
+ childType: typeof key === 'function' ? key : undefined, identifiedBy, parentKey
46
+ };
41
47
  Reflect.defineMetadata(METADATA_KEY, [...existing, metadata], target, propKey);
42
48
  });
43
49
  }
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChronicleClassOrPropertyDecorator, decorateClassOrProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChronicleClassOrPropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the clearWith class or property decorator. */
9
10
  export interface ClearWithMetadata {
@@ -22,7 +23,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:clearWith:property';
22
23
  * @returns A class and property decorator.
23
24
  */
24
25
  export function clearWith(eventType: Function): ChronicleClassOrPropertyDecorator {
25
- return decorateClassOrProperty((target: object, propertyKey?: string | symbol) => {
26
+ return decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol) => {
26
27
  if (propertyKey !== undefined) {
27
28
  const key = propertyKey.toString();
28
29
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the count property decorator. */
9
10
  export interface CountMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:count';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function count(eventType: Function, constantKey?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: CountMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -20,6 +20,12 @@ import { getSetFromMetadata, setFrom } from './setFrom.js';
20
20
  import { getSetValueMetadata, setValue } from './setValue.js';
21
21
  import { getSubtractFromMetadata, subtractFrom } from './subtractFrom.js';
22
22
  import { getTrackedJsonSchemaProperties, jsonSchemaProperty } from '../../schemas/jsonSchemaProperty.js';
23
+ import { DecoratorType } from '../../types/DecoratorType.js';
24
+ import { TypeDiscoverer } from '../../types/TypeDiscoverer.js';
25
+ import { DefaultClientArtifactsProvider } from '../../artifacts/DefaultClientArtifactsProvider.js';
26
+ import { ReadModels } from '../../readModels/ReadModels.js';
27
+ import type { ChronicleConnection } from '../../connection/ChronicleConnection.js';
28
+ import { vi } from 'vitest';
23
29
 
24
30
  class Changed {}
25
31
 
@@ -45,7 +51,46 @@ class Mappings {
45
51
  @jsonSchemaProperty() tracked!: string;
46
52
  }
47
53
 
54
+ class StandardMappedOnly {
55
+ @setFrom(Changed) value = '';
56
+ }
57
+
58
+ class UnconstructedMappedOnly {
59
+ @setFrom(Changed) value = '';
60
+ }
61
+
48
62
  describe('standard model-bound decorators', () => {
63
+ it('collects a standard-mapped class for lazy root discovery when constructed without file discovery', () => {
64
+ new StandardMappedOnly();
65
+ new StandardMappedOnly();
66
+ expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.ReadModel, 'StandardMappedOnly'))
67
+ .toBeUndefined();
68
+ expect(DefaultClientArtifactsProvider.default.readModels.filter(type => type === StandardMappedOnly)).toHaveLength(1);
69
+ });
70
+
71
+ it('registers the declaring class once even when subclasses and many instances are constructed', () => {
72
+ class Base {
73
+ @setFrom(Changed) value = '';
74
+ }
75
+ class Derived extends Base {}
76
+ const track = vi.spyOn(TypeDiscoverer.default, 'trackModelBoundProperty');
77
+ try {
78
+ for (let index = 0; index < 50; index++) new Base();
79
+ new Derived();
80
+ expect(track.mock.calls.filter(([type]) => type === Base)).toHaveLength(1);
81
+ expect(track.mock.calls.some(([type]) => type === Derived)).toBe(false);
82
+ } finally {
83
+ track.mockRestore();
84
+ }
85
+ });
86
+
87
+ it('should explain that an unconstructed property-only model was never registered', async () => {
88
+ const getInstanceByKey = vi.fn().mockResolvedValue({ ReadModel: '{"value":"stored"}' });
89
+ const connection = { readModels: { getInstanceByKey } } as unknown as ChronicleConnection;
90
+ const readModels = new ReadModels('store', 'Default', connection, DefaultClientArtifactsProvider.default, 'sink');
91
+ await expect(readModels.findInstanceById(UnconstructedMappedOnly, 'id')).rejects.toThrow(/add a class-level @fromEvent/);
92
+ expect(getInstanceByKey).not.toHaveBeenCalled();
93
+ });
49
94
  it('stores class and property annotations without constructing an instance', () => {
50
95
  const target = Mappings.prototype;
51
96
  expect(isNoAutoMap(Mappings)).toBe(true);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the decrement property decorator. */
9
10
  export interface DecrementMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:decrement';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function decrement(eventType: Function, constantKey?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: DecrementMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the fromAll property decorator. */
9
10
  export interface FromAllMetadata {
@@ -25,7 +26,7 @@ const METADATA_KEY = 'chronicle:projection:fromAll';
25
26
  * @returns A property decorator.
26
27
  */
27
28
  export function fromAll(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
28
- return decorateProperty((target: object, propertyKey: string | symbol) => {
29
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
29
30
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
30
31
  const metadata: FromAllMetadata = { property, contextProperty };
31
32
  Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the fromEvery property decorator. */
9
10
  export interface FromEveryMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:fromEvery';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function fromEvery(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
27
28
  const metadata: FromEveryMetadata = { property, contextProperty };
28
29
  Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the increment property decorator. */
9
10
  export interface IncrementMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:increment';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function increment(eventType: Function, constantKey?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: IncrementMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the join property decorator. */
9
10
  export interface JoinMetadata {
@@ -25,7 +26,7 @@ const METADATA_KEY = 'chronicle:projection:join';
25
26
  * @returns A property decorator.
26
27
  */
27
28
  export function join(eventType: Function, on?: string, eventPropertyName?: string): ChroniclePropertyDecorator {
28
- return decorateProperty((target: object, propertyKey: string | symbol) => {
29
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
29
30
  const key = propertyKey.toString();
30
31
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
31
32
  const existing: JoinMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { decorateProperty, hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
8
 
8
9
  const METADATA_KEY = 'chronicle:projection:nested';
9
10
 
@@ -13,7 +14,7 @@ const METADATA_KEY = 'chronicle:projection:nested';
13
14
  * @param target - The class prototype.
14
15
  * @param propertyKey - The property name.
15
16
  */
16
- const decorateNested = decorateProperty((target: object, propertyKey: string | symbol): void => {
17
+ const decorateNested = decorateModelBoundProperty((target: object, propertyKey: string | symbol): void => {
17
18
  const key = propertyKey.toString();
18
19
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
19
20
  Reflect.defineMetadata(METADATA_KEY, true, target, key);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { decorateClassOrProperty, hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
+ import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
7
8
 
8
9
  const CLASS_METADATA_KEY = 'chronicle:projection:noAutoMap:class';
9
10
  const PROPERTY_METADATA_KEY = 'chronicle:projection:noAutoMap:property';
@@ -18,7 +19,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:noAutoMap:property';
18
19
  * @param target - The class constructor, or the class prototype when used on a property.
19
20
  * @param propertyKey - The property name, when used as a property decorator.
20
21
  */
21
- const decorateNoAutoMap = decorateClassOrProperty((target: object, propertyKey?: string | symbol): void => {
22
+ const decorateNoAutoMap = decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol): void => {
22
23
  if (propertyKey !== undefined) {
23
24
  const key = propertyKey.toString();
24
25
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChronicleClassOrPropertyDecorator, decorateClassOrProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChronicleClassOrPropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the removedWith class or property decorator. */
9
10
  export interface RemovedWithMetadata {
@@ -28,7 +29,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:removedWith:property';
28
29
  * @returns A class and property decorator.
29
30
  */
30
31
  export function removedWith(eventType: Function, key?: string, parentKey?: string): ChronicleClassOrPropertyDecorator {
31
- return decorateClassOrProperty((target: object, propertyKey?: string | symbol) => {
32
+ return decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol) => {
32
33
  if (propertyKey !== undefined) {
33
34
  const propKey = propertyKey.toString();
34
35
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChronicleClassOrPropertyDecorator, decorateClassOrProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChronicleClassOrPropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the removedWithJoin class or property decorator. */
9
10
  export interface RemovedWithJoinMetadata {
@@ -23,7 +24,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:removedWithJoin:property';
23
24
  * @returns A class and property decorator.
24
25
  */
25
26
  export function removedWithJoin(eventType: Function, key?: string): ChronicleClassOrPropertyDecorator {
26
- return decorateClassOrProperty((target: object, propertyKey?: string | symbol) => {
27
+ return decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol) => {
27
28
  if (propertyKey !== undefined) {
28
29
  const propKey = propertyKey.toString();
29
30
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the setFrom property decorator. */
9
10
  export interface SetFromMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:setFrom';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function setFrom(eventType: Function, eventPropertyName?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: SetFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -5,7 +5,8 @@ import 'reflect-metadata';
5
5
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
6
6
  import type { EventContext } from '../../events/index.js';
7
7
  import { TypeIntrospector } from '../../types/index.js';
8
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
8
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
9
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
9
10
 
10
11
  /** Metadata stored by the setFromContext property decorator. */
11
12
  export interface SetFromContextMetadata {
@@ -29,7 +30,7 @@ export function setFromContext(
29
30
  eventType: Function,
30
31
  contextPropertyOrAccessor?: string | PropertyAccessor<EventContext>
31
32
  ): ChroniclePropertyDecorator {
32
- return decorateProperty((target: object, propertyKey: string | symbol) => {
33
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
33
34
  const key = propertyKey.toString();
34
35
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
35
36
  const existing: SetFromContextMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the setValue property decorator. */
9
10
  export interface SetValueMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:setValue';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function setValue(eventType: Function, value: unknown): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: SetValueMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -3,7 +3,8 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
- import { ChroniclePropertyDecorator, decorateProperty, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
7
8
 
8
9
  /** Metadata stored by the subtractFrom property decorator. */
9
10
  export interface SubtractFromMetadata {
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:subtractFrom';
22
23
  * @returns A property decorator.
23
24
  */
24
25
  export function subtractFrom(eventType: Function, eventPropertyName?: string): ChroniclePropertyDecorator {
25
- return decorateProperty((target: object, propertyKey: string | symbol) => {
26
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
26
27
  const key = propertyKey.toString();
27
28
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
28
29
  const existing: SubtractFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
@@ -0,0 +1,5 @@
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
+ /** The wire representation of PropertyPath.NotSet, used for nested projections. */
5
+ export const notSetPropertyPath = '*NotSet*';
@@ -105,7 +105,9 @@ export class Reactors implements IReactors {
105
105
  private readonly _lifecycle: ConnectionLifecycle;
106
106
  private readonly _reactors = new Map<string, Constructor>();
107
107
  private readonly _queues = new Map<string, AsyncQueue<ReactorMessage>>();
108
+ private readonly _observations = new Map<string, AbortController>();
108
109
  private _registered = false;
110
+ private _disposed = false;
109
111
 
110
112
  /**
111
113
  * Creates a new {@link Reactors} instance.
@@ -133,6 +135,13 @@ export class Reactors implements IReactors {
133
135
  });
134
136
  }
135
137
 
138
+ /** Stops observations permanently when the owning client is disposed. */
139
+ dispose(): void {
140
+ this._disposed = true;
141
+ this._registered = false;
142
+ this.disconnectAll();
143
+ }
144
+
136
145
  /** @inheritdoc */
137
146
  async discover(): Promise<void> {
138
147
  this._reactors.clear();
@@ -147,7 +156,7 @@ export class Reactors implements IReactors {
147
156
 
148
157
  /** @inheritdoc */
149
158
  async register(): Promise<void> {
150
- if (this._registered) {
159
+ if (this._registered || this._disposed) {
151
160
  return;
152
161
  }
153
162
 
@@ -155,6 +164,7 @@ export class Reactors implements IReactors {
155
164
  await this.discover();
156
165
  }
157
166
 
167
+ if (this._disposed) return;
158
168
  this._logger.info('Registering reactors', { count: this._reactors.size });
159
169
  for (const [id, reactorType] of this._reactors) {
160
170
  this.startObservation(id, reactorType);
@@ -164,6 +174,7 @@ export class Reactors implements IReactors {
164
174
  }
165
175
 
166
176
  private startObservation(id: string, reactorType: Constructor): void {
177
+ if (this._disposed) return;
167
178
  const metadata = getReactorMetadata(reactorType)!;
168
179
  const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
169
180
  const eventTypes = this.getEventTypesFor(reactorType);
@@ -205,12 +216,12 @@ export class Reactors implements IReactors {
205
216
  private scheduleReobserve(id: string, reactorType: Constructor): void {
206
217
  // A disconnect clears the registration; the reconnect re-registers every
207
218
  // reactor from scratch, so retrying here as well would double up.
208
- if (!this._registered) {
219
+ if (!this._registered || this._disposed) {
209
220
  return;
210
221
  }
211
222
 
212
223
  const handle = setTimeout(() => {
213
- if (!this._registered) {
224
+ if (!this._registered || this._disposed) {
214
225
  return;
215
226
  }
216
227
 
@@ -228,7 +239,9 @@ export class Reactors implements IReactors {
228
239
  eventTypes: EventTypeEntry[]
229
240
  ): Promise<void> {
230
241
  const queue = new AsyncQueue<ReactorMessage>();
242
+ const controller = new AbortController();
231
243
  this._queues.set(id, queue);
244
+ this._observations.set(id, controller);
232
245
 
233
246
  queue.send({
234
247
  Content: {
@@ -259,7 +272,7 @@ export class Reactors implements IReactors {
259
272
  try {
260
273
  const reactorInstance = new (reactorType as new () => Record<string, Function>)();
261
274
 
262
- for await (const eventsToObserve of this._connection.reactors.observe(queue)) {
275
+ for await (const eventsToObserve of this._connection.reactors.observe(queue, { signal: controller.signal })) {
263
276
  let lastSuccessfullyObservedEvent = SEQUENCE_NUMBER_UNAVAILABLE;
264
277
  let state = ObservationState.Success;
265
278
  const exceptionMessages: string[] = [];
@@ -358,7 +371,9 @@ export class Reactors implements IReactors {
358
371
  // leak a duplicate stream on every reconnect.
359
372
  if (this._queues.get(id) === queue) {
360
373
  this._queues.delete(id);
374
+ this._observations.delete(id);
361
375
  }
376
+ queue.complete();
362
377
  }
363
378
  }
364
379
 
@@ -422,9 +437,9 @@ export class Reactors implements IReactors {
422
437
  }
423
438
 
424
439
  private disconnectAll(): void {
425
- for (const queue of this._queues.values()) {
426
- queue.complete();
427
- }
440
+ for (const controller of this._observations.values()) controller.abort();
441
+ for (const queue of this._queues.values()) queue.complete();
442
+ this._observations.clear();
428
443
  this._queues.clear();
429
444
  }
430
445
  }
@@ -25,8 +25,8 @@ export interface ReactorMetadata {
25
25
  * identifier. This is the TypeScript equivalent of the C# `[Reactor]` attribute.
26
26
  *
27
27
  * Reactors observe events from an event sequence and produce side effects.
28
- * Method dispatch is by convention: the type of the first parameter of each public method
29
- * determines which events it handles.
28
+ * Method dispatch is by convention: a handler's name is the event class name with
29
+ * its first letter lowercased (for example, `projectRegistered` handles `ProjectRegistered`).
30
30
  *
31
31
  * @param id - The unique identifier for the reactor. Defaults to the class name if omitted.
32
32
  * @param eventSequenceId - Optional explicit event sequence identifier.
@@ -33,8 +33,9 @@ export interface IReadModels {
33
33
  * @param readModelType - The read model type to retrieve.
34
34
  * @param key - The read model key.
35
35
  * @param sessionId - Optional session identifier.
36
- * @returns The read model instance. When no instance exists, returns a prototype-only instance.
37
- * @deprecated Use {@link findInstanceById} to distinguish an absent instance from a stored one.
36
+ * @returns The read model instance. When the kernel returns an empty document for a missing key,
37
+ * returns a prototype-only instance; a JSON `null` document may construct a model with default fields.
38
+ * Use {@link findInstanceById} when absence must be distinguished from stored data.
38
39
  */
39
40
  getInstanceById<TReadModel>(readModelType: Constructor<TReadModel>, key: string, sessionId?: string): Promise<TReadModel>;
40
41
 
@@ -25,6 +25,28 @@ function createMaterializedReadModels(instancesJson: string[], releaseResponse:
25
25
  }
26
26
 
27
27
  describe('MaterializedReadModels', () => {
28
+ describe('when materialized models have undecorated members', () => {
29
+ class Model { name = ''; count = 0; occurred = new Date(0); }
30
+ const json = JSON.stringify({ name: 'stored', count: 3, occurred: '2024-02-01T00:00:00.000Z' });
31
+
32
+ it('should restore members in paged reads', async () => {
33
+ const { readModels } = createMaterializedReadModels([json]);
34
+ const [instance] = await readModels.getInstances(Model);
35
+ expect(instance).toMatchObject({ name: 'stored', count: 3 });
36
+ expect(instance.occurred).toEqual(new Date('2024-02-01T00:00:00.000Z'));
37
+ });
38
+
39
+ it('should restore members in observed pages', async () => {
40
+ const connection = { materializedReadModels: { observeInstances: async function* () {
41
+ yield { Instances: [json] };
42
+ } } } as unknown as ChronicleConnection;
43
+ const readModels = new MaterializedReadModels('store', 'tenant', connection);
44
+ const response = await readModels.observeInstances(Model)[Symbol.asyncIterator]().next();
45
+ expect(response.value?.[0].name).toBe('stored');
46
+ expect(response.value?.[0].occurred).toBeInstanceOf(Date);
47
+ });
48
+ });
49
+
28
50
  describe('when a read model has a property decorated with @subject()', () => {
29
51
  class Employee {
30
52
  id = '';
@@ -7,6 +7,7 @@ import { ChronicleConnection } from '../connection/index.js';
7
7
  import { JsonSchemaGenerator } from '../schemas/index.js';
8
8
  import { getReadModelMetadata } from './readModel.js';
9
9
  import { ReadModelSubjectResolver } from './ReadModelSubjectResolver.js';
10
+ import { deserializeReadModel } from './deserializeReadModel.js';
10
11
  import type { IMaterializedReadModels } from './IMaterializedReadModels.js';
11
12
 
12
13
  const defaultTake = 50;
@@ -82,10 +83,7 @@ export class MaterializedReadModels implements IMaterializedReadModels {
82
83
  }
83
84
 
84
85
  private deserialize<TReadModel>(readModelType: Constructor<TReadModel>, json: string): TReadModel {
85
- if (!json) {
86
- return Object.create(readModelType.prototype) as TReadModel;
87
- }
88
- return JsonSerializer.deserialize(readModelType as Constructor<object>, json) as TReadModel;
86
+ return deserializeReadModel(readModelType, json);
89
87
  }
90
88
 
91
89
  private async releaseInstances<TReadModel>(readModelType: Constructor<TReadModel>, instances: TReadModel[]): Promise<TReadModel[]> {