@cratis/chronicle 6.16.0 → 6.17.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 (163) 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/InvalidEventContextPropertyError.d.ts +10 -0
  28. package/dist/projections/InvalidEventContextPropertyError.d.ts.map +1 -0
  29. package/dist/projections/InvalidEventContextPropertyError.js +16 -0
  30. package/dist/projections/InvalidEventContextPropertyError.js.map +1 -0
  31. package/dist/projections/ProjectionCapabilityProvenance.d.ts +8 -0
  32. package/dist/projections/ProjectionCapabilityProvenance.d.ts.map +1 -0
  33. package/dist/projections/ProjectionCapabilityProvenance.js +4 -0
  34. package/dist/projections/ProjectionCapabilityProvenance.js.map +1 -0
  35. package/dist/projections/ProjectionDefinitionCompiler.d.ts +2 -0
  36. package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
  37. package/dist/projections/ProjectionDefinitionCompiler.js +148 -7
  38. package/dist/projections/ProjectionDefinitionCompiler.js.map +1 -1
  39. package/dist/projections/ProjectionEventSchema.d.ts +10 -0
  40. package/dist/projections/ProjectionEventSchema.d.ts.map +1 -0
  41. package/dist/projections/ProjectionEventSchema.js +4 -0
  42. package/dist/projections/ProjectionEventSchema.js.map +1 -0
  43. package/dist/projections/VariantReclassifier.d.ts +2 -0
  44. package/dist/projections/VariantReclassifier.d.ts.map +1 -1
  45. package/dist/projections/VariantReclassifier.js.map +1 -1
  46. package/dist/projections/captureProjectionProvenance.d.ts +4 -0
  47. package/dist/projections/captureProjectionProvenance.d.ts.map +1 -0
  48. package/dist/projections/captureProjectionProvenance.js +56 -0
  49. package/dist/projections/captureProjectionProvenance.js.map +1 -0
  50. package/dist/projections/declarative/AllSetBuilder.d.ts.map +1 -1
  51. package/dist/projections/declarative/AllSetBuilder.js +2 -1
  52. package/dist/projections/declarative/AllSetBuilder.js.map +1 -1
  53. package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
  54. package/dist/projections/declarative/FromBuilder.js +12 -2
  55. package/dist/projections/declarative/FromBuilder.js.map +1 -1
  56. package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
  57. package/dist/projections/declarative/JoinBuilder.js +2 -1
  58. package/dist/projections/declarative/JoinBuilder.js.map +1 -1
  59. package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
  60. package/dist/projections/declarative/ProjectionBuilderCore.js +8 -0
  61. package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
  62. package/dist/projections/declarative/RemovedWithBuilder.d.ts.map +1 -1
  63. package/dist/projections/declarative/RemovedWithBuilder.js +2 -1
  64. package/dist/projections/declarative/RemovedWithBuilder.js.map +1 -1
  65. package/dist/projections/declarative/SetBuilder.d.ts.map +1 -1
  66. package/dist/projections/declarative/SetBuilder.js +2 -1
  67. package/dist/projections/declarative/SetBuilder.js.map +1 -1
  68. package/dist/projections/declarative/projectionBuilderProvenance.d.ts +16 -0
  69. package/dist/projections/declarative/projectionBuilderProvenance.d.ts.map +1 -0
  70. package/dist/projections/declarative/projectionBuilderProvenance.js +28 -0
  71. package/dist/projections/declarative/projectionBuilderProvenance.js.map +1 -0
  72. package/dist/projections/eventContextPropertyExpression.d.ts +3 -0
  73. package/dist/projections/eventContextPropertyExpression.d.ts.map +1 -0
  74. package/dist/projections/eventContextPropertyExpression.js +69 -0
  75. package/dist/projections/eventContextPropertyExpression.js.map +1 -0
  76. package/dist/projections/eventContractPath.d.ts +4 -0
  77. package/dist/projections/eventContractPath.d.ts.map +1 -0
  78. package/dist/projections/eventContractPath.js +7 -0
  79. package/dist/projections/eventContractPath.js.map +1 -0
  80. package/dist/projections/index.d.ts +1 -0
  81. package/dist/projections/index.d.ts.map +1 -1
  82. package/dist/projections/index.js +1 -0
  83. package/dist/projections/index.js.map +1 -1
  84. package/dist/projections/invalidEventContextPropertyInProjection.d.ts +4 -0
  85. package/dist/projections/invalidEventContextPropertyInProjection.d.ts.map +1 -0
  86. package/dist/projections/invalidEventContextPropertyInProjection.js +7 -0
  87. package/dist/projections/invalidEventContextPropertyInProjection.js.map +1 -0
  88. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
  89. package/dist/projections/modelBound/childrenAndNestedBuilder.js +2 -1
  90. package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
  91. package/dist/reactors/Reactors.d.ts.map +1 -1
  92. package/dist/reactors/Reactors.js +12 -3
  93. package/dist/reactors/Reactors.js.map +1 -1
  94. package/dist/readModels/rootReadModelTypes.d.ts.map +1 -1
  95. package/dist/readModels/rootReadModelTypes.js +12 -1
  96. package/dist/readModels/rootReadModelTypes.js.map +1 -1
  97. package/dist/reducers/Reducers.d.ts.map +1 -1
  98. package/dist/reducers/Reducers.js +12 -3
  99. package/dist/reducers/Reducers.js.map +1 -1
  100. package/dist/testing/projections/ProjectionCapabilities.d.ts +17 -0
  101. package/dist/testing/projections/ProjectionCapabilities.d.ts.map +1 -0
  102. package/dist/testing/projections/ProjectionCapabilities.js +233 -0
  103. package/dist/testing/projections/ProjectionCapabilities.js.map +1 -0
  104. package/dist/testing/projections/UnsupportedProjectionOperation.d.ts +11 -0
  105. package/dist/testing/projections/UnsupportedProjectionOperation.d.ts.map +1 -0
  106. package/dist/testing/projections/UnsupportedProjectionOperation.js +16 -0
  107. package/dist/testing/projections/UnsupportedProjectionOperation.js.map +1 -0
  108. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts +7 -0
  109. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts.map +1 -0
  110. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js +16 -0
  111. package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js.map +1 -0
  112. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts +10 -0
  113. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts.map +1 -0
  114. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js +20 -0
  115. package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js.map +1 -0
  116. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts +3 -0
  117. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts.map +1 -0
  118. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js +7 -0
  119. package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js.map +1 -0
  120. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts +12 -0
  121. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts.map +1 -0
  122. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js +34 -0
  123. package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js.map +1 -0
  124. package/dist/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +1 -1
  126. package/projections/CompiledProjectionDefinitions.ts +6 -0
  127. package/projections/InvalidEventContextPropertyError.ts +14 -0
  128. package/projections/ProjectionCapabilityProvenance.ts +10 -0
  129. package/projections/ProjectionDefinitionCompiler.ts +146 -7
  130. package/projections/ProjectionEventSchema.ts +13 -0
  131. package/projections/VariantReclassifier.ts +2 -0
  132. package/projections/captureProjectionProvenance.ts +50 -0
  133. package/projections/declarative/AllSetBuilder.ts +2 -1
  134. package/projections/declarative/CompositeKeyBuilder.spec.ts +1 -1
  135. package/projections/declarative/FromBuilder.ts +12 -2
  136. package/projections/declarative/JoinBuilder.ts +2 -1
  137. package/projections/declarative/ProjectionBuilderCore.ts +7 -0
  138. package/projections/declarative/ProjectionBuilderFor.spec.ts +4 -4
  139. package/projections/declarative/RemovedWithBuilder.ts +2 -1
  140. package/projections/declarative/SetBuilder.ts +2 -1
  141. package/projections/declarative/projectionBuilderProvenance.ts +40 -0
  142. package/projections/eventContextExpressions.spec.ts +273 -0
  143. package/projections/eventContextPropertyExpression.ts +72 -0
  144. package/projections/eventContractPath.ts +9 -0
  145. package/projections/index.ts +1 -0
  146. package/projections/invalidEventContextPropertyInProjection.ts +13 -0
  147. package/projections/modelBound/childrenAndNestedBuilder.ts +2 -1
  148. package/reactors/Reactors.ts +13 -3
  149. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +93 -2
  150. package/readModels/rootReadModelTypes.ts +11 -1
  151. package/reducers/Reducers.ts +14 -3
  152. package/reducers/for_Reducers/when_delivering/with_activation.spec.ts +67 -1
  153. package/testing/projections/ProjectionCapabilities.ts +213 -0
  154. package/testing/projections/UnsupportedProjectionOperation.ts +16 -0
  155. package/testing/projections/for_ProjectionCapabilities/given/Changed.ts +12 -0
  156. package/testing/projections/for_ProjectionCapabilities/given/Model.ts +13 -0
  157. package/testing/projections/for_ProjectionCapabilities/given/Removed.ts +7 -0
  158. package/testing/projections/for_ProjectionCapabilities/given/compile.ts +39 -0
  159. package/testing/projections/for_ProjectionCapabilities/when_compiling_capability_evidence.spec.ts +68 -0
  160. package/testing/projections/for_ProjectionCapabilities/when_rejecting_unsupported_operations.spec.ts +230 -0
  161. package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts +73 -0
  162. package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts +118 -0
  163. 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,7 +15,15 @@ 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';
24
+ import { eventContextPropertyExpression } from './eventContextPropertyExpression.js';
25
+ import { InvalidEventContextPropertyError } from './InvalidEventContextPropertyError.js';
26
+ import { invalidEventContextPropertyInProjection } from './invalidEventContextPropertyInProjection.js';
18
27
  import { getProjectionMetadata } from './declarative/projection.js';
19
28
  import { ProjectionBuilderFor } from './declarative/ProjectionBuilderFor.js';
20
29
  import type { IProjectionFor } from './declarative/IProjectionFor.js';
@@ -39,6 +48,14 @@ import { getRemovedWithJoinClassMetadata, getRemovedWithJoinPropertyMetadata } f
39
48
  import { getVariantOfMetadata } from './modelBound/variantOf.js';
40
49
  import { getEntersOnMetadata } from './modelBound/entersOn.js';
41
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';
42
59
  import type { JoinRecord } from './declarative/ProjectionBuilderCore.js';
43
60
  import { BuiltProjection, crossWireGroups, mergeGlobalHandlers, reclassify, VariantDeclaration } from './VariantReclassifier.js';
44
61
 
@@ -61,8 +78,9 @@ export class ProjectionDefinitionCompiler {
61
78
  */
62
79
  compile(declarative: Iterable<Constructor>, modelBound: Iterable<Constructor>): CompiledProjectionDefinitions {
63
80
  const builtProjections: BuiltProjection[] = [
64
- ...Array.from(declarative, type => this.buildDeclarativeDefinition(type)),
65
- ...Array.from(modelBound, type => this.buildModelBoundDefinition(type))
81
+ ...Array.from(declarative, type => this.buildWithContextValidation(type, () => this.buildDeclarativeDefinition(type),
82
+ getProjectionMetadata(type)?.readModelType)),
83
+ ...Array.from(modelBound, type => this.buildWithContextValidation(type, () => this.buildModelBoundDefinition(type), type))
66
84
  ];
67
85
  // A sibling's entering event adds RemovedWith to each variant. Hash only after
68
86
  // every variant has been cross-wired, so repeated registrations remain stable.
@@ -75,12 +93,65 @@ export class ProjectionDefinitionCompiler {
75
93
  // existing sparse wire objects: adding those default-valued fields would change the payload.
76
94
  // Evaluators must tolerate absent optional contract fields (for example NoAutoMapProperties).
77
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
+ }
78
106
  return {
79
107
  definitions,
80
- readModels: this.buildReadModelDefinitions(definitions)
108
+ readModels: this.buildReadModelDefinitions(definitions),
109
+ provenance,
110
+ eventSchemas
81
111
  };
82
112
  }
83
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
+
143
+ private buildWithContextValidation(type: Constructor, build: () => BuiltProjection, readModelType?: Constructor): BuiltProjection {
144
+ try {
145
+ return build();
146
+ } catch (error) {
147
+ if (error instanceof InvalidEventContextPropertyError) {
148
+ const readModel = readModelType ? getReadModelId(readModelType) : type.name;
149
+ throw invalidEventContextPropertyInProjection(error, type.name, readModel);
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+
84
155
  private buildReadModelDefinitions(projections: ProjectionDefinition[]): ReturnType<typeof buildReadModelDefinition>[] {
85
156
  const byReadModel = new Map<string, ReturnType<typeof buildReadModelDefinition>>();
86
157
  for (const projection of projections) {
@@ -149,6 +220,12 @@ export class ProjectionDefinitionCompiler {
149
220
  }
150
221
  }
151
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
+ }
152
229
  let variant: VariantDeclaration | undefined;
153
230
  const variantDeclaration = builder.getVariantDeclaration();
154
231
  if (variantDeclaration) {
@@ -158,8 +235,11 @@ export class ProjectionDefinitionCompiler {
158
235
  definition.From = reclassified.from;
159
236
  definition.Join = reclassified.join;
160
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' });
161
241
  }
162
- return { typeName: type.name, definition, variant };
242
+ return { typeName: type.name, definition, variant, provenance };
163
243
  }
164
244
 
165
245
  private inferReadModelIdentifier(mappedProperties: string[]): string | undefined {
@@ -187,6 +267,16 @@ export class ProjectionDefinitionCompiler {
187
267
  const eventSequenceId = getEventSequenceMetadata(type);
188
268
  const properties = TypeIntrospector.getTrackedProperties(type);
189
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
+ };
190
280
 
191
281
  const fromByEventType = new Map<string, FromRecord>();
192
282
  const joinByEventType = new Map<string, { Key: ContractEventType; Value: { On: string; Properties: Record<string, string>; Key: string } }>();
@@ -228,6 +318,23 @@ export class ProjectionDefinitionCompiler {
228
318
  }
229
319
 
230
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
+ }
231
338
  applyPropertyMappings(prototype, property, fromByEventType);
232
339
  for (const mapping of getJoinMetadata(prototype, property)) {
233
340
  const entry = this.ensureJoinEntry(joinByEventType, mapping.eventType);
@@ -262,6 +369,7 @@ export class ProjectionDefinitionCompiler {
262
369
  for (const clearWith of getClearWithPropertyMetadata(prototype, property)) {
263
370
  const entry = ensureFromEntry(fromByEventType, clearWith.eventType);
264
371
  entry.Value.Properties[property] = '$null';
372
+ overrides.set(`${eventContractPath('From', toContractEventType(clearWith.eventType))}.Properties.${property}`, '@clearWith');
265
373
  }
266
374
  }
267
375
  }
@@ -271,13 +379,15 @@ export class ProjectionDefinitionCompiler {
271
379
  const fromEvery = getFromEveryMetadata(prototype, property) ?? getFromAllMetadata(prototype, property);
272
380
  if (fromEvery) {
273
381
  allProperties[property] = fromEvery.contextProperty
274
- ? fromEvery.contextProperty
382
+ ? eventContextPropertyExpression(fromEvery.contextProperty)
275
383
  : (fromEvery.property ?? property);
276
384
  }
277
385
  }
278
386
 
279
387
  let from = Array.from(fromByEventType.values());
280
388
  let join = Array.from(joinByEventType.values());
389
+ let preLoweringFrom = from;
390
+ let preLoweringJoin = join;
281
391
  let variant: VariantDeclaration | undefined;
282
392
  const variantMetadata = getVariantOfMetadata(type);
283
393
  if (variantMetadata) {
@@ -287,6 +397,7 @@ export class ProjectionDefinitionCompiler {
287
397
  if (entersOn.key) {
288
398
  const entry = ensureFromEntry(fromByEventType, entersOn.eventType);
289
399
  entry.Value.Key = entersOn.key;
400
+ overrides.set(`${eventContractPath('From', contractType)}.Key`, '@entersOn');
290
401
  }
291
402
  return contractType;
292
403
  });
@@ -295,11 +406,21 @@ export class ProjectionDefinitionCompiler {
295
406
  for (const handlerType of this._clientArtifacts.globalForHandlers) {
296
407
  const globalForMetadata = getGlobalForMetadata(handlerType);
297
408
  if (globalForMetadata?.identity === variantMetadata.identity) {
298
- 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
+ }
299
418
  }
300
419
  }
301
420
  const memberNames = new Set(getReadModelMetadata(type)?.members.keys() ?? TypeIntrospector.getMembers(type).keys());
302
421
  const merged = mergeGlobalHandlers(type.name, memberNames, from, globalHandlers);
422
+ preLoweringFrom = merged;
423
+ preLoweringJoin = join;
303
424
  const reclassified = reclassify(type.name, merged, join, enteringEventTypes, variantMetadata.key);
304
425
  from = reclassified.from;
305
426
  join = reclassified.join;
@@ -330,7 +451,25 @@ export class ProjectionDefinitionCompiler {
330
451
  NoAutoMapProperties: properties.filter(property => isPropertyNoAutoMap(prototype, property)),
331
452
  Nested: nestedByProperty
332
453
  };
333
- 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 };
334
473
  }
335
474
 
336
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
+ }
@@ -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
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
5
  import { IAllSetBuilder } from './IAllSetBuilder.js';
6
+ import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
6
7
 
7
8
  /**
8
9
  * Concrete implementation of {@link IAllSetBuilder} for fromEvery mappings.
@@ -27,7 +28,7 @@ export class AllSetBuilder<TReadModel, TParentBuilder> implements IAllSetBuilder
27
28
 
28
29
  /** @inheritdoc */
29
30
  toEventContextProperty(contextPropertyName: string): TParentBuilder {
30
- this._setProperty(this._targetProperty, `$context.${contextPropertyName}`);
31
+ this._setProperty(this._targetProperty, eventContextPropertyExpression(contextPropertyName));
31
32
  return this._parent;
32
33
  }
33
34
 
@@ -101,7 +101,7 @@ describe('CompositeKeyBuilder', () => {
101
101
  expect(() => builder.join(OrderLineJoined, join => join
102
102
  .on(model => model.id)
103
103
  .usingParentKey(event => event.orderId)
104
- .usingParentKeyFromContext('someContextProperty')
104
+ .usingParentKeyFromContext('eventSourceId')
105
105
  .usingParentCompositeKey<CompositeKey>(key => key.set(target => target.orderId, event => event.orderId))
106
106
  .usingConstantParentKey('constant')
107
107
  )).not.toThrow();
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
5
  import { constantValueExpression } from '../constantValueExpression.js';
6
+ import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
6
7
  import { AddBuilder } from './AddBuilder.js';
7
8
  import { AddChildBuilder, ChildAdditionEntry } from './AddChildBuilder.js';
8
9
  import { CompositeKeyBuilder } from './CompositeKeyBuilder.js';
@@ -14,6 +15,7 @@ import { ISetBuilder } from './ISetBuilder.js';
14
15
  import { ISubtractBuilder } from './ISubtractBuilder.js';
15
16
  import { SetBuilder } from './SetBuilder.js';
16
17
  import { SubtractBuilder } from './SubtractBuilder.js';
18
+ import { recordKeyDeclaration } from './projectionBuilderProvenance.js';
17
19
 
18
20
  /**
19
21
  * Accumulated property mapping for a from clause.
@@ -57,12 +59,14 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
57
59
  const proxy = new Proxy({}, handler);
58
60
  keyAccessor(proxy as TEvent);
59
61
  this.entry.key = handler.property;
62
+ recordKeyDeclaration(this, 'Key', '.from().usingKey');
60
63
  return this;
61
64
  }
62
65
 
63
66
  /** @inheritdoc */
64
67
  usingKeyFromContext(contextPropertyName: string): this {
65
- this.entry.key = `$context.${contextPropertyName}`;
68
+ this.entry.key = eventContextPropertyExpression(contextPropertyName);
69
+ recordKeyDeclaration(this, 'Key', '.from().usingKeyFromContext');
66
70
  return this;
67
71
  }
68
72
 
@@ -72,12 +76,14 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
72
76
  const proxy = new Proxy({}, handler);
73
77
  keyAccessor(proxy as TEvent);
74
78
  this.entry.parentKey = handler.property;
79
+ recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKey');
75
80
  return this;
76
81
  }
77
82
 
78
83
  /** @inheritdoc */
79
84
  usingParentKeyFromContext(contextPropertyName: string): this {
80
- this.entry.parentKey = `$context.${contextPropertyName}`;
85
+ this.entry.parentKey = eventContextPropertyExpression(contextPropertyName);
86
+ recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKeyFromContext');
81
87
  return this;
82
88
  }
83
89
 
@@ -86,6 +92,7 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
86
92
  const compositeKeyBuilder = new CompositeKeyBuilder<TKeyType, TEvent>();
87
93
  builderCallback(compositeKeyBuilder);
88
94
  this.entry.key = compositeKeyBuilder.build();
95
+ recordKeyDeclaration(this, 'Key', '.from().usingCompositeKey');
89
96
  return this;
90
97
  }
91
98
 
@@ -94,18 +101,21 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
94
101
  const compositeKeyBuilder = new CompositeKeyBuilder<TKeyType, TEvent>();
95
102
  builderCallback(compositeKeyBuilder);
96
103
  this.entry.parentKey = compositeKeyBuilder.build();
104
+ recordKeyDeclaration(this, 'ParentKey', '.from().usingParentCompositeKey');
97
105
  return this;
98
106
  }
99
107
 
100
108
  /** @inheritdoc */
101
109
  usingConstantKey(value: string): this {
102
110
  this.entry.key = constantValueExpression(value);
111
+ recordKeyDeclaration(this, 'Key', '.from().usingConstantKey');
103
112
  return this;
104
113
  }
105
114
 
106
115
  /** @inheritdoc */
107
116
  usingConstantParentKey(value: string): this {
108
117
  this.entry.parentKey = constantValueExpression(value);
118
+ recordKeyDeclaration(this, 'ParentKey', '.from().usingConstantParentKey');
109
119
  return this;
110
120
  }
111
121
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
5
  import { constantValueExpression } from '../constantValueExpression.js';
6
+ import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
6
7
  import { AddBuilder } from './AddBuilder.js';
7
8
  import { AddChildBuilder, ChildAdditionEntry } from './AddChildBuilder.js';
8
9
  import { CompositeKeyBuilder } from './CompositeKeyBuilder.js';
@@ -72,7 +73,7 @@ export class JoinBuilder<TReadModel, TEvent> implements IJoinBuilder<TReadModel,
72
73
 
73
74
  /** @inheritdoc */
74
75
  usingKeyFromContext(contextPropertyName: string): this {
75
- this.entry.key = `$context.${contextPropertyName}`;
76
+ this.entry.key = eventContextPropertyExpression(contextPropertyName);
76
77
  return this;
77
78
  }
78
79
 
@@ -14,6 +14,8 @@ import { FromEveryBuilder } from './FromEveryBuilder.js';
14
14
  import { RemovedWithBuilder } from './RemovedWithBuilder.js';
15
15
  import { RemovedWithJoinBuilder } from './RemovedWithJoinBuilder.js';
16
16
  import { ChildAdditionEntry } from './AddChildBuilder.js';
17
+ import { eventContractPath } from '../eventContractPath.js';
18
+ import { getProjectionBuilderProvenance, recordChildDeclaration, recordFromEveryDeclaration, recordKeyDeclaration } from './projectionBuilderProvenance.js';
17
19
 
18
20
  /** The contract-level event type identifier shape used across projection definitions. */
19
21
  export type ContractEventType = { Id: string; Generation: number; Tombstone: boolean };
@@ -118,6 +120,10 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
118
120
  ParentKey: fromBuilder.entry.parentKey
119
121
  }
120
122
  });
123
+ for (const [field, declaration] of getProjectionBuilderProvenance(fromBuilder).keys) {
124
+ recordKeyDeclaration(this, `${eventContractPath('From', contractType)}.${field}`, declaration);
125
+ }
126
+ for (const child of fromBuilder.entry.children) recordChildDeclaration(this, `Children.${child.targetProperty}`, '.from().addChild');
121
127
  this.mergeChildAdditions(contractType, fromBuilder.entry.children);
122
128
  return this as unknown as TBuilder;
123
129
  }
@@ -146,6 +152,7 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
146
152
 
147
153
  /** @inheritdoc */
148
154
  fromEvery(builderCallback: (builder: IFromEveryBuilder<TReadModel>) => void): TBuilder {
155
+ recordFromEveryDeclaration(this);
149
156
  const builder = new FromEveryBuilder<TReadModel>();
150
157
  builderCallback(builder);
151
158
  this._all = {
@@ -227,19 +227,19 @@ describe('ProjectionBuilderFor', () => {
227
227
  };
228
228
 
229
229
  it('should change the hash when a From property expression changes', () => {
230
- expect(hashFor(false, 'one', 'source')).not.toBe(hashFor(true, 'one', 'source'));
230
+ expect(hashFor(false, 'one', 'eventSourceId')).not.toBe(hashFor(true, 'one', 'eventSourceId'));
231
231
  });
232
232
 
233
233
  it('should change the hash when a Join key changes', () => {
234
- expect(hashFor(false, 'one', 'source')).not.toBe(hashFor(false, 'two', 'source'));
234
+ expect(hashFor(false, 'one', 'eventSourceId')).not.toBe(hashFor(false, 'two', 'eventSourceId'));
235
235
  });
236
236
 
237
237
  it('should change the hash when a RemovedWith key changes', () => {
238
- expect(hashFor(false, 'one', 'source')).not.toBe(hashFor(false, 'one', 'correlationId'));
238
+ expect(hashFor(false, 'one', 'eventSourceId')).not.toBe(hashFor(false, 'one', 'correlationId'));
239
239
  });
240
240
 
241
241
  it('should give identical definitions identical hashes', () => {
242
- expect(hashFor(false, 'one', 'source')).toBe(hashFor(false, 'one', 'source'));
242
+ expect(hashFor(false, 'one', 'eventSourceId')).toBe(hashFor(false, 'one', 'eventSourceId'));
243
243
  });
244
244
 
245
245
  it('should give reordered property mappings the same hash', () => {
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
5
  import { IRemovedWithBuilder } from './IRemovedWithBuilder.js';
6
+ import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
6
7
 
7
8
  /**
8
9
  * Accumulated removal configuration for a removedWith clause.
@@ -34,7 +35,7 @@ export class RemovedWithBuilder<TReadModel, TEvent> implements IRemovedWithBuild
34
35
 
35
36
  /** @inheritdoc */
36
37
  usingKeyFromContext(contextPropertyName: string): this {
37
- this.entry.key = `$context.${contextPropertyName}`;
38
+ this.entry.key = eventContextPropertyExpression(contextPropertyName);
38
39
  return this;
39
40
  }
40
41
  }
@@ -4,6 +4,7 @@
4
4
  import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
5
5
  import { ISetBuilder } from './ISetBuilder.js';
6
6
  import { constantValueExpression } from '../constantValueExpression.js';
7
+ import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
7
8
 
8
9
  /**
9
10
  * Concrete implementation of {@link ISetBuilder} that records the property mapping
@@ -35,7 +36,7 @@ export class SetBuilder<TEvent, TParentBuilder> implements ISetBuilder<TEvent, T
35
36
 
36
37
  /** @inheritdoc */
37
38
  toEventContextProperty(contextPropertyName: string): TParentBuilder {
38
- this._setProperty(this._readModelProperty, `$context.${contextPropertyName}`);
39
+ this._setProperty(this._readModelProperty, eventContextPropertyExpression(contextPropertyName));
39
40
  return this._parent;
40
41
  }
41
42
 
@@ -0,0 +1,40 @@
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
+ /** Internal declaration origins; never part of a builder or its published interface. */
5
+ interface BuilderProvenance {
6
+ fromEvery: boolean;
7
+ keys: Map<string, string>;
8
+ children: Map<string, string>;
9
+ }
10
+
11
+ const declarations = new WeakMap<object, BuilderProvenance>();
12
+
13
+ function forBuilder(builder: object): BuilderProvenance {
14
+ let provenance = declarations.get(builder);
15
+ if (!provenance) {
16
+ provenance = { fromEvery: false, keys: new Map(), children: new Map() };
17
+ declarations.set(builder, provenance);
18
+ }
19
+ return provenance;
20
+ }
21
+
22
+ /** Records an explicit key operation on a from builder or projection builder. */
23
+ export function recordKeyDeclaration(builder: object, path: string, declaration: string): void {
24
+ forBuilder(builder).keys.set(path, declaration);
25
+ }
26
+
27
+ /** Records a child addition without adding state to the public builder type. */
28
+ export function recordChildDeclaration(builder: object, path: string, declaration: string): void {
29
+ forBuilder(builder).children.set(path, declaration);
30
+ }
31
+
32
+ /** Remembers empty fromEvery declarations, which have no wire representation. */
33
+ export function recordFromEveryDeclaration(builder: object): void {
34
+ forBuilder(builder).fromEvery = true;
35
+ }
36
+
37
+ /** Reads declaration origins while compiling, before variant lowering. */
38
+ export function getProjectionBuilderProvenance(builder: object): Readonly<BuilderProvenance> {
39
+ return forBuilder(builder);
40
+ }