@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.
- package/artifacts/ActivatedArtifact.ts +5 -1
- package/artifacts/ArtifactCompletionFailed.ts +20 -0
- package/artifacts/ArtifactInvocationContext.ts +21 -0
- package/artifacts/for_withActivatedArtifact/when_completing/after_processing_failure.spec.ts +34 -0
- package/artifacts/index.ts +2 -0
- package/artifacts/withActivatedArtifact.ts +20 -3
- package/dist/artifacts/ActivatedArtifact.d.ts +4 -1
- package/dist/artifacts/ActivatedArtifact.d.ts.map +1 -1
- package/dist/artifacts/ArtifactCompletionFailed.d.ts +9 -0
- package/dist/artifacts/ArtifactCompletionFailed.d.ts.map +1 -0
- package/dist/artifacts/ArtifactCompletionFailed.js +19 -0
- package/dist/artifacts/ArtifactCompletionFailed.js.map +1 -0
- package/dist/artifacts/ArtifactInvocationContext.d.ts +18 -0
- package/dist/artifacts/ArtifactInvocationContext.d.ts.map +1 -0
- package/dist/artifacts/ArtifactInvocationContext.js +4 -0
- package/dist/artifacts/ArtifactInvocationContext.js.map +1 -0
- package/dist/artifacts/index.d.ts +2 -0
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +1 -0
- package/dist/artifacts/index.js.map +1 -1
- package/dist/artifacts/withActivatedArtifact.d.ts +2 -1
- package/dist/artifacts/withActivatedArtifact.d.ts.map +1 -1
- package/dist/artifacts/withActivatedArtifact.js +22 -3
- package/dist/artifacts/withActivatedArtifact.js.map +1 -1
- package/dist/projections/CompiledProjectionDefinitions.d.ts +6 -0
- package/dist/projections/CompiledProjectionDefinitions.d.ts.map +1 -1
- package/dist/projections/InvalidEventContextPropertyError.d.ts +10 -0
- package/dist/projections/InvalidEventContextPropertyError.d.ts.map +1 -0
- package/dist/projections/InvalidEventContextPropertyError.js +16 -0
- package/dist/projections/InvalidEventContextPropertyError.js.map +1 -0
- package/dist/projections/ProjectionCapabilityProvenance.d.ts +8 -0
- package/dist/projections/ProjectionCapabilityProvenance.d.ts.map +1 -0
- package/dist/projections/ProjectionCapabilityProvenance.js +4 -0
- package/dist/projections/ProjectionCapabilityProvenance.js.map +1 -0
- package/dist/projections/ProjectionDefinitionCompiler.d.ts +2 -0
- package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
- package/dist/projections/ProjectionDefinitionCompiler.js +148 -7
- package/dist/projections/ProjectionDefinitionCompiler.js.map +1 -1
- package/dist/projections/ProjectionEventSchema.d.ts +10 -0
- package/dist/projections/ProjectionEventSchema.d.ts.map +1 -0
- package/dist/projections/ProjectionEventSchema.js +4 -0
- package/dist/projections/ProjectionEventSchema.js.map +1 -0
- package/dist/projections/VariantReclassifier.d.ts +2 -0
- package/dist/projections/VariantReclassifier.d.ts.map +1 -1
- package/dist/projections/VariantReclassifier.js.map +1 -1
- package/dist/projections/captureProjectionProvenance.d.ts +4 -0
- package/dist/projections/captureProjectionProvenance.d.ts.map +1 -0
- package/dist/projections/captureProjectionProvenance.js +56 -0
- package/dist/projections/captureProjectionProvenance.js.map +1 -0
- package/dist/projections/declarative/AllSetBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/AllSetBuilder.js +2 -1
- package/dist/projections/declarative/AllSetBuilder.js.map +1 -1
- package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/FromBuilder.js +12 -2
- package/dist/projections/declarative/FromBuilder.js.map +1 -1
- package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/JoinBuilder.js +2 -1
- package/dist/projections/declarative/JoinBuilder.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js +8 -0
- package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
- package/dist/projections/declarative/RemovedWithBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/RemovedWithBuilder.js +2 -1
- package/dist/projections/declarative/RemovedWithBuilder.js.map +1 -1
- package/dist/projections/declarative/SetBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/SetBuilder.js +2 -1
- package/dist/projections/declarative/SetBuilder.js.map +1 -1
- package/dist/projections/declarative/projectionBuilderProvenance.d.ts +16 -0
- package/dist/projections/declarative/projectionBuilderProvenance.d.ts.map +1 -0
- package/dist/projections/declarative/projectionBuilderProvenance.js +28 -0
- package/dist/projections/declarative/projectionBuilderProvenance.js.map +1 -0
- package/dist/projections/eventContextPropertyExpression.d.ts +3 -0
- package/dist/projections/eventContextPropertyExpression.d.ts.map +1 -0
- package/dist/projections/eventContextPropertyExpression.js +69 -0
- package/dist/projections/eventContextPropertyExpression.js.map +1 -0
- package/dist/projections/eventContractPath.d.ts +4 -0
- package/dist/projections/eventContractPath.d.ts.map +1 -0
- package/dist/projections/eventContractPath.js +7 -0
- package/dist/projections/eventContractPath.js.map +1 -0
- package/dist/projections/index.d.ts +1 -0
- package/dist/projections/index.d.ts.map +1 -1
- package/dist/projections/index.js +1 -0
- package/dist/projections/index.js.map +1 -1
- package/dist/projections/invalidEventContextPropertyInProjection.d.ts +4 -0
- package/dist/projections/invalidEventContextPropertyInProjection.d.ts.map +1 -0
- package/dist/projections/invalidEventContextPropertyInProjection.js +7 -0
- package/dist/projections/invalidEventContextPropertyInProjection.js.map +1 -0
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js +2 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +12 -3
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/readModels/rootReadModelTypes.d.ts.map +1 -1
- package/dist/readModels/rootReadModelTypes.js +12 -1
- package/dist/readModels/rootReadModelTypes.js.map +1 -1
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +12 -3
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/testing/projections/ProjectionCapabilities.d.ts +17 -0
- package/dist/testing/projections/ProjectionCapabilities.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionCapabilities.js +233 -0
- package/dist/testing/projections/ProjectionCapabilities.js.map +1 -0
- package/dist/testing/projections/UnsupportedProjectionOperation.d.ts +11 -0
- package/dist/testing/projections/UnsupportedProjectionOperation.d.ts.map +1 -0
- package/dist/testing/projections/UnsupportedProjectionOperation.js +16 -0
- package/dist/testing/projections/UnsupportedProjectionOperation.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts +7 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js +16 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts +10 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Model.d.ts.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js +20 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Model.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts +3 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.d.ts.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js +7 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Removed.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts +12 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/compile.d.ts.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js +34 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/compile.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/projections/CompiledProjectionDefinitions.ts +6 -0
- package/projections/InvalidEventContextPropertyError.ts +14 -0
- package/projections/ProjectionCapabilityProvenance.ts +10 -0
- package/projections/ProjectionDefinitionCompiler.ts +146 -7
- package/projections/ProjectionEventSchema.ts +13 -0
- package/projections/VariantReclassifier.ts +2 -0
- package/projections/captureProjectionProvenance.ts +50 -0
- package/projections/declarative/AllSetBuilder.ts +2 -1
- package/projections/declarative/CompositeKeyBuilder.spec.ts +1 -1
- package/projections/declarative/FromBuilder.ts +12 -2
- package/projections/declarative/JoinBuilder.ts +2 -1
- package/projections/declarative/ProjectionBuilderCore.ts +7 -0
- package/projections/declarative/ProjectionBuilderFor.spec.ts +4 -4
- package/projections/declarative/RemovedWithBuilder.ts +2 -1
- package/projections/declarative/SetBuilder.ts +2 -1
- package/projections/declarative/projectionBuilderProvenance.ts +40 -0
- package/projections/eventContextExpressions.spec.ts +273 -0
- package/projections/eventContextPropertyExpression.ts +72 -0
- package/projections/eventContractPath.ts +9 -0
- package/projections/index.ts +1 -0
- package/projections/invalidEventContextPropertyInProjection.ts +13 -0
- package/projections/modelBound/childrenAndNestedBuilder.ts +2 -1
- package/reactors/Reactors.ts +13 -3
- package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +93 -2
- package/readModels/rootReadModelTypes.ts +11 -1
- package/reducers/Reducers.ts +14 -3
- package/reducers/for_Reducers/when_delivering/with_activation.spec.ts +67 -1
- package/testing/projections/ProjectionCapabilities.ts +213 -0
- package/testing/projections/UnsupportedProjectionOperation.ts +16 -0
- package/testing/projections/for_ProjectionCapabilities/given/Changed.ts +12 -0
- package/testing/projections/for_ProjectionCapabilities/given/Model.ts +13 -0
- package/testing/projections/for_ProjectionCapabilities/given/Removed.ts +7 -0
- package/testing/projections/for_ProjectionCapabilities/given/compile.ts +39 -0
- package/testing/projections/for_ProjectionCapabilities/when_compiling_capability_evidence.spec.ts +68 -0
- package/testing/projections/for_ProjectionCapabilities/when_rejecting_unsupported_operations.spec.ts +230 -0
- package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts +73 -0
- package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts +118 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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('
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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 =
|
|
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,
|
|
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
|
+
}
|