@cratis/chronicle 6.16.1 → 6.18.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 (141) hide show
  1. package/artifacts/ActivatedArtifact.ts +5 -1
  2. package/artifacts/ArtifactCompletionFailed.ts +20 -0
  3. package/artifacts/ArtifactInvocationContext.ts +21 -0
  4. package/artifacts/for_withActivatedArtifact/when_completing/after_processing_failure.spec.ts +34 -0
  5. package/artifacts/index.ts +2 -0
  6. package/artifacts/withActivatedArtifact.ts +20 -3
  7. package/dist/artifacts/ActivatedArtifact.d.ts +4 -1
  8. package/dist/artifacts/ActivatedArtifact.d.ts.map +1 -1
  9. package/dist/artifacts/ArtifactCompletionFailed.d.ts +9 -0
  10. package/dist/artifacts/ArtifactCompletionFailed.d.ts.map +1 -0
  11. package/dist/artifacts/ArtifactCompletionFailed.js +19 -0
  12. package/dist/artifacts/ArtifactCompletionFailed.js.map +1 -0
  13. package/dist/artifacts/ArtifactInvocationContext.d.ts +18 -0
  14. package/dist/artifacts/ArtifactInvocationContext.d.ts.map +1 -0
  15. package/dist/artifacts/ArtifactInvocationContext.js +4 -0
  16. package/dist/artifacts/ArtifactInvocationContext.js.map +1 -0
  17. package/dist/artifacts/index.d.ts +2 -0
  18. package/dist/artifacts/index.d.ts.map +1 -1
  19. package/dist/artifacts/index.js +1 -0
  20. package/dist/artifacts/index.js.map +1 -1
  21. package/dist/artifacts/withActivatedArtifact.d.ts +2 -1
  22. package/dist/artifacts/withActivatedArtifact.d.ts.map +1 -1
  23. package/dist/artifacts/withActivatedArtifact.js +22 -3
  24. package/dist/artifacts/withActivatedArtifact.js.map +1 -1
  25. package/dist/projections/CompiledProjectionDefinitions.d.ts +6 -0
  26. package/dist/projections/CompiledProjectionDefinitions.d.ts.map +1 -1
  27. package/dist/projections/ProjectionCapabilityProvenance.d.ts +8 -0
  28. package/dist/projections/ProjectionCapabilityProvenance.d.ts.map +1 -0
  29. package/dist/projections/ProjectionCapabilityProvenance.js +4 -0
  30. package/dist/projections/ProjectionCapabilityProvenance.js.map +1 -0
  31. package/dist/projections/ProjectionDefinitionCompiler.d.ts +1 -0
  32. package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
  33. package/dist/projections/ProjectionDefinitionCompiler.js +130 -4
  34. package/dist/projections/ProjectionDefinitionCompiler.js.map +1 -1
  35. package/dist/projections/ProjectionEventSchema.d.ts +10 -0
  36. package/dist/projections/ProjectionEventSchema.d.ts.map +1 -0
  37. package/dist/projections/ProjectionEventSchema.js +4 -0
  38. package/dist/projections/ProjectionEventSchema.js.map +1 -0
  39. package/dist/projections/VariantReclassifier.d.ts +2 -0
  40. package/dist/projections/VariantReclassifier.d.ts.map +1 -1
  41. package/dist/projections/VariantReclassifier.js.map +1 -1
  42. package/dist/projections/captureProjectionProvenance.d.ts +4 -0
  43. package/dist/projections/captureProjectionProvenance.d.ts.map +1 -0
  44. package/dist/projections/captureProjectionProvenance.js +56 -0
  45. package/dist/projections/captureProjectionProvenance.js.map +1 -0
  46. package/dist/projections/declarative/AddChildBuilder.d.ts +13 -2
  47. package/dist/projections/declarative/AddChildBuilder.d.ts.map +1 -1
  48. package/dist/projections/declarative/AddChildBuilder.js +27 -1
  49. package/dist/projections/declarative/AddChildBuilder.js.map +1 -1
  50. package/dist/projections/declarative/FromBuilder.d.ts +4 -0
  51. package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
  52. package/dist/projections/declarative/FromBuilder.js +10 -2
  53. package/dist/projections/declarative/FromBuilder.js.map +1 -1
  54. package/dist/projections/declarative/IAddChildBuilder.d.ts +12 -0
  55. package/dist/projections/declarative/IAddChildBuilder.d.ts.map +1 -1
  56. package/dist/projections/declarative/IReadModelPropertiesBuilder.d.ts +16 -1
  57. package/dist/projections/declarative/IReadModelPropertiesBuilder.d.ts.map +1 -1
  58. package/dist/projections/declarative/JoinBuilder.d.ts +4 -0
  59. package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
  60. package/dist/projections/declarative/JoinBuilder.js +1 -2
  61. package/dist/projections/declarative/JoinBuilder.js.map +1 -1
  62. package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
  63. package/dist/projections/declarative/ProjectionBuilderCore.js +9 -1
  64. package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
  65. package/dist/projections/declarative/projectionBuilderProvenance.d.ts +16 -0
  66. package/dist/projections/declarative/projectionBuilderProvenance.d.ts.map +1 -0
  67. package/dist/projections/declarative/projectionBuilderProvenance.js +28 -0
  68. package/dist/projections/declarative/projectionBuilderProvenance.js.map +1 -0
  69. package/dist/projections/eventContractPath.d.ts +4 -0
  70. package/dist/projections/eventContractPath.d.ts.map +1 -0
  71. package/dist/projections/eventContractPath.js +7 -0
  72. package/dist/projections/eventContractPath.js.map +1 -0
  73. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
  74. package/dist/projections/modelBound/childrenAndNestedBuilder.js +41 -10
  75. package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
  76. package/dist/reactors/Reactors.d.ts.map +1 -1
  77. package/dist/reactors/Reactors.js +12 -3
  78. package/dist/reactors/Reactors.js.map +1 -1
  79. package/dist/reducers/Reducers.d.ts.map +1 -1
  80. package/dist/reducers/Reducers.js +12 -3
  81. package/dist/reducers/Reducers.js.map +1 -1
  82. package/dist/testing/projections/ProjectionCapabilities.d.ts +17 -0
  83. package/dist/testing/projections/ProjectionCapabilities.d.ts.map +1 -0
  84. package/dist/testing/projections/ProjectionCapabilities.js +233 -0
  85. package/dist/testing/projections/ProjectionCapabilities.js.map +1 -0
  86. package/dist/testing/projections/UnsupportedProjectionOperation.d.ts +11 -0
  87. package/dist/testing/projections/UnsupportedProjectionOperation.d.ts.map +1 -0
  88. package/dist/testing/projections/UnsupportedProjectionOperation.js +16 -0
  89. package/dist/testing/projections/UnsupportedProjectionOperation.js.map +1 -0
  90. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts +7 -0
  91. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts.map +1 -0
  92. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js +16 -0
  93. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js.map +1 -0
  94. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts +10 -0
  95. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts.map +1 -0
  96. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js +20 -0
  97. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js.map +1 -0
  98. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts +3 -0
  99. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts.map +1 -0
  100. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js +7 -0
  101. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js.map +1 -0
  102. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts +12 -0
  103. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts.map +1 -0
  104. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js +34 -0
  105. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js.map +1 -0
  106. package/dist/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +2 -2
  108. package/projections/CompiledProjectionDefinitions.ts +6 -0
  109. package/projections/ProjectionCapabilityProvenance.ts +10 -0
  110. package/projections/ProjectionDefinitionCompiler.registration.spec.ts +4 -2
  111. package/projections/ProjectionDefinitionCompiler.ts +127 -4
  112. package/projections/ProjectionEventSchema.ts +13 -0
  113. package/projections/Projections.childrenAndNested.spec.ts +250 -1
  114. package/projections/VariantReclassifier.ts +2 -0
  115. package/projections/captureProjectionProvenance.ts +50 -0
  116. package/projections/declarative/AddChildBuilder.ts +33 -2
  117. package/projections/declarative/FromBuilder.ts +18 -1
  118. package/projections/declarative/IAddChildBuilder.ts +14 -0
  119. package/projections/declarative/IReadModelPropertiesBuilder.ts +18 -1
  120. package/projections/declarative/JoinBuilder.ts +9 -1
  121. package/projections/declarative/ProjectionBuilderCore.ts +8 -1
  122. package/projections/declarative/addChildCompatibility.standard.spec.ts +80 -0
  123. package/projections/declarative/projectionBuilderProvenance.ts +40 -0
  124. package/projections/eventContextExpressions.spec.ts +20 -0
  125. package/projections/eventContractPath.ts +9 -0
  126. package/projections/modelBound/childrenAndNestedBuilder.ts +45 -11
  127. package/reactors/Reactors.ts +13 -3
  128. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +93 -2
  129. package/reducers/Reducers.ts +14 -3
  130. package/reducers/for_Reducers/when_delivering/with_activation.spec.ts +67 -1
  131. package/testing/projections/ProjectionCapabilities.ts +213 -0
  132. package/testing/projections/UnsupportedProjectionOperation.ts +16 -0
  133. package/testing/projections/for_ProjectionCapabilities/given/Changed.ts +12 -0
  134. package/testing/projections/for_ProjectionCapabilities/given/Model.ts +13 -0
  135. package/testing/projections/for_ProjectionCapabilities/given/Removed.ts +7 -0
  136. package/testing/projections/for_ProjectionCapabilities/given/compile.ts +39 -0
  137. package/testing/projections/for_ProjectionCapabilities/when_compiling_capability_evidence.spec.ts +68 -0
  138. package/testing/projections/for_ProjectionCapabilities/when_rejecting_unsupported_operations.spec.ts +230 -0
  139. package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts +73 -0
  140. package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts +118 -0
  141. package/testing/projections/for_ProjectionCapabilities/when_validating_supported_operations.spec.ts +103 -0
@@ -5,6 +5,7 @@ import { AutoMap, ReadModelObserverType, type ProjectionDefinition } from '@crat
5
5
  import { Constructor } from '@cratis/fundamentals';
6
6
  import { IClientArtifactsProvider } from '../artifacts/index.js';
7
7
  import { EventSequenceId } from '../eventSequences/EventSequenceId.js';
8
+ import { getEventTypeFor, getEventTypeJsonSchemaFor } from '../events/eventTypeDecorator.js';
8
9
  import { getReadModelMetadata } from '../readModels/index.js';
9
10
  import { getReadModelId } from '../readModels/readModel.js';
10
11
  import { buildReadModelDefinition } from '../readModels/buildReadModelDefinition.js';
@@ -14,6 +15,11 @@ import { WellKnownSinks } from '../sinks/index.js';
14
15
  import { TypeIntrospector } from '../types/index.js';
15
16
  import { canonicalStringify } from './canonicalStringify.js';
16
17
  import { CompiledProjectionDefinitions } from './CompiledProjectionDefinitions.js';
18
+ import { captureProjectionProvenance } from './captureProjectionProvenance.js';
19
+ import { eventContractPath } from './eventContractPath.js';
20
+ import { getProjectionBuilderProvenance } from './declarative/projectionBuilderProvenance.js';
21
+ import type { ProjectionCapabilityProvenance } from './ProjectionCapabilityProvenance.js';
22
+ import type { ProjectionEventSchema } from './ProjectionEventSchema.js';
17
23
  import { constantValueExpression } from './constantValueExpression.js';
18
24
  import { eventContextPropertyExpression } from './eventContextPropertyExpression.js';
19
25
  import { InvalidEventContextPropertyError } from './InvalidEventContextPropertyError.js';
@@ -42,6 +48,14 @@ import { getRemovedWithJoinClassMetadata, getRemovedWithJoinPropertyMetadata } f
42
48
  import { getVariantOfMetadata } from './modelBound/variantOf.js';
43
49
  import { getEntersOnMetadata } from './modelBound/entersOn.js';
44
50
  import { getGlobalForMetadata } from './modelBound/globalFor.js';
51
+ import { getSetFromMetadata } from './modelBound/setFrom.js';
52
+ import { getSetFromContextMetadata } from './modelBound/setFromContext.js';
53
+ import { getSetValueMetadata } from './modelBound/setValue.js';
54
+ import { getAddFromMetadata } from './modelBound/addFrom.js';
55
+ import { getSubtractFromMetadata } from './modelBound/subtractFrom.js';
56
+ import { getCountMetadata } from './modelBound/count.js';
57
+ import { getIncrementMetadata } from './modelBound/increment.js';
58
+ import { getDecrementMetadata } from './modelBound/decrement.js';
45
59
  import type { JoinRecord } from './declarative/ProjectionBuilderCore.js';
46
60
  import { BuiltProjection, crossWireGroups, mergeGlobalHandlers, reclassify, VariantDeclaration } from './VariantReclassifier.js';
47
61
 
@@ -79,12 +93,53 @@ export class ProjectionDefinitionCompiler {
79
93
  // existing sparse wire objects: adding those default-valued fields would change the payload.
80
94
  // Evaluators must tolerate absent optional contract fields (for example NoAutoMapProperties).
81
95
  const definitions = builtProjections.map(built => built.definition as unknown as ProjectionDefinition);
96
+ const provenance = new Map<ProjectionDefinition, readonly ProjectionCapabilityProvenance[]>();
97
+ const eventSchemas = new Map<ProjectionDefinition, ReadonlyMap<string, ProjectionEventSchema>>();
98
+ const availableEvents = new Map<string, Constructor>(this._clientArtifacts.eventTypes.map(type => {
99
+ const event = getEventTypeFor(type);
100
+ return [`${event.id.value}:${event.generation.value}`, type] as const;
101
+ }));
102
+ for (let index = 0; index < definitions.length; index++) {
103
+ provenance.set(definitions[index], builtProjections[index].provenance ?? []);
104
+ eventSchemas.set(definitions[index], this.buildEventSchemaCatalog(definitions[index], availableEvents));
105
+ }
82
106
  return {
83
107
  definitions,
84
- readModels: this.buildReadModelDefinitions(definitions)
108
+ readModels: this.buildReadModelDefinitions(definitions),
109
+ provenance,
110
+ eventSchemas
85
111
  };
86
112
  }
87
113
 
114
+ private buildEventSchemaCatalog(definition: ProjectionDefinition, availableEvents: ReadonlyMap<string, Constructor>): ReadonlyMap<string, ProjectionEventSchema> {
115
+ const catalog = new Map<string, ProjectionEventSchema>();
116
+ const collect = (node: Record<string, unknown>) => {
117
+ for (const section of ['From', 'Join', 'RemovedWith', 'RemovedWithJoin'] as const) {
118
+ for (const entry of node[section] as Array<{ Key: ContractEventType }> ?? []) {
119
+ const eventType = entry.Key;
120
+ const key = getEventTypeMapKey(eventType);
121
+ const type = availableEvents.get(`${eventType.Id}:${eventType.Generation}`);
122
+ if (type && !catalog.has(key)) {
123
+ // Registration uses this exact schema path. Clone it so a compile or consumer
124
+ // cannot mutate metadata or another compile's catalog.
125
+ catalog.set(key, { eventType: { ...eventType }, schema: structuredClone(getEventTypeJsonSchemaFor(type)) });
126
+ }
127
+ }
128
+ }
129
+ const fromEventProperty = node.FromEventProperty as { Event?: ContractEventType } | undefined;
130
+ if (fromEventProperty?.Event) {
131
+ const key = getEventTypeMapKey(fromEventProperty.Event);
132
+ const type = availableEvents.get(`${fromEventProperty.Event.Id}:${fromEventProperty.Event.Generation}`);
133
+ if (type && !catalog.has(key)) catalog.set(key, { eventType: { ...fromEventProperty.Event }, schema: structuredClone(getEventTypeJsonSchemaFor(type)) });
134
+ }
135
+ for (const section of ['Children', 'Nested'] as const) {
136
+ for (const child of Object.values(node[section] as Record<string, Record<string, unknown>> ?? {})) collect(child);
137
+ }
138
+ };
139
+ collect(definition as unknown as Record<string, unknown>);
140
+ return catalog;
141
+ }
142
+
88
143
  private buildWithContextValidation(type: Constructor, build: () => BuiltProjection, readModelType?: Constructor): BuiltProjection {
89
144
  try {
90
145
  return build();
@@ -165,6 +220,12 @@ export class ProjectionDefinitionCompiler {
165
220
  }
166
221
  }
167
222
 
223
+ const declarations = getProjectionBuilderProvenance(builder);
224
+ const provenance = captureProjectionProvenance(definition, false,
225
+ new Map([...declarations.keys, ...declarations.children]));
226
+ if (declarations.fromEvery && !provenance.some(entry => entry.contractPath === 'All')) {
227
+ provenance.push({ contractPath: 'All', declaration: '.fromEvery' });
228
+ }
168
229
  let variant: VariantDeclaration | undefined;
169
230
  const variantDeclaration = builder.getVariantDeclaration();
170
231
  if (variantDeclaration) {
@@ -174,8 +235,11 @@ export class ProjectionDefinitionCompiler {
174
235
  definition.From = reclassified.from;
175
236
  definition.Join = reclassified.join;
176
237
  variant = variantDeclaration;
238
+ provenance.push({ contractPath: 'Variant', declaration: '.variantOf' });
239
+ provenance.push({ contractPath: 'Variant.Key', declaration: '.variantOf' });
240
+ for (const entering of variant.enteringEventTypes) provenance.push({ contractPath: eventContractPath('EntersOn', entering), declaration: '.entersOn' });
177
241
  }
178
- return { typeName: type.name, definition, variant };
242
+ return { typeName: type.name, definition, variant, provenance };
179
243
  }
180
244
 
181
245
  private inferReadModelIdentifier(mappedProperties: string[]): string | undefined {
@@ -203,6 +267,16 @@ export class ProjectionDefinitionCompiler {
203
267
  const eventSequenceId = getEventSequenceMetadata(type);
204
268
  const properties = TypeIntrospector.getTrackedProperties(type);
205
269
  const prototype = type.prototype;
270
+ const overrides = new Map<string, string>();
271
+ const declaredFrom = new Set(getFromEventMetadata(type).map(entry => getEventTypeMapKey(toContractEventType(entry.eventType))));
272
+ const recordMappings = (property: string, metadata: Array<{ eventType: Function }>, declaration: string) => {
273
+ for (const mapping of metadata) {
274
+ const eventType = toContractEventType(mapping.eventType);
275
+ const path = eventContractPath('From', eventType);
276
+ overrides.set(`${path}.Properties.${property}`, declaration);
277
+ if (!declaredFrom.has(getEventTypeMapKey(eventType)) && !overrides.has(path)) overrides.set(path, declaration);
278
+ }
279
+ };
206
280
 
207
281
  const fromByEventType = new Map<string, FromRecord>();
208
282
  const joinByEventType = new Map<string, { Key: ContractEventType; Value: { On: string; Properties: Record<string, string>; Key: string } }>();
@@ -244,6 +318,23 @@ export class ProjectionDefinitionCompiler {
244
318
  }
245
319
 
246
320
  for (const property of properties) {
321
+ recordMappings(property, getSetFromMetadata(prototype, property), '@setFrom');
322
+ recordMappings(property, getSetFromContextMetadata(prototype, property), '@setFromContext');
323
+ recordMappings(property, getSetValueMetadata(prototype, property), '@setValue');
324
+ recordMappings(property, getAddFromMetadata(prototype, property), '@addFrom');
325
+ recordMappings(property, getSubtractFromMetadata(prototype, property), '@subtractFrom');
326
+ recordMappings(property, getIncrementMetadata(prototype, property), '@increment');
327
+ recordMappings(property, getDecrementMetadata(prototype, property), '@decrement');
328
+ recordMappings(property, getCountMetadata(prototype, property), '@count');
329
+ for (const [declaration, mappings] of [
330
+ ['@increment', getIncrementMetadata(prototype, property)],
331
+ ['@decrement', getDecrementMetadata(prototype, property)],
332
+ ['@count', getCountMetadata(prototype, property)]
333
+ ] as const) {
334
+ for (const mapping of mappings) {
335
+ if (mapping.constantKey) overrides.set(`${eventContractPath('From', toContractEventType(mapping.eventType))}.Key`, declaration);
336
+ }
337
+ }
247
338
  applyPropertyMappings(prototype, property, fromByEventType);
248
339
  for (const mapping of getJoinMetadata(prototype, property)) {
249
340
  const entry = this.ensureJoinEntry(joinByEventType, mapping.eventType);
@@ -278,6 +369,7 @@ export class ProjectionDefinitionCompiler {
278
369
  for (const clearWith of getClearWithPropertyMetadata(prototype, property)) {
279
370
  const entry = ensureFromEntry(fromByEventType, clearWith.eventType);
280
371
  entry.Value.Properties[property] = '$null';
372
+ overrides.set(`${eventContractPath('From', toContractEventType(clearWith.eventType))}.Properties.${property}`, '@clearWith');
281
373
  }
282
374
  }
283
375
  }
@@ -294,6 +386,8 @@ export class ProjectionDefinitionCompiler {
294
386
 
295
387
  let from = Array.from(fromByEventType.values());
296
388
  let join = Array.from(joinByEventType.values());
389
+ let preLoweringFrom = from;
390
+ let preLoweringJoin = join;
297
391
  let variant: VariantDeclaration | undefined;
298
392
  const variantMetadata = getVariantOfMetadata(type);
299
393
  if (variantMetadata) {
@@ -303,6 +397,7 @@ export class ProjectionDefinitionCompiler {
303
397
  if (entersOn.key) {
304
398
  const entry = ensureFromEntry(fromByEventType, entersOn.eventType);
305
399
  entry.Value.Key = entersOn.key;
400
+ overrides.set(`${eventContractPath('From', contractType)}.Key`, '@entersOn');
306
401
  }
307
402
  return contractType;
308
403
  });
@@ -311,11 +406,21 @@ export class ProjectionDefinitionCompiler {
311
406
  for (const handlerType of this._clientArtifacts.globalForHandlers) {
312
407
  const globalForMetadata = getGlobalForMetadata(handlerType);
313
408
  if (globalForMetadata?.identity === variantMetadata.identity) {
314
- globalHandlers.set(handlerType.name, this.buildFromRecordsForType(handlerType));
409
+ const records = this.buildFromRecordsForType(handlerType);
410
+ globalHandlers.set(handlerType.name, records);
411
+ for (const record of records) {
412
+ const path = eventContractPath('From', record.Key);
413
+ overrides.set(path, `@globalFor(${handlerType.name})`);
414
+ for (const property of Object.keys(record.Value.Properties)) {
415
+ overrides.set(`${path}.Properties.${property}`, `@globalFor(${handlerType.name})`);
416
+ }
417
+ }
315
418
  }
316
419
  }
317
420
  const memberNames = new Set(getReadModelMetadata(type)?.members.keys() ?? TypeIntrospector.getMembers(type).keys());
318
421
  const merged = mergeGlobalHandlers(type.name, memberNames, from, globalHandlers);
422
+ preLoweringFrom = merged;
423
+ preLoweringJoin = join;
319
424
  const reclassified = reclassify(type.name, merged, join, enteringEventTypes, variantMetadata.key);
320
425
  from = reclassified.from;
321
426
  join = reclassified.join;
@@ -346,7 +451,25 @@ export class ProjectionDefinitionCompiler {
346
451
  NoAutoMapProperties: properties.filter(property => isPropertyNoAutoMap(prototype, property)),
347
452
  Nested: nestedByProperty
348
453
  };
349
- return { typeName: type.name, definition, variant };
454
+ const provenance = captureProjectionProvenance({ ...definition, From: preLoweringFrom, Join: preLoweringJoin }, true, overrides);
455
+ if (Object.keys(allProperties).length && Object.keys(allProperties).every(property =>
456
+ getFromAllMetadata(prototype, property) && !getFromEveryMetadata(prototype, property))) {
457
+ const index = provenance.findIndex(entry => entry.contractPath === 'All');
458
+ if (index >= 0) provenance[index] = { contractPath: 'All', declaration: '@fromAll' };
459
+ }
460
+ for (const property of Object.keys(allProperties)) {
461
+ if (getFromAllMetadata(prototype, property) && !getFromEveryMetadata(prototype, property)) {
462
+ const path = `All.Properties.${property}`;
463
+ const index = provenance.findIndex(entry => entry.contractPath === path);
464
+ if (index >= 0) provenance[index] = { contractPath: path, declaration: '@fromAll' };
465
+ }
466
+ }
467
+ if (variant) {
468
+ provenance.push({ contractPath: 'Variant', declaration: '@variantOf' });
469
+ provenance.push({ contractPath: 'Variant.Key', declaration: '@variantOf' });
470
+ for (const entering of variant.enteringEventTypes) provenance.push({ contractPath: eventContractPath('EntersOn', entering), declaration: '@entersOn' });
471
+ }
472
+ return { typeName: type.name, definition, variant, provenance };
350
473
  }
351
474
 
352
475
  /**
@@ -0,0 +1,13 @@
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 { JsonSchema } from '../schemas/JsonSchema.js';
5
+ import type { ContractEventType } from './declarative/ProjectionBuilderCore.js';
6
+
7
+ /** Schema registered for one participating event type and generation. */
8
+ export interface ProjectionEventSchema {
9
+ /** Event identifier, generation, and tombstone status. */
10
+ readonly eventType: ContractEventType;
11
+ /** Isolated copy of the schema registered for this event. */
12
+ readonly schema: JsonSchema;
13
+ }
@@ -10,7 +10,10 @@ import { ChronicleConnection } from '../connection/index.js';
10
10
  import { eventType } from '../events/eventTypeDecorator.js';
11
11
  import { childrenFrom } from './modelBound/childrenFrom.js';
12
12
  import { clearWith } from './modelBound/clearWith.js';
13
+ import { count } from './modelBound/count.js';
14
+ import { decrement } from './modelBound/decrement.js';
13
15
  import { fromEvent } from './modelBound/fromEvent.js';
16
+ import { increment } from './modelBound/increment.js';
14
17
  import { nested } from './modelBound/nested.js';
15
18
  import { noAutoMap } from './modelBound/noAutoMap.js';
16
19
  import { setFrom } from './modelBound/setFrom.js';
@@ -22,6 +25,7 @@ import { Projections } from './Projections.js';
22
25
  class LineAdded {
23
26
  productId!: string;
24
27
  quantity!: number;
28
+ label!: string;
25
29
  }
26
30
  eventType()(LineAdded);
27
31
 
@@ -30,6 +34,11 @@ class LineQuantityChanged {
30
34
  }
31
35
  eventType()(LineQuantityChanged);
32
36
 
37
+ class AlternateLineAdded {
38
+ alternateId!: string;
39
+ }
40
+ eventType()(AlternateLineAdded);
41
+
33
42
  class LineQuantityCleared {}
34
43
  eventType()(LineQuantityCleared);
35
44
 
@@ -71,6 +80,117 @@ setFrom(LineQuantityChanged)(OrderLine.prototype, 'quantity');
71
80
  noAutoMap(OrderLine.prototype, 'quantity');
72
81
  clearWith(LineQuantityCleared)(OrderLine.prototype, 'quantity');
73
82
 
83
+ class ChildWithId {
84
+ id = '';
85
+ label = '';
86
+ }
87
+ class ChildWithUppercaseId {
88
+ Id = '';
89
+ }
90
+ class ChildWithExplicitId {
91
+ employeeNumber = '';
92
+ label = '';
93
+ }
94
+ class ChildWithMatchingEventKey {
95
+ productId = '';
96
+ }
97
+ class ChildWithMappedId {
98
+ id = '';
99
+ }
100
+ setFrom(LineAdded, 'productId')(ChildWithMappedId.prototype, 'id');
101
+ class ChildWithoutAutoMap {
102
+ id = '';
103
+ }
104
+ noAutoMap(ChildWithoutAutoMap);
105
+ class ChildWithNoAutoMappedId {
106
+ id = '';
107
+ }
108
+ noAutoMap(ChildWithNoAutoMappedId.prototype, 'id');
109
+ class ChildWithConstantCount {
110
+ id = '';
111
+ total = 0;
112
+ label = '';
113
+ }
114
+ count(LineAdded, 'all')(ChildWithConstantCount.prototype, 'total');
115
+ class ChildWithConstantIncrement {
116
+ id = '';
117
+ total = 0;
118
+ label = '';
119
+ }
120
+ increment(LineAdded, 'all')(ChildWithConstantIncrement.prototype, 'total');
121
+ class ChildWithConstantDecrement {
122
+ id = '';
123
+ total = 0;
124
+ label = '';
125
+ }
126
+ decrement(LineAdded, 'all')(ChildWithConstantDecrement.prototype, 'total');
127
+ class ChildWithPlainCount {
128
+ id = '';
129
+ total = 0;
130
+ label = '';
131
+ }
132
+ count(LineAdded)(ChildWithPlainCount.prototype, 'total');
133
+ class ChildWithMappedLabelAndCount {
134
+ id = '';
135
+ total = 0;
136
+ label = '';
137
+ }
138
+ count(LineAdded)(ChildWithMappedLabelAndCount.prototype, 'total');
139
+ setFrom(LineAdded)(ChildWithMappedLabelAndCount.prototype, 'label');
140
+ class ChildWithEmptyKey {
141
+ [''] = '';
142
+ }
143
+ class ChildWithoutMatchingEmptyKey {
144
+ label = '';
145
+ }
146
+ class IdentifiedChildren {
147
+ byConvention!: ChildWithId[];
148
+ byUppercaseId!: ChildWithUppercaseId[];
149
+ byExplicitId!: ChildWithExplicitId[];
150
+ byExplicitIdWithoutChildType!: ChildWithId[];
151
+ byInferredEventKey!: ChildWithMatchingEventKey[];
152
+ byEventKey!: ChildWithId[];
153
+ byMatchingKey!: ChildWithId[];
154
+ withExplicitMapping!: ChildWithMappedId[];
155
+ withoutAutoMap!: ChildWithoutAutoMap[];
156
+ withNoAutoMappedId!: ChildWithNoAutoMappedId[];
157
+ withDifferentEventKeys!: ChildWithId[];
158
+ withConstantCount!: ChildWithConstantCount[];
159
+ withConstantIncrement!: ChildWithConstantIncrement[];
160
+ withConstantDecrement!: ChildWithConstantDecrement[];
161
+ withPlainCount!: ChildWithPlainCount[];
162
+ withMappedLabelAndCount!: ChildWithMappedLabelAndCount[];
163
+ withEmptyEventKey!: ChildWithEmptyKey[];
164
+ withoutMatchingEmptyKey!: ChildWithoutMatchingEmptyKey[];
165
+ }
166
+ childrenFrom(LineAdded, ChildWithId)(IdentifiedChildren.prototype, 'byConvention');
167
+ childrenFrom(LineAdded, ChildWithUppercaseId)(IdentifiedChildren.prototype, 'byUppercaseId');
168
+ childrenFrom(LineAdded, ChildWithExplicitId, 'employeeNumber')(IdentifiedChildren.prototype, 'byExplicitId');
169
+ childrenFrom(LineAdded, undefined, 'id')(IdentifiedChildren.prototype, 'byExplicitIdWithoutChildType');
170
+ childrenFrom(LineAdded, 'productId')(IdentifiedChildren.prototype, 'byInferredEventKey');
171
+ childrenFrom(LineAdded, 'productId', 'id')(IdentifiedChildren.prototype, 'byEventKey');
172
+ childrenFrom(LineAdded, 'id', 'id')(IdentifiedChildren.prototype, 'byMatchingKey');
173
+ childrenFrom(LineAdded, ChildWithMappedId)(IdentifiedChildren.prototype, 'withExplicitMapping');
174
+ childrenFrom(LineAdded, ChildWithoutAutoMap)(IdentifiedChildren.prototype, 'withoutAutoMap');
175
+ childrenFrom(LineAdded, ChildWithNoAutoMappedId)(IdentifiedChildren.prototype, 'withNoAutoMappedId');
176
+ childrenFrom(LineAdded, 'productId')(IdentifiedChildren.prototype, 'withDifferentEventKeys');
177
+ childrenFrom(AlternateLineAdded, 'alternateId')(IdentifiedChildren.prototype, 'withDifferentEventKeys');
178
+ childrenFrom(LineAdded, ChildWithConstantCount)(IdentifiedChildren.prototype, 'withConstantCount');
179
+ childrenFrom(LineAdded, 'productId')(IdentifiedChildren.prototype, 'withConstantIncrement');
180
+ field(Array, { enumerable: true, genericArguments: [ChildWithConstantIncrement] })(IdentifiedChildren.prototype, 'withConstantIncrement');
181
+ childrenFrom(LineAdded, ChildWithConstantDecrement)(IdentifiedChildren.prototype, 'withConstantDecrement');
182
+ childrenFrom(LineAdded, ChildWithPlainCount)(IdentifiedChildren.prototype, 'withPlainCount');
183
+ childrenFrom(LineAdded, ChildWithMappedLabelAndCount)(IdentifiedChildren.prototype, 'withMappedLabelAndCount');
184
+ childrenFrom(LineAdded, '')(IdentifiedChildren.prototype, 'withEmptyEventKey');
185
+ childrenFrom(LineAdded, '')(IdentifiedChildren.prototype, 'withoutMatchingEmptyKey');
186
+ field(Array, { enumerable: true, genericArguments: [ChildWithMatchingEventKey] })(IdentifiedChildren.prototype, 'byInferredEventKey');
187
+ field(Array, { enumerable: true, genericArguments: [ChildWithId] })(IdentifiedChildren.prototype, 'byEventKey');
188
+ field(Array, { enumerable: true, genericArguments: [ChildWithId] })(IdentifiedChildren.prototype, 'byMatchingKey');
189
+ field(Array, { enumerable: true, genericArguments: [ChildWithId] })(IdentifiedChildren.prototype, 'withDifferentEventKeys');
190
+ field(Array, { enumerable: true, genericArguments: [ChildWithEmptyKey] })(IdentifiedChildren.prototype, 'withEmptyEventKey');
191
+ field(Array, { enumerable: true, genericArguments: [ChildWithoutMatchingEmptyKey] })(IdentifiedChildren.prototype, 'withoutMatchingEmptyKey');
192
+ fromEvent(OrderCreated)(IdentifiedChildren);
193
+
74
194
  class OrderSummary {
75
195
  total!: number;
76
196
  }
@@ -140,10 +260,11 @@ noAutoMap(OrderWithoutAutoMap);
140
260
  fromEvent(OrderCreated)(OrderWithoutAutoMap);
141
261
 
142
262
  class Grandchild {
143
- id!: string;
263
+ id = '';
144
264
  }
145
265
 
146
266
  class ChildWithGrandchildren {
267
+ id = '';
147
268
  grandchildren!: Grandchild[];
148
269
  detail!: Grandchild | undefined;
149
270
  }
@@ -248,6 +369,130 @@ describe('Projections with childrenFrom, nested and clearWith', () => {
248
369
  expect(updateEntry.Value.Properties.quantity).toBe('quantity');
249
370
  });
250
371
 
372
+ it('should map a convention or explicit child identifier from the creating event source id', async () => {
373
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
374
+ await projections.register();
375
+
376
+ const children = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children;
377
+ expect(children.byConvention.IdentifiedBy).toBe('id');
378
+ expect(children.byConvention.From[0].Value.Properties).toEqual({ id: '$eventContext(EventSourceId)' });
379
+ expect(children.byUppercaseId.IdentifiedBy).toBe('Id');
380
+ expect(children.byUppercaseId.From[0].Value.Properties).toEqual({ Id: '$eventContext(EventSourceId)' });
381
+ expect(children.byExplicitId.IdentifiedBy).toBe('employeeNumber');
382
+ expect(children.byExplicitId.From[0].Value.Properties).toEqual({ employeeNumber: '$eventContext(EventSourceId)' });
383
+ });
384
+
385
+ it('should map an explicit identifier without child-type metadata from the creating event source id', async () => {
386
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
387
+ await projections.register();
388
+
389
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.byExplicitIdWithoutChildType;
390
+ expect(child.IdentifiedBy).toBe('id');
391
+ expect(child.From[0].Value.Properties).toEqual({ id: '$eventContext(EventSourceId)' });
392
+ });
393
+
394
+ it('should map the child identifier from an explicit event key when it differs from the property name', async () => {
395
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
396
+ await projections.register();
397
+
398
+ const children = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children;
399
+ expect(children.byEventKey.From[0].Value.Properties.id).toBe('productId');
400
+ expect(children.byInferredEventKey.IdentifiedBy).toBe('productId');
401
+ expect(children.byInferredEventKey.From[0].Value.Properties).toEqual({});
402
+ expect(children.byMatchingKey.From[0].Value.Properties).toEqual({});
403
+ });
404
+
405
+ it('should leave explicitly mapped identifiers and class-level disabled auto-mapping unchanged', async () => {
406
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
407
+ await projections.register();
408
+
409
+ const children = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children;
410
+ expect(children.withExplicitMapping.From[0].Value.Properties).toEqual({ id: 'productId' });
411
+ expect(children.withoutAutoMap.AutoMap).toBe(AutoMap.Disabled);
412
+ expect(children.withoutAutoMap.From[0].Value.Properties).toEqual({});
413
+ });
414
+
415
+ it('should map an identifier with property-level noAutoMap from the creating event source id', async () => {
416
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
417
+ await projections.register();
418
+
419
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withNoAutoMappedId;
420
+ expect(child.AutoMap).toBe(AutoMap.Enabled);
421
+ expect(child.NoAutoMapProperties).toContain('id');
422
+ expect(child.From[0].Value.Properties.id).toBe('$eventContext(EventSourceId)');
423
+ });
424
+
425
+ it('should map each creating event using its own key on a shared children collection', async () => {
426
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
427
+ await projections.register();
428
+
429
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withDifferentEventKeys;
430
+ expect(child.IdentifiedBy).toBe('id');
431
+ expect(child.From).toHaveLength(2);
432
+ expect(child.From.find(candidate => candidate.Key.Id === 'LineAdded')?.Value).toEqual({
433
+ Key: 'productId', ParentKey: '$eventSourceId', Properties: { id: 'productId' }
434
+ });
435
+ expect(child.From.find(candidate => candidate.Key.Id === 'AlternateLineAdded')?.Value).toEqual({
436
+ Key: 'alternateId', ParentKey: '$eventSourceId', Properties: { id: 'alternateId' }
437
+ });
438
+ });
439
+
440
+ it('should preserve aggregate-only child count, increment and decrement with constant keys', async () => {
441
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
442
+ await projections.register();
443
+
444
+ const children = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children;
445
+ for (const [name, operation] of [
446
+ ['withConstantCount', '$count'],
447
+ ['withConstantIncrement', '$increment'],
448
+ ['withConstantDecrement', '$decrement']
449
+ ]) {
450
+ const child = children[name];
451
+ expect(child.IdentifiedBy).toBe('id');
452
+ expect(child.From[0].Value).toEqual({
453
+ Key: '$value(all)', ParentKey: '$eventSourceId', Properties: { total: operation }
454
+ });
455
+ }
456
+ });
457
+
458
+ it('should preserve aggregate-only child count without a constant key', async () => {
459
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
460
+ await projections.register();
461
+
462
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withPlainCount;
463
+ expect(child.IdentifiedBy).toBe('id');
464
+ expect(child.From[0].Value).toEqual({
465
+ Key: '$eventSourceId', ParentKey: '$eventSourceId', Properties: { total: '$count' }
466
+ });
467
+ });
468
+
469
+ it('should still map the identifier when a child count also explicitly maps a label', async () => {
470
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
471
+ await projections.register();
472
+
473
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withMappedLabelAndCount;
474
+ expect(child.From[0].Value.Properties).toEqual({ total: '$count', label: 'label', id: '$eventContext(EventSourceId)' });
475
+ });
476
+
477
+ it('should discover a child property matching an explicitly empty event key', async () => {
478
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
479
+ await projections.register();
480
+
481
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withEmptyEventKey;
482
+ expect(child.IdentifiedBy).toBe('');
483
+ expect(child.From[0].Value.Key).toBe('');
484
+ expect(child.From[0].Value.Properties).toEqual({});
485
+ });
486
+
487
+ it('should not infer an identifier from an empty event key without a matching child property', async () => {
488
+ const { projections, registerMock } = createProjections([IdentifiedChildren]);
489
+ await projections.register();
490
+
491
+ const child = (registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition).Children.withoutMatchingEmptyKey;
492
+ expect(child.IdentifiedBy).toBe('$eventSourceId');
493
+ expect(child.From[0].Value.Key).toBe('');
494
+ });
495
+
251
496
  it('should clear only the child member through a From mapping', async () => {
252
497
  const { projections, registerMock } = createProjections([Order]);
253
498
  await projections.register();
@@ -324,6 +569,7 @@ describe('Projections with childrenFrom, nested and clearWith', () => {
324
569
  const child = definition.Children.children;
325
570
  expect(child.AutoMap).toBe(AutoMap.Disabled);
326
571
  expect(child.Children.grandchildren.AutoMap).toBe(AutoMap.Disabled);
572
+ expect(child.Children.grandchildren.From[0].Value.Properties).toEqual({});
327
573
  expect(child.Nested.detail.AutoMap).toBe(AutoMap.Disabled);
328
574
  });
329
575
 
@@ -334,7 +580,10 @@ describe('Projections with childrenFrom, nested and clearWith', () => {
334
580
  const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
335
581
  const child = definition.Children.children;
336
582
  expect(child.AutoMap).toBe(AutoMap.Disabled);
583
+ expect(child.From[0].Value.Properties).toEqual({});
337
584
  expect(child.Children.grandchildren.AutoMap).toBe(AutoMap.Enabled);
585
+ expect(child.Children.grandchildren.IdentifiedBy).toBe('id');
586
+ expect(child.Children.grandchildren.From[0].Value.Properties.id).toBe('$eventContext(EventSourceId)');
338
587
  expect(child.Nested.detail.AutoMap).toBe(AutoMap.Enabled);
339
588
  });
340
589
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { ContractEventType, FromRecord, JoinRecord } from './declarative/ProjectionBuilderCore.js';
5
5
  import { GlobalHandlerPropertyNotOnVariant } from './GlobalHandlerPropertyNotOnVariant.js';
6
+ import type { ProjectionCapabilityProvenance } from './ProjectionCapabilityProvenance.js';
6
7
  import { VariantMustDeclareEntersOnEvent } from './VariantMustDeclareEntersOnEvent.js';
7
8
 
8
9
  const EVENT_SOURCE_ID_KEY = '$eventSourceId';
@@ -28,6 +29,7 @@ export interface BuiltProjection {
28
29
  readonly typeName: string;
29
30
  readonly definition: Record<string, unknown>;
30
31
  readonly variant?: VariantDeclaration;
32
+ readonly provenance?: ProjectionCapabilityProvenance[];
31
33
  }
32
34
 
33
35
  function eventTypeKey(eventType: ContractEventType): string {
@@ -0,0 +1,50 @@
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 { ProjectionCapabilityProvenance } from './ProjectionCapabilityProvenance.js';
5
+ import type { ContractEventType } from './declarative/ProjectionBuilderCore.js';
6
+ import { eventContractPath } from './eventContractPath.js';
7
+
8
+ /** Captures pre-lowering declarations without changing the registration contract. */
9
+ export function captureProjectionProvenance(definition: Record<string, unknown>, modelBound: boolean, overrides: ReadonlyMap<string, string> = new Map()): ProjectionCapabilityProvenance[] {
10
+ const result: ProjectionCapabilityProvenance[] = [];
11
+ const add = (contractPath: string, declaration: string) => result.push({ contractPath, declaration: overrides.get(contractPath) ?? declaration });
12
+ const prefix = modelBound ? '@' : '.';
13
+ const from = definition.From as Array<{ Key: ContractEventType; Value: { Properties: Record<string, string>; Key: string; ParentKey: string } }> ?? [];
14
+ for (const entry of from) {
15
+ const path = eventContractPath('From', entry.Key);
16
+ add(path, `${prefix}${modelBound ? 'fromEvent' : 'from'}`);
17
+ add(`${path}.Key`, `${prefix}${modelBound ? 'fromEvent' : 'from().usingKey'}`);
18
+ add(`${path}.ParentKey`, `${prefix}${modelBound ? 'fromEvent' : 'from().usingParentKey'}`);
19
+ for (const [property, expression] of Object.entries(entry.Value.Properties ?? {})) {
20
+ const operation = expression.startsWith('$add(') ? 'addFrom' : expression.startsWith('$subtract(') ? 'subtractFrom'
21
+ : expression === '$count' ? 'count' : expression === '$increment' ? 'increment'
22
+ : expression === '$decrement' ? 'decrement' : expression === '$null' ? 'clearWith'
23
+ : expression.startsWith('$value(') ? 'setValue' : expression.startsWith('$eventContext(') ? 'setFromContext' : 'setFrom';
24
+ add(`${path}.Properties.${property}`, `${prefix}${modelBound ? operation : `from().${operation}`}`);
25
+ }
26
+ }
27
+ for (const section of ['Join', 'RemovedWith', 'RemovedWithJoin'] as const) {
28
+ for (const entry of definition[section] as Array<{ Key: ContractEventType; Value: { Properties?: Record<string, string>; Key?: string; ParentKey?: string } }> ?? []) {
29
+ const path = eventContractPath(section, entry.Key);
30
+ const declaration = modelBound ? `@${section[0].toLowerCase()}${section.slice(1)}` : `.${section[0].toLowerCase()}${section.slice(1)}`;
31
+ add(path, declaration);
32
+ if (entry.Value.Key !== undefined) add(`${path}.Key`, declaration);
33
+ if (entry.Value.ParentKey !== undefined) add(`${path}.ParentKey`, declaration);
34
+ for (const property of Object.keys(entry.Value.Properties ?? {})) add(`${path}.Properties.${property}`, declaration);
35
+ }
36
+ }
37
+ for (const section of ['Children', 'Nested'] as const) {
38
+ for (const property of Object.keys(definition[section] as Record<string, unknown> ?? {})) {
39
+ add(`${section}.${property}`, modelBound ? (section === 'Children' ? '@childrenFrom' : '@nested') : `.${section.toLowerCase()}`);
40
+ }
41
+ }
42
+ const all = definition.All as { Properties?: Record<string, string>; IncludeChildren?: boolean } | undefined;
43
+ if (all?.IncludeChildren || Object.keys(all?.Properties ?? {}).length) add('All', modelBound ? '@fromEvery' : '.fromEvery');
44
+ for (const property of Object.keys(all?.Properties ?? {})) add(`All.Properties.${property}`, modelBound ? '@fromEvery' : '.fromEvery');
45
+ if ((definition.FromEvery as unknown[] | undefined)?.length) add('FromEvery', modelBound ? '@fromEvery' : '.fromEvery');
46
+ if (definition.IsActive === false) add('IsActive', modelBound ? '@passive' : '.passive');
47
+ if (definition.EventSequenceId) add('EventSequenceId', modelBound ? '@eventSequence' : '.fromEventSequence');
48
+ if (definition.FromEventProperty) add('FromEventProperty', modelBound ? '@fromEvent' : '.from');
49
+ return result;
50
+ }