@cratis/chronicle 6.3.2 → 6.5.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 (228) hide show
  1. package/ChronicleClient.ts +1 -1
  2. package/EventStore.ts +7 -2
  3. package/README.md +13 -2
  4. package/artifacts/validateArtifactSchemas.spec.ts +75 -0
  5. package/artifacts/validateArtifactSchemas.ts +39 -0
  6. package/auditing/AmbientContext.spec.ts +57 -0
  7. package/auditing/CausationManager.ts +13 -15
  8. package/compliance/pii.ts +7 -6
  9. package/compliance/subject.ts +6 -5
  10. package/confidentiality/encrypted.ts +7 -6
  11. package/correlation/CorrelationIdManager.ts +5 -0
  12. package/dist/ChronicleClient.js +1 -1
  13. package/dist/ChronicleClient.js.map +1 -1
  14. package/dist/EventStore.d.ts +3 -1
  15. package/dist/EventStore.d.ts.map +1 -1
  16. package/dist/EventStore.js +6 -2
  17. package/dist/EventStore.js.map +1 -1
  18. package/dist/artifacts/validateArtifactSchemas.d.ts +4 -0
  19. package/dist/artifacts/validateArtifactSchemas.d.ts.map +1 -0
  20. package/dist/artifacts/validateArtifactSchemas.js +38 -0
  21. package/dist/artifacts/validateArtifactSchemas.js.map +1 -0
  22. package/dist/auditing/CausationManager.d.ts +3 -1
  23. package/dist/auditing/CausationManager.d.ts.map +1 -1
  24. package/dist/auditing/CausationManager.js +9 -15
  25. package/dist/auditing/CausationManager.js.map +1 -1
  26. package/dist/compliance/pii.d.ts +2 -1
  27. package/dist/compliance/pii.d.ts.map +1 -1
  28. package/dist/compliance/pii.js +6 -5
  29. package/dist/compliance/pii.js.map +1 -1
  30. package/dist/compliance/subject.d.ts +2 -1
  31. package/dist/compliance/subject.d.ts.map +1 -1
  32. package/dist/compliance/subject.js +5 -4
  33. package/dist/compliance/subject.js.map +1 -1
  34. package/dist/confidentiality/encrypted.d.ts +2 -1
  35. package/dist/confidentiality/encrypted.d.ts.map +1 -1
  36. package/dist/confidentiality/encrypted.js +6 -5
  37. package/dist/confidentiality/encrypted.js.map +1 -1
  38. package/dist/correlation/CorrelationIdManager.d.ts +2 -0
  39. package/dist/correlation/CorrelationIdManager.d.ts.map +1 -1
  40. package/dist/correlation/CorrelationIdManager.js +4 -0
  41. package/dist/correlation/CorrelationIdManager.js.map +1 -1
  42. package/dist/eventSequences/AppendOptions.d.ts +3 -4
  43. package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
  44. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  45. package/dist/eventSequences/EventSequence.js +19 -14
  46. package/dist/eventSequences/EventSequence.js.map +1 -1
  47. package/dist/eventSequences/EventSequenceNumber.d.ts +3 -1
  48. package/dist/eventSequences/EventSequenceNumber.d.ts.map +1 -1
  49. package/dist/eventSequences/EventSequenceNumber.js +3 -1
  50. package/dist/eventSequences/EventSequenceNumber.js.map +1 -1
  51. package/dist/events/eventTypeDecorator.d.ts +8 -7
  52. package/dist/events/eventTypeDecorator.d.ts.map +1 -1
  53. package/dist/events/eventTypeDecorator.js +22 -5
  54. package/dist/events/eventTypeDecorator.js.map +1 -1
  55. package/dist/events/index.d.ts +2 -0
  56. package/dist/events/index.d.ts.map +1 -1
  57. package/dist/events/index.js +2 -0
  58. package/dist/events/index.js.map +1 -1
  59. package/dist/events/isRegisteredEvent.d.ts +4 -0
  60. package/dist/events/isRegisteredEvent.d.ts.map +1 -0
  61. package/dist/events/isRegisteredEvent.js +9 -0
  62. package/dist/events/isRegisteredEvent.js.map +1 -0
  63. package/dist/identity/IdentityProvider.d.ts +2 -0
  64. package/dist/identity/IdentityProvider.d.ts.map +1 -1
  65. package/dist/identity/IdentityProvider.js +4 -0
  66. package/dist/identity/IdentityProvider.js.map +1 -1
  67. package/dist/projections/modelBound/addFrom.d.ts +2 -1
  68. package/dist/projections/modelBound/addFrom.d.ts.map +1 -1
  69. package/dist/projections/modelBound/addFrom.js +4 -3
  70. package/dist/projections/modelBound/addFrom.js.map +1 -1
  71. package/dist/projections/modelBound/childrenFrom.d.ts +2 -1
  72. package/dist/projections/modelBound/childrenFrom.d.ts.map +1 -1
  73. package/dist/projections/modelBound/childrenFrom.js +4 -3
  74. package/dist/projections/modelBound/childrenFrom.js.map +1 -1
  75. package/dist/projections/modelBound/clearWith.d.ts +2 -1
  76. package/dist/projections/modelBound/clearWith.d.ts.map +1 -1
  77. package/dist/projections/modelBound/clearWith.js +4 -3
  78. package/dist/projections/modelBound/clearWith.js.map +1 -1
  79. package/dist/projections/modelBound/count.d.ts +2 -1
  80. package/dist/projections/modelBound/count.d.ts.map +1 -1
  81. package/dist/projections/modelBound/count.js +4 -3
  82. package/dist/projections/modelBound/count.js.map +1 -1
  83. package/dist/projections/modelBound/decrement.d.ts +2 -1
  84. package/dist/projections/modelBound/decrement.d.ts.map +1 -1
  85. package/dist/projections/modelBound/decrement.js +4 -3
  86. package/dist/projections/modelBound/decrement.js.map +1 -1
  87. package/dist/projections/modelBound/fromAll.d.ts +2 -1
  88. package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
  89. package/dist/projections/modelBound/fromAll.js +4 -3
  90. package/dist/projections/modelBound/fromAll.js.map +1 -1
  91. package/dist/projections/modelBound/fromEvery.d.ts +2 -1
  92. package/dist/projections/modelBound/fromEvery.d.ts.map +1 -1
  93. package/dist/projections/modelBound/fromEvery.js +4 -3
  94. package/dist/projections/modelBound/fromEvery.js.map +1 -1
  95. package/dist/projections/modelBound/increment.d.ts +2 -1
  96. package/dist/projections/modelBound/increment.d.ts.map +1 -1
  97. package/dist/projections/modelBound/increment.js +4 -3
  98. package/dist/projections/modelBound/increment.js.map +1 -1
  99. package/dist/projections/modelBound/join.d.ts +2 -1
  100. package/dist/projections/modelBound/join.d.ts.map +1 -1
  101. package/dist/projections/modelBound/join.js +4 -3
  102. package/dist/projections/modelBound/join.js.map +1 -1
  103. package/dist/projections/modelBound/nested.d.ts +1 -6
  104. package/dist/projections/modelBound/nested.d.ts.map +1 -1
  105. package/dist/projections/modelBound/nested.js +6 -2
  106. package/dist/projections/modelBound/nested.js.map +1 -1
  107. package/dist/projections/modelBound/noAutoMap.d.ts +4 -11
  108. package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -1
  109. package/dist/projections/modelBound/noAutoMap.js +6 -2
  110. package/dist/projections/modelBound/noAutoMap.js.map +1 -1
  111. package/dist/projections/modelBound/removedWith.d.ts +2 -1
  112. package/dist/projections/modelBound/removedWith.d.ts.map +1 -1
  113. package/dist/projections/modelBound/removedWith.js +4 -3
  114. package/dist/projections/modelBound/removedWith.js.map +1 -1
  115. package/dist/projections/modelBound/removedWithJoin.d.ts +2 -1
  116. package/dist/projections/modelBound/removedWithJoin.d.ts.map +1 -1
  117. package/dist/projections/modelBound/removedWithJoin.js +4 -3
  118. package/dist/projections/modelBound/removedWithJoin.js.map +1 -1
  119. package/dist/projections/modelBound/setFrom.d.ts +2 -1
  120. package/dist/projections/modelBound/setFrom.d.ts.map +1 -1
  121. package/dist/projections/modelBound/setFrom.js +4 -3
  122. package/dist/projections/modelBound/setFrom.js.map +1 -1
  123. package/dist/projections/modelBound/setFromContext.d.ts +3 -2
  124. package/dist/projections/modelBound/setFromContext.d.ts.map +1 -1
  125. package/dist/projections/modelBound/setFromContext.js +4 -3
  126. package/dist/projections/modelBound/setFromContext.js.map +1 -1
  127. package/dist/projections/modelBound/setValue.d.ts +2 -1
  128. package/dist/projections/modelBound/setValue.d.ts.map +1 -1
  129. package/dist/projections/modelBound/setValue.js +4 -3
  130. package/dist/projections/modelBound/setValue.js.map +1 -1
  131. package/dist/projections/modelBound/subtractFrom.d.ts +2 -1
  132. package/dist/projections/modelBound/subtractFrom.d.ts.map +1 -1
  133. package/dist/projections/modelBound/subtractFrom.js +4 -3
  134. package/dist/projections/modelBound/subtractFrom.js.map +1 -1
  135. package/dist/readModels/IReadModels.d.ts +10 -1
  136. package/dist/readModels/IReadModels.d.ts.map +1 -1
  137. package/dist/readModels/MaterializedReadModels.js +2 -2
  138. package/dist/readModels/MaterializedReadModels.js.map +1 -1
  139. package/dist/readModels/ReadModels.d.ts +6 -1
  140. package/dist/readModels/ReadModels.d.ts.map +1 -1
  141. package/dist/readModels/ReadModels.js +28 -2
  142. package/dist/readModels/ReadModels.js.map +1 -1
  143. package/dist/readModels/readModel.d.ts +2 -1
  144. package/dist/readModels/readModel.d.ts.map +1 -1
  145. package/dist/readModels/readModel.js +22 -5
  146. package/dist/readModels/readModel.js.map +1 -1
  147. package/dist/schemas/JsonSchemaGenerator.d.ts +6 -1
  148. package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
  149. package/dist/schemas/JsonSchemaGenerator.js +41 -31
  150. package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
  151. package/dist/schemas/createDeferredSchema.d.ts +7 -0
  152. package/dist/schemas/createDeferredSchema.d.ts.map +1 -0
  153. package/dist/schemas/createDeferredSchema.js +19 -0
  154. package/dist/schemas/createDeferredSchema.js.map +1 -0
  155. package/dist/schemas/index.d.ts +1 -0
  156. package/dist/schemas/index.d.ts.map +1 -1
  157. package/dist/schemas/index.js.map +1 -1
  158. package/dist/schemas/jsonSchemaProperty.d.ts +2 -1
  159. package/dist/schemas/jsonSchemaProperty.d.ts.map +1 -1
  160. package/dist/schemas/jsonSchemaProperty.js +3 -2
  161. package/dist/schemas/jsonSchemaProperty.js.map +1 -1
  162. package/dist/tsconfig.tsbuildinfo +1 -1
  163. package/dist/types/TypeIntrospector.d.ts.map +1 -1
  164. package/dist/types/TypeIntrospector.js +9 -3
  165. package/dist/types/TypeIntrospector.js.map +1 -1
  166. package/dist/types/index.d.ts +2 -0
  167. package/dist/types/index.d.ts.map +1 -1
  168. package/dist/types/propertyDecoratorMetadata.d.ts +17 -0
  169. package/dist/types/propertyDecoratorMetadata.d.ts.map +1 -0
  170. package/dist/types/propertyDecoratorMetadata.js +76 -0
  171. package/dist/types/propertyDecoratorMetadata.js.map +1 -0
  172. package/dist/types/standardDecoratorMetadata.d.ts +9 -0
  173. package/dist/types/standardDecoratorMetadata.d.ts.map +1 -0
  174. package/dist/types/standardDecoratorMetadata.js +20 -0
  175. package/dist/types/standardDecoratorMetadata.js.map +1 -0
  176. package/eventSequences/AppendOptions.ts +3 -4
  177. package/eventSequences/EventSequence.spec.ts +59 -0
  178. package/eventSequences/EventSequence.ts +21 -15
  179. package/eventSequences/EventSequenceNumber.ts +4 -1
  180. package/eventSequences/for_EventSequence/when_appending/with_metadata.spec.ts +2 -1
  181. package/events/classDecorators.standard.spec.ts +77 -0
  182. package/events/eventTypeDecorator.ts +31 -13
  183. package/events/index.ts +3 -0
  184. package/events/isRegisteredEvent.spec.ts +26 -0
  185. package/events/isRegisteredEvent.ts +12 -0
  186. package/identity/IdentityProvider.ts +5 -0
  187. package/package.json +3 -1
  188. package/projections/modelBound/addFrom.ts +5 -4
  189. package/projections/modelBound/childrenFrom.ts +5 -4
  190. package/projections/modelBound/clearWith.ts +5 -4
  191. package/projections/modelBound/count.ts +5 -4
  192. package/projections/modelBound/decorators.standard.spec.ts +71 -0
  193. package/projections/modelBound/decrement.ts +5 -4
  194. package/projections/modelBound/fromAll.ts +5 -4
  195. package/projections/modelBound/fromEvery.ts +5 -4
  196. package/projections/modelBound/increment.ts +5 -4
  197. package/projections/modelBound/join.ts +5 -4
  198. package/projections/modelBound/nested.ts +9 -2
  199. package/projections/modelBound/noAutoMap.ts +11 -2
  200. package/projections/modelBound/removedWith.ts +5 -4
  201. package/projections/modelBound/removedWithJoin.ts +5 -4
  202. package/projections/modelBound/setFrom.ts +5 -4
  203. package/projections/modelBound/setFromContext.ts +7 -6
  204. package/projections/modelBound/setValue.ts +5 -4
  205. package/projections/modelBound/subtractFrom.ts +5 -4
  206. package/readModels/IReadModels.ts +11 -1
  207. package/readModels/MaterializedReadModels.spec.ts +17 -6
  208. package/readModels/MaterializedReadModels.ts +2 -2
  209. package/readModels/ReadModels.spec.ts +96 -3
  210. package/readModels/ReadModels.ts +32 -2
  211. package/readModels/readModel.ts +24 -6
  212. package/readModels/standardFallbacks.standard.spec.ts +39 -0
  213. package/schemas/JsonSchemaGenerator.confidentiality.spec.ts +2 -0
  214. package/schemas/JsonSchemaGenerator.spec.ts +28 -1
  215. package/schemas/JsonSchemaGenerator.ts +45 -30
  216. package/schemas/createDeferredSchema.ts +21 -0
  217. package/schemas/decorators.expected.fixture.ts +24 -0
  218. package/schemas/decorators.legacy.spec.ts +62 -0
  219. package/schemas/decorators.standard.spec.ts +235 -0
  220. package/schemas/index.ts +1 -0
  221. package/schemas/jsonSchemaProperty.ts +4 -3
  222. package/schemas/legacyDesignMetadata.spec.ts +39 -0
  223. package/types/TypeIntrospector.ts +8 -3
  224. package/types/index.ts +2 -0
  225. package/types/propertyDecoratorMetadata.ts +80 -0
  226. package/types/standardDecoratorMetadata.spec.ts +31 -0
  227. package/types/standardDecoratorMetadata.ts +26 -0
  228. package/vitest.config.ts +17 -0
@@ -0,0 +1,76 @@
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
+ import 'reflect-metadata';
4
+ import { getStandardMetadata } from './standardDecoratorMetadata.js';
5
+ /** Dispatches class and field decorations while retaining the legacy invocation shape. */
6
+ export function decorateClassOrProperty(legacy) {
7
+ const propertyDecorator = decorateProperty(legacy);
8
+ return (target, propertyOrContext) => {
9
+ if (typeof propertyOrContext === 'object' && propertyOrContext !== null) {
10
+ if (propertyOrContext.kind === 'class') {
11
+ if (typeof target !== 'function')
12
+ throw new TypeError('Chronicle class decorators require a class constructor.');
13
+ legacy(target);
14
+ }
15
+ else {
16
+ propertyDecorator(target, propertyOrContext);
17
+ }
18
+ return;
19
+ }
20
+ if (!target)
21
+ throw new TypeError('Invalid legacy decorator invocation.');
22
+ legacy(target, propertyOrContext);
23
+ };
24
+ }
25
+ /** Stores standard property annotations on the class metadata object, without constructing an instance. */
26
+ export function decorateProperty(legacy) {
27
+ return (target, keyOrContext) => {
28
+ if (typeof keyOrContext === 'object' && keyOrContext !== null) {
29
+ const context = keyOrContext;
30
+ if (target !== undefined || context.kind !== 'field' || context.static || context.private || typeof context.name !== 'string' || !context.metadata) {
31
+ throw new TypeError('Chronicle property decorators require a public instance field with standard decorator metadata.');
32
+ }
33
+ // Legacy decorators use target.constructor to track fields and store type-level annotations.
34
+ // The context.metadata object is owned by this class; never mutate its parent's metadata.
35
+ Object.defineProperty(context.metadata, 'constructor', { value: context.metadata, configurable: true });
36
+ legacy(context.metadata, context.name);
37
+ return;
38
+ }
39
+ if (!target || (typeof keyOrContext !== 'string' && typeof keyOrContext !== 'symbol')) {
40
+ throw new TypeError('Invalid legacy property decorator invocation.');
41
+ }
42
+ legacy(target, keyOrContext);
43
+ };
44
+ }
45
+ /** Reads an annotation declared by either decorator transform on a class field. */
46
+ export function getPropertyMetadata(key, target, property) {
47
+ const ownLegacy = Reflect.getOwnMetadata(key, target, property);
48
+ if (ownLegacy !== undefined)
49
+ return ownLegacy;
50
+ const type = target.constructor;
51
+ const metadata = type && getStandardMetadata(type);
52
+ const ownStandard = metadata && Reflect.getOwnMetadata(key, metadata, property);
53
+ if (ownStandard !== undefined)
54
+ return ownStandard;
55
+ return Reflect.getMetadata(key, target, property)
56
+ ?? (metadata ? Reflect.getMetadata(key, metadata, property) : undefined);
57
+ }
58
+ /** Checks whether either decorator transform declared an annotation on a class field. */
59
+ export function hasPropertyMetadata(key, target, property) {
60
+ const type = target.constructor;
61
+ const metadata = type && getStandardMetadata(type);
62
+ return Reflect.hasMetadata(key, target, property) || !!metadata && Reflect.hasMetadata(key, metadata, property);
63
+ }
64
+ /** Reads a type-level annotation emitted by a standard field decorator (or its legacy equivalent). */
65
+ export function getTypeOrFieldMetadata(key, type) {
66
+ const ownLegacy = Reflect.getOwnMetadata(key, type);
67
+ if (ownLegacy !== undefined)
68
+ return ownLegacy;
69
+ const metadata = getStandardMetadata(type);
70
+ const ownStandard = metadata && Reflect.getOwnMetadata(key, metadata);
71
+ if (ownStandard !== undefined)
72
+ return ownStandard;
73
+ return Reflect.getMetadata(key, type)
74
+ ?? (metadata ? Reflect.getMetadata(key, metadata) : undefined);
75
+ }
76
+ //# sourceMappingURL=propertyDecoratorMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propertyDecoratorMetadata.js","sourceRoot":"","sources":["../../types/propertyDecoratorMetadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /** A class decorator accepted by both the legacy and standard TypeScript transforms. */
2
+ export type ChronicleClassDecorator = ClassDecorator & ((value: Function, context: ClassDecoratorContext) => void);
3
+ /** Gets the metadata object attached when a standard-decorated class finishes evaluation. */
4
+ export declare function getStandardMetadata(type: Function): object | undefined;
5
+ /** Whether a class completed its own standard decorator evaluation. */
6
+ export declare function hasOwnStandardMetadata(type: Function): boolean;
7
+ /** Prevents a class decorator from inspecting fields before their metadata is attached. */
8
+ export declare function requireCompletedStandardMetadata(type: Function): void;
9
+ //# sourceMappingURL=standardDecoratorMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standardDecoratorMetadata.d.ts","sourceRoot":"","sources":["../../types/standardDecoratorMetadata.ts"],"names":[],"mappings":"AAOA,wFAAwF;AACxF,MAAM,MAAM,uBAAuB,GAAG,cAAc,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC,CAAC;AAEnH,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAEtE;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAE9D;AAED,2FAA2F;AAC3F,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAIrE"}
@@ -0,0 +1,20 @@
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
+ // Node versions without native decorator metadata need this before evaluating a decorated class.
4
+ const symbolWithMetadata = Symbol;
5
+ symbolWithMetadata.metadata ??= Symbol.for('Symbol.metadata');
6
+ /** Gets the metadata object attached when a standard-decorated class finishes evaluation. */
7
+ export function getStandardMetadata(type) {
8
+ return Reflect.get(type, symbolWithMetadata.metadata);
9
+ }
10
+ /** Whether a class completed its own standard decorator evaluation. */
11
+ export function hasOwnStandardMetadata(type) {
12
+ return Object.prototype.hasOwnProperty.call(type, symbolWithMetadata.metadata);
13
+ }
14
+ /** Prevents a class decorator from inspecting fields before their metadata is attached. */
15
+ export function requireCompletedStandardMetadata(type) {
16
+ if (!hasOwnStandardMetadata(type)) {
17
+ throw new TypeError(`Standard decorator metadata for ${type.name} is not complete; inspect its schema after class evaluation.`);
18
+ }
19
+ }
20
+ //# sourceMappingURL=standardDecoratorMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standardDecoratorMetadata.js","sourceRoot":"","sources":["../../types/standardDecoratorMetadata.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,iGAAiG;AACjG,MAAM,kBAAkB,GAAG,MAAyC,CAAC;AACrE,kBAAkB,CAAC,QAAQ,KAAK,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAK9D,6FAA6F;AAC7F,MAAM,UAAU,mBAAmB,CAAC,IAAc;IAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,QAAQ,CAAuB,CAAC;AAChF,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,sBAAsB,CAAC,IAAc;IACjD,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AACnF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gCAAgC,CAAC,IAAc;IAC3D,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CAAC,mCAAmC,IAAI,CAAC,IAAI,8DAA8D,CAAC,CAAC;IACpI,CAAC;AACL,CAAC"}
@@ -42,10 +42,9 @@ export interface AppendOptions {
42
42
  tags?: ReadonlyArray<string | Tag>;
43
43
 
44
44
  /**
45
- * Optional per-event-source-id concurrency scopes, keyed by event source id.
46
- * Only meaningful for the `appendMany(events: EventForEventSourceId[], options?)` overload, which
47
- * can target multiple distinct event sources in a single batch. When an event source id has no
48
- * entry here, it falls back to {@link concurrencyScope}.
45
+ * Optional labeled concurrency scopes, keyed by event source id. Labels need not be append targets.
46
+ * Any target without an explicit scope falls back to {@link concurrencyScope}.
47
+ * The kernel requires at least one event in a batch, so scope-only batches are not supported.
49
48
  */
50
49
  concurrencyScopes?: Record<string, ConcurrencyScope>;
51
50
  }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { describe, expect, it, vi } from 'vitest';
5
5
  import type { ChronicleConnection } from '../connection/index.js';
6
+ import { causationManager, CausationType } from '../auditing/index.js';
6
7
  import type { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
7
8
  import { eventType } from '../events/eventTypeDecorator.js';
8
9
  import { CompleteStreamError } from './CompleteStreamError.js';
@@ -354,6 +355,64 @@ describe('EventSequence', () => {
354
355
  });
355
356
  });
356
357
 
358
+ describe('when appending several batches in one command scope', () => {
359
+ it('should not carry the previous append link into the next append', async () => {
360
+ const { eventSequence, appendManyForEventSources } = createEventSequence();
361
+ await causationManager.run(new CausationType('Command'), {}, async () => {
362
+ const commandChain = causationManager.getCurrentChain().map(c => ({
363
+ Occurred: { Value: c.occurred.toISOString() }, Type: c.type.name, Properties: { ...c.properties }
364
+ }));
365
+ await eventSequence.appendMany([{ eventSourceId: 'target', event: new SomethingHappened() }]);
366
+ await eventSequence.appendMany([{ eventSourceId: 'target', event: new SomethingHappened() }]);
367
+
368
+ // The test runner may have an ambient causation link; neither batch may add one to the next.
369
+ const [first, second] = appendManyForEventSources.mock.calls;
370
+ const expectedChain = [...commandChain, expect.objectContaining({
371
+ Type: CausationType.appendManyEvents.name, Properties: { count: '1' }
372
+ })];
373
+ expect(first[0].Causation).toEqual(expectedChain);
374
+ expect(second[0].Causation).toEqual(expectedChain);
375
+ });
376
+ });
377
+ });
378
+
379
+ describe('when a batch has an independent concurrency label', () => {
380
+ it('should send both the event target and the independent scope', async () => {
381
+ const { eventSequence, appendManyForEventSources } = createEventSequence();
382
+ await eventSequence.appendMany([{ eventSourceId: 'target', event: new SomethingHappened() }], {
383
+ concurrencyScope: { sequenceNumber: 7n },
384
+ concurrencyScopes: { independent: { sequenceNumber: 3n, eventSourceId: true } }
385
+ });
386
+
387
+ const scopes = appendManyForEventSources.mock.calls[0][0].ConcurrencyScopes;
388
+ expect(scopes).toEqual(expect.arrayContaining([
389
+ expect.objectContaining({ EventSourceId: 'independent', Scope: expect.objectContaining({ SequenceNumber: 3n, EventSourceId: true }) }),
390
+ expect.objectContaining({ EventSourceId: 'target', Scope: expect.objectContaining({ SequenceNumber: 7n }) })
391
+ ]));
392
+ });
393
+ });
394
+
395
+ describe('when a batch expects no matching event', () => {
396
+ it('should send the dedicated flag and unavailable sequence number', async () => {
397
+ const { eventSequence, appendManyForEventSources } = createEventSequence();
398
+ await eventSequence.appendMany([{ eventSourceId: 'target', event: new SomethingHappened() }], {
399
+ concurrencyScopes: { independent: { sequenceNumber: EventSequenceNumber.beforeFirst.value, eventSourceId: true } }
400
+ });
401
+ const scope = appendManyForEventSources.mock.calls[0][0].ConcurrencyScopes[0].Scope;
402
+ expect(scope.ExpectsNoMatchingEvent).toBe(true);
403
+ expect(scope.SequenceNumber).toBe(EventSequenceNumber.unset.value);
404
+ });
405
+ });
406
+
407
+ describe('when a batch contains only concurrency scopes', () => {
408
+ it('should reject locally because the kernel requires an event', async () => {
409
+ const { eventSequence, appendManyForEventSources } = createEventSequence();
410
+ await expect(eventSequence.appendMany([], { concurrencyScopes: { independent: { sequenceNumber: 1n } } }))
411
+ .rejects.toThrow('Chronicle requires at least one event');
412
+ expect(appendManyForEventSources).not.toHaveBeenCalled();
413
+ });
414
+ });
415
+
357
416
  describe('when an EventForEventSourceId specifies its own stream targeting', () => {
358
417
  const { eventSequence, appendManyForEventSources } = createEventSequence();
359
418
 
@@ -71,8 +71,8 @@ export class EventSequence implements IEventSequence {
71
71
  // Merge static tags declared on the event type with tags supplied at append time.
72
72
  const tags = mergeTags(getTagsFor(event.constructor as Function), options?.tags);
73
73
 
74
- causationManager.add(CausationType.appendEvent, { eventType: eventType.id.value });
75
- const causationChain = causationManager.getCurrentChain();
74
+ const causationChain = causationManager.run(CausationType.appendEvent, { eventType: eventType.id.value },
75
+ () => causationManager.getCurrentChain());
76
76
  const identity = identityProvider.getCurrent();
77
77
 
78
78
  const metricAttributes = {
@@ -212,23 +212,26 @@ export class EventSequence implements IEventSequence {
212
212
  const appendOptions = typeof eventSourceIdOrEvents === 'string'
213
213
  ? options
214
214
  : eventsOrOptions as AppendOptions | undefined;
215
+ if (eventsForEventSourceIds.length === 0 && Object.keys(appendOptions?.concurrencyScopes ?? {}).length > 0) {
216
+ throw new Error('Chronicle requires at least one event to validate concurrency scopes.');
217
+ }
215
218
 
216
219
  const correlationId = appendOptions?.correlationId === undefined
217
220
  ? Guid.as(correlationIdManager.current.value)
218
221
  : Guid.as(appendOptions.correlationId);
219
222
 
220
- causationManager.add(CausationType.appendManyEvents, { count: String(eventsForEventSourceIds.length) });
221
- const batchCausationChain = causationManager.getCurrentChain();
223
+ const batchCausationChain = causationManager.run(CausationType.appendManyEvents, { count: String(eventsForEventSourceIds.length) },
224
+ () => causationManager.getCurrentChain());
222
225
  const identity = identityProvider.getCurrent();
223
226
 
224
- // Each distinct event source id in the batch gets its own concurrency scope: an explicit
225
- // entry in options.concurrencyScopes wins, falling back to the shared options.concurrencyScope
226
- // when no per-source entry is given — mirroring C#'s AppendMany(IEnumerable<EventForEventSourceId>, ...)
227
- // overload, which takes an IDictionary<EventSourceId, ConcurrencyScope> rather than one shared scope.
228
- const concurrencyScopesByEventSourceId = appendOptions?.concurrencyScopes;
229
- const defaultConcurrencyScope = appendOptions?.concurrencyScope;
230
- const resolveConcurrencyScope = (eventSourceId: string) =>
231
- this.toContractConcurrencyScope(concurrencyScopesByEventSourceId?.[eventSourceId] ?? defaultConcurrencyScope);
227
+ // Explicit labels may narrow a different event source than any target in this batch.
228
+ // Preserve them and supply the shared fallback only for targets without an explicit scope.
229
+ const concurrencyScopes = new Map<string, ConcurrencyScope | undefined>(Object.entries(appendOptions?.concurrencyScopes ?? {}));
230
+ for (const { eventSourceId } of eventsForEventSourceIds) {
231
+ if (!concurrencyScopes.has(eventSourceId)) {
232
+ concurrencyScopes.set(eventSourceId, appendOptions?.concurrencyScope);
233
+ }
234
+ }
232
235
 
233
236
  const eventsToAppend = eventsForEventSourceIds.map(({ eventSourceId, event, eventStreamType, eventStreamId, eventSourceType, subject, occurred, tags: instanceTags }) => {
234
237
  const eventType = getEventTypeFor(event.constructor as Function);
@@ -286,9 +289,9 @@ export class EventSequence implements IEventSequence {
286
289
  Properties: { ...c.properties }
287
290
  })),
288
291
  CausedBy: toContractsCausedBy(identity),
289
- ConcurrencyScopes: distinctEventSourceIds.map(eventSourceId => ({
292
+ ConcurrencyScopes: [...concurrencyScopes].map(([eventSourceId, scope]) => ({
290
293
  EventSourceId: eventSourceId,
291
- Scope: resolveConcurrencyScope(eventSourceId)
294
+ Scope: this.toContractConcurrencyScope(scope)
292
295
  }))
293
296
  });
294
297
 
@@ -752,7 +755,10 @@ export class EventSequence implements IEventSequence {
752
755
 
753
756
  private toContractConcurrencyScope(scope?: ConcurrencyScope) {
754
757
  return {
755
- SequenceNumber: scope?.sequenceNumber ?? EventSequenceNumber.unset.value,
758
+ SequenceNumber: scope?.sequenceNumber === EventSequenceNumber.beforeFirst.value
759
+ ? EventSequenceNumber.unset.value
760
+ : scope?.sequenceNumber ?? EventSequenceNumber.unset.value,
761
+ ExpectsNoMatchingEvent: scope?.sequenceNumber === EventSequenceNumber.beforeFirst.value,
756
762
  EventSourceId: scope?.eventSourceId ?? false,
757
763
  EventStreamType: scope?.eventStreamType ?? '',
758
764
  EventStreamId: scope?.eventStreamId ?? '',
@@ -8,9 +8,12 @@ export class EventSequenceNumber {
8
8
  /** Represents the first possible sequence number. */
9
9
  static readonly first = new EventSequenceNumber(0n);
10
10
 
11
- /** Represents an unset sequence number. */
11
+ /** Represents an unset/unavailable sequence number; no expected revision was supplied. */
12
12
  static readonly unset = new EventSequenceNumber(18446744073709551615n);
13
13
 
14
+ /** Expects no event matching the scope. Encoded as ExpectsNoMatchingEvent, not as a wire sequence number. */
15
+ static readonly beforeFirst = new EventSequenceNumber(18446744073709551613n);
16
+
14
17
  constructor(readonly value: bigint) {}
15
18
 
16
19
  /**
@@ -67,8 +67,9 @@ describe('when appending with explicit legacy routing and metadata', () => {
67
67
  request.Occurred!.Value.should.equal(occurred.toISOString());
68
68
  });
69
69
  it('should not derive concurrency scope from the route', () => {
70
+ // The .NET converter sends the contract's explicit false when no before-first check was requested.
70
71
  request.ConcurrencyScope!.should.deep.equal({
71
- SequenceNumber: EventSequenceNumber.unset.value, EventSourceId: false,
72
+ SequenceNumber: EventSequenceNumber.unset.value, ExpectsNoMatchingEvent: false, EventSourceId: false,
72
73
  EventStreamType: '', EventStreamId: '', EventSourceType: '', EventTypes: []
73
74
  });
74
75
  });
@@ -0,0 +1,77 @@
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 { describe, expect, it } from 'vitest';
5
+ import { constraint, getConstraintMetadata } from './constraints/constraint.js';
6
+ import { eventType, getEventTypeMetadata } from './eventTypeDecorator.js';
7
+ import { filterEventsByTag, getFilterTagsFor } from './filterEventsByTagDecorator.js';
8
+ import { eventTypeMigration, getEventTypeMigrationMetadata } from './migrations/eventTypeMigration.js';
9
+ import { tag, tags, getTagsFor } from './tagDecorator.js';
10
+ import { entersOn, getEntersOnMetadata } from '../projections/modelBound/entersOn.js';
11
+ import { eventLog, eventSequence, getEventSequenceMetadata } from '../projections/modelBound/eventSequence.js';
12
+ import { fromEvent, getFromEventMetadata } from '../projections/modelBound/fromEvent.js';
13
+ import { getGlobalForMetadata, globalFor } from '../projections/modelBound/globalFor.js';
14
+ import { isNotRewindable, notRewindable } from '../projections/modelBound/notRewindable.js';
15
+ import { isPassive, passive } from '../projections/modelBound/passive.js';
16
+ import { getVariantOfMetadata, variantOf } from '../projections/modelBound/variantOf.js';
17
+ import { getSeederMetadata, seeder } from '../seeding/seeder.js';
18
+ import { getWebhookMetadata, webhook } from '../webhooks/webhook.js';
19
+
20
+ @eventType('migration-fixture', 1)
21
+ class Previous {}
22
+
23
+ @eventType('migration-fixture', 2)
24
+ class Upgraded {}
25
+
26
+ @eventTypeMigration(Upgraded, Previous)
27
+ class Migration {}
28
+
29
+ @constraint('standard-constraint')
30
+ class Constraint {}
31
+
32
+ @tag('first')
33
+ @tags('second')
34
+ @filterEventsByTag('required')
35
+ class Tagged {}
36
+
37
+ @fromEvent(Upgraded)
38
+ @entersOn(Upgraded)
39
+ @eventSequence('sequence')
40
+ @passive
41
+ @notRewindable
42
+ class ModelBound {}
43
+
44
+ @eventLog
45
+ class EventLogModel {}
46
+
47
+ @globalFor(Upgraded)
48
+ class GlobalHandler {}
49
+
50
+ @variantOf(Upgraded, 'kind')
51
+ class Variant {}
52
+
53
+ @seeder()
54
+ class Seeder {}
55
+
56
+ @webhook('standard-webhook', 'https://example.com/hooks')
57
+ class Webhook {}
58
+
59
+ describe('standard class decorators', () => {
60
+ it('retains class registrations and metadata without legacy decorator syntax', () => {
61
+ expect(getEventTypeMetadata(Previous)?.schema.properties).toEqual({});
62
+ expect(getEventTypeMigrationMetadata(Migration)?.eventType.generation.value).toBe(2);
63
+ expect(getConstraintMetadata(Constraint)?.id.value).toBe('standard-constraint');
64
+ expect(getTagsFor(Tagged).map(item => item.value)).toEqual(['second', 'first']);
65
+ expect(getFilterTagsFor(Tagged).map(item => item.value)).toEqual(['required']);
66
+ expect(getFromEventMetadata(ModelBound)).toHaveLength(1);
67
+ expect(getEntersOnMetadata(ModelBound)).toHaveLength(1);
68
+ expect(getEventSequenceMetadata(ModelBound)).toBe('sequence');
69
+ expect(isPassive(ModelBound)).toBe(true);
70
+ expect(isNotRewindable(ModelBound)).toBe(true);
71
+ expect(getEventSequenceMetadata(EventLogModel)).toBeDefined();
72
+ expect(getGlobalForMetadata(GlobalHandler)?.identity).toBe(Upgraded);
73
+ expect(getVariantOfMetadata(Variant)?.identity).toBe(Upgraded);
74
+ expect(getSeederMetadata(Seeder)?.isSeeder).toBe(true);
75
+ expect(getWebhookMetadata(Webhook)?.targetUrl.value).toBe('https://example.com/hooks');
76
+ });
77
+ });
@@ -8,6 +8,8 @@ import { EventTypeId } from './EventTypeId.js';
8
8
  import { EventTypeGeneration } from './EventTypeGeneration.js';
9
9
  import { DecoratorType, TypeDiscoverer, TypeIntrospector } from '../types/index.js';
10
10
  import { JsonSchema, JsonSchemaGenerator } from '../schemas/index.js';
11
+ import { ChronicleClassDecorator } from '../types/standardDecoratorMetadata.js';
12
+ import { createDeferredSchema } from '../schemas/createDeferredSchema.js';
11
13
 
12
14
  /** Metadata key used to store event type information on a class. */
13
15
  const EVENT_TYPE_METADATA_KEY = 'chronicle:eventType';
@@ -51,18 +53,18 @@ export interface EventTypeMetadata {
51
53
  * }
52
54
  * ```
53
55
  */
54
- export function eventType(): ClassDecorator;
55
- export function eventType(id: string): ClassDecorator;
56
- export function eventType(id: string, generation: number): ClassDecorator;
57
- export function eventType(id: string, generation: number, tombstone: boolean): ClassDecorator;
58
- export function eventType(generation: number): ClassDecorator;
59
- export function eventType(generation: number, tombstone: boolean): ClassDecorator;
60
- export function eventType(tombstone: boolean): ClassDecorator;
56
+ export function eventType(): ChronicleClassDecorator;
57
+ export function eventType(id: string): ChronicleClassDecorator;
58
+ export function eventType(id: string, generation: number): ChronicleClassDecorator;
59
+ export function eventType(id: string, generation: number, tombstone: boolean): ChronicleClassDecorator;
60
+ export function eventType(generation: number): ChronicleClassDecorator;
61
+ export function eventType(generation: number, tombstone: boolean): ChronicleClassDecorator;
62
+ export function eventType(tombstone: boolean): ChronicleClassDecorator;
61
63
  export function eventType(
62
64
  idOrGenerationOrTombstone?: string | number | boolean,
63
65
  generationOrTombstone?: number | boolean,
64
66
  tombstone: boolean = false
65
- ): ClassDecorator {
67
+ ): ChronicleClassDecorator {
66
68
  let id = '';
67
69
  let generation = EventTypeGeneration.firstValue;
68
70
  let isTombstone = false;
@@ -86,14 +88,23 @@ export function eventType(
86
88
  isTombstone = idOrGenerationOrTombstone;
87
89
  }
88
90
 
89
- return (target: object) => {
90
- const constructor = target as Function;
91
+ return (target: object, context?: ClassDecoratorContext) => {
92
+ let constructor = target as Function;
91
93
  const eventTypeId = new EventTypeId(id || constructor.name);
92
94
  const eventTypeInstance = new EventType(eventTypeId, new EventTypeGeneration(generation), isTombstone);
93
- const members = TypeIntrospector.getMembers(constructor);
94
- const metadata: EventTypeMetadata = {
95
+ const members = context?.kind === 'class' ? undefined : TypeIntrospector.getMembers(constructor);
96
+ const resolve = createDeferredSchema(() => constructor);
97
+ const metadata: EventTypeMetadata = context?.kind === 'class' ? {
95
98
  eventType: eventTypeInstance,
96
- members,
99
+ get members() {
100
+ return resolve().members;
101
+ },
102
+ get schema() {
103
+ return resolve().schema;
104
+ }
105
+ } : {
106
+ eventType: eventTypeInstance,
107
+ members: members!,
97
108
  schema: JsonSchemaGenerator.generate(constructor, members)
98
109
  };
99
110
  Reflect.defineMetadata(EVENT_TYPE_METADATA_KEY, metadata, target);
@@ -102,6 +113,13 @@ export function eventType(
102
113
  constructor as Constructor,
103
114
  eventTypeId.value
104
115
  );
116
+ context?.addInitializer(function () {
117
+ if (this !== constructor) {
118
+ constructor = this;
119
+ Reflect.defineMetadata(EVENT_TYPE_METADATA_KEY, metadata, constructor);
120
+ TypeDiscoverer.default.register(DecoratorType.EventType, constructor as Constructor, eventTypeId.value);
121
+ }
122
+ });
105
123
  };
106
124
  }
107
125
 
package/events/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
+ import '../types/standardDecoratorMetadata.js';
5
+
4
6
  export { EventType } from './EventType.js';
5
7
  export { EventTypeId } from './EventTypeId.js';
6
8
  export { EventTypeGeneration } from './EventTypeGeneration.js';
@@ -11,6 +13,7 @@ export type { CausationEntry } from './CausationEntry.js';
11
13
  export type { AppendedEvent } from './AppendedEvent.js';
12
14
  export type { IEventTypes } from './IEventTypes.js';
13
15
  export { EventTypes } from './EventTypes.js';
16
+ export { isRegisteredEvent } from './isRegisteredEvent.js';
14
17
  export { Tag } from './Tag.js';
15
18
  export { tag, tags, getTagsFor } from './tagDecorator.js';
16
19
  export { filterEventsByTag, getFilterTagsFor } from './filterEventsByTagDecorator.js';
@@ -0,0 +1,26 @@
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 { describe, expect, it } from 'vitest';
5
+ import type { IEventStore } from '../IEventStore.js';
6
+ import { eventType } from './eventTypeDecorator.js';
7
+ import { isRegisteredEvent } from './isRegisteredEvent.js';
8
+
9
+ class CurrentEvent {}
10
+ class UnregisteredEvent {}
11
+ eventType('c00f9f33-0ccb-4319-9def-b19ef26f0351')(CurrentEvent);
12
+ eventType('c00f9f33-0ccb-4319-9def-b19ef26f0352')(UnregisteredEvent);
13
+
14
+ const store = { eventTypes: { all: [CurrentEvent] } } as IEventStore;
15
+
16
+ describe('when classifying a command return value against a store', () => {
17
+ it('should accept an instance of the registered event', () => {
18
+ expect(isRegisteredEvent(store, new CurrentEvent())).toBe(true);
19
+ });
20
+
21
+ it('should reject decorated but unregistered types and ordinary payloads', () => {
22
+ expect(isRegisteredEvent(store, new UnregisteredEvent())).toBe(false);
23
+ expect(isRegisteredEvent(store, { event: new CurrentEvent() })).toBe(false);
24
+ expect(isRegisteredEvent(store, null)).toBe(false);
25
+ });
26
+ });
@@ -0,0 +1,12 @@
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 type { Constructor } from '@cratis/fundamentals';
5
+ import type { IEventStore } from '../IEventStore.js';
6
+ import { hasEventType } from './eventTypeDecorator.js';
7
+
8
+ /** Checks whether a value is an instance of a current event type registered in this store. */
9
+ export function isRegisteredEvent(store: Pick<IEventStore, 'eventTypes'>, value: unknown): value is object {
10
+ return typeof value === 'object' && value !== null &&
11
+ hasEventType(value.constructor) && store.eventTypes.all.includes(value.constructor as Constructor);
12
+ }
@@ -16,6 +16,11 @@ export class IdentityProvider implements IIdentityProvider {
16
16
  return this._storage.getStore() ?? Identity.system;
17
17
  }
18
18
 
19
+ /** Runs an async or synchronous operation as the given identity, restoring the caller's identity afterward. */
20
+ run<T>(identity: Identity, callback: () => T): T {
21
+ return this._storage.run(identity, callback);
22
+ }
23
+
19
24
  /** @inheritdoc */
20
25
  setCurrentIdentity(identity: Identity): void {
21
26
  this._storage.enterWith(identity);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "6.3.2",
3
+ "version": "6.5.0",
4
4
  "description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -162,6 +162,7 @@
162
162
  "build": "tsc -b",
163
163
  "clean": "rm -rf dist",
164
164
  "test": "vitest run",
165
+ "test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts",
165
166
  "test:native": "node ./scripts/check-native-esm.mjs"
166
167
  },
167
168
  "dependencies": {
@@ -178,6 +179,7 @@
178
179
  "@cratis/fundamentals": "7.19.2",
179
180
  "@types/node": "^26.5.1",
180
181
  "@typescript/native": "npm:typescript@^7.0.2",
182
+ "esbuild": "^0.28.2",
181
183
  "typescript": "npm:@typescript/typescript6@^6.0.2",
182
184
  "vite": "^8.3.0",
183
185
  "vitest": "^5.0.1"
@@ -3,6 +3,7 @@
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
7
 
7
8
  /** Metadata stored by the addFrom property decorator. */
8
9
  export interface AddFromMetadata {
@@ -20,14 +21,14 @@ const METADATA_KEY = 'chronicle:projection:addFrom';
20
21
  * @param eventPropertyName - Optional event property name. Defaults to the property name.
21
22
  * @returns A property decorator.
22
23
  */
23
- export function addFrom(eventType: Function, eventPropertyName?: string): PropertyDecorator {
24
- return (target: object, propertyKey: string | symbol) => {
24
+ export function addFrom(eventType: Function, eventPropertyName?: string): ChroniclePropertyDecorator {
25
+ return decorateProperty((target: object, propertyKey: string | symbol) => {
25
26
  const key = propertyKey.toString();
26
27
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
27
28
  const existing: AddFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
28
29
  const metadata: AddFromMetadata = { eventType, eventPropertyName };
29
30
  Reflect.defineMetadata(METADATA_KEY, [...existing, metadata], target, key);
30
- };
31
+ });
31
32
  }
32
33
 
33
34
  /**
@@ -37,5 +38,5 @@ export function addFrom(eventType: Function, eventPropertyName?: string): Proper
37
38
  * @returns An array of addFrom metadata entries.
38
39
  */
39
40
  export function getAddFromMetadata(target: object, propertyKey: string): AddFromMetadata[] {
40
- return Reflect.getMetadata(METADATA_KEY, target, propertyKey) ?? [];
41
+ return getPropertyMetadata<AddFromMetadata[]>(METADATA_KEY, target, propertyKey) ?? [];
41
42
  }
@@ -3,6 +3,7 @@
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
7
 
7
8
  /** Metadata stored by the childrenFrom property decorator. */
8
9
  export interface ChildrenFromMetadata {
@@ -31,14 +32,14 @@ export function childrenFrom(
31
32
  key?: string,
32
33
  identifiedBy?: string,
33
34
  parentKey?: string
34
- ): PropertyDecorator {
35
- return (target: object, propertyKey: string | symbol) => {
35
+ ): ChroniclePropertyDecorator {
36
+ return decorateProperty((target: object, propertyKey: string | symbol) => {
36
37
  const propKey = propertyKey.toString();
37
38
  TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
38
39
  const existing: ChildrenFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, propKey) ?? [];
39
40
  const metadata: ChildrenFromMetadata = { eventType, key, identifiedBy, parentKey };
40
41
  Reflect.defineMetadata(METADATA_KEY, [...existing, metadata], target, propKey);
41
- };
42
+ });
42
43
  }
43
44
 
44
45
  /**
@@ -48,5 +49,5 @@ export function childrenFrom(
48
49
  * @returns An array of childrenFrom metadata entries.
49
50
  */
50
51
  export function getChildrenFromMetadata(target: object, propertyKey: string): ChildrenFromMetadata[] {
51
- return Reflect.getMetadata(METADATA_KEY, target, propertyKey) ?? [];
52
+ return getPropertyMetadata<ChildrenFromMetadata[]>(METADATA_KEY, target, propertyKey) ?? [];
52
53
  }