@cratis/chronicle 6.17.0 → 6.19.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/dist/projections/declarative/AddChildBuilder.d.ts +13 -2
- package/dist/projections/declarative/AddChildBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/AddChildBuilder.js +27 -1
- package/dist/projections/declarative/AddChildBuilder.js.map +1 -1
- package/dist/projections/declarative/FromBuilder.d.ts +4 -0
- package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/FromBuilder.js +1 -2
- package/dist/projections/declarative/FromBuilder.js.map +1 -1
- package/dist/projections/declarative/IAddChildBuilder.d.ts +12 -0
- package/dist/projections/declarative/IAddChildBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/IReadModelPropertiesBuilder.d.ts +16 -1
- package/dist/projections/declarative/IReadModelPropertiesBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/JoinBuilder.d.ts +4 -0
- package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/JoinBuilder.js +1 -2
- package/dist/projections/declarative/JoinBuilder.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js +41 -10
- package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
- package/dist/testing/IReadModelProcessor.d.ts +7 -0
- package/dist/testing/IReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/IReadModelProcessor.js +4 -0
- package/dist/testing/IReadModelProcessor.js.map +1 -0
- package/dist/testing/ReadModelScenario.d.ts +6 -8
- package/dist/testing/ReadModelScenario.d.ts.map +1 -1
- package/dist/testing/ReadModelScenario.js +50 -50
- package/dist/testing/ReadModelScenario.js.map +1 -1
- package/dist/testing/ReadModelState.d.ts +6 -0
- package/dist/testing/ReadModelState.d.ts.map +1 -0
- package/dist/testing/ReadModelState.js +4 -0
- package/dist/testing/ReadModelState.js.map +1 -0
- package/dist/testing/ReducerReadModelProcessor.d.ts +13 -0
- package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/ReducerReadModelProcessor.js +38 -0
- package/dist/testing/ReducerReadModelProcessor.js.map +1 -0
- package/dist/testing/ScenarioEvent.d.ts +8 -0
- package/dist/testing/ScenarioEvent.d.ts.map +1 -0
- package/dist/testing/ScenarioEvent.js +4 -0
- package/dist/testing/ScenarioEvent.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/projections/ProjectionCapabilities.d.ts +3 -0
- package/dist/testing/projections/ProjectionCapabilities.d.ts.map +1 -1
- package/dist/testing/projections/ProjectionCapabilities.js +117 -43
- package/dist/testing/projections/ProjectionCapabilities.js.map +1 -1
- package/dist/testing/projections/ProjectionExpressionEvaluator.d.ts +9 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.js +68 -0
- package/dist/testing/projections/ProjectionExpressionEvaluator.js.map +1 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts +30 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.js +142 -0
- package/dist/testing/projections/ProjectionReadModelProcessor.js.map +1 -0
- package/dist/testing/projections/ProjectionValueConverter.d.ts +13 -0
- package/dist/testing/projections/ProjectionValueConverter.d.ts.map +1 -0
- package/dist/testing/projections/ProjectionValueConverter.js +122 -0
- package/dist/testing/projections/ProjectionValueConverter.js.map +1 -0
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts +0 -2
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.d.ts.map +1 -1
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js +0 -4
- package/dist/testing/projections/for_ProjectionCapabilities/given/Changed.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/projections/ProjectionDefinitionCompiler.registration.spec.ts +4 -2
- package/projections/Projections.childrenAndNested.spec.ts +250 -1
- package/projections/declarative/AddChildBuilder.ts +33 -2
- package/projections/declarative/FromBuilder.ts +9 -1
- package/projections/declarative/IAddChildBuilder.ts +14 -0
- package/projections/declarative/IReadModelPropertiesBuilder.ts +18 -1
- package/projections/declarative/JoinBuilder.ts +9 -1
- package/projections/declarative/ProjectionBuilderCore.ts +1 -1
- package/projections/declarative/addChildCompatibility.standard.spec.ts +80 -0
- package/projections/eventContextExpressions.spec.ts +20 -0
- package/projections/modelBound/childrenAndNestedBuilder.ts +45 -11
- package/testing/IReadModelProcessor.ts +10 -0
- package/testing/ReadModelScenario.spec.ts +156 -10
- package/testing/ReadModelScenario.ts +57 -57
- package/testing/ReadModelState.ts +5 -0
- package/testing/ReducerReadModelProcessor.ts +39 -0
- package/testing/ScenarioEvent.ts +7 -0
- package/testing/index.ts +1 -0
- package/testing/projections/ProjectionCapabilities.ts +100 -39
- package/testing/projections/ProjectionExpressionEvaluator.ts +57 -0
- package/testing/projections/ProjectionReadModelProcessor.fixtures.spec.ts +135 -0
- package/testing/projections/ProjectionReadModelProcessor.ts +147 -0
- package/testing/projections/ProjectionValueConverter.spec.ts +81 -0
- package/testing/projections/ProjectionValueConverter.ts +113 -0
- package/testing/projections/for_ProjectionCapabilities/given/Changed.ts +1 -3
- package/testing/projections/for_ProjectionCapabilities/when_rejecting_unsupported_operations.spec.ts +81 -1
- package/testing/projections/for_ProjectionCapabilities/when_validating_numeric_schemas.spec.ts +40 -12
- package/testing/projections/for_ProjectionCapabilities/when_validating_schema_boundaries.spec.ts +76 -29
- package/testing/projections/for_ProjectionCapabilities/when_validating_supported_operations.spec.ts +8 -35
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReadModelScenario.d.ts","sourceRoot":"","sources":["../../testing/ReadModelScenario.ts"],"names":[],"mappings":"AAGA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ReadModelScenario.d.ts","sourceRoot":"","sources":["../../testing/ReadModelScenario.ts"],"names":[],"mappings":"AAGA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAazF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAQnF,KAAK,iBAAiB,GAAG,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC,GAC9F,OAAO,CAAC,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,mBAAmB,CAAC,CAAC,CAAC;AAEhF;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,UAAU,SAAS,MAAM;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAA+D;IAE/E,qFAAqF;IACrF,YAAY,aAAa,EAAE,WAAW,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC,EAAE,iBAAiB,EAwChF;IAED,4CAA4C;IAC5C,IAAI,KAAK,IAAI,6BAA6B,CAAC,UAAU,CAAC,CAErD;IAED,yEAAyE;IACzE,IAAI,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAQzC;IAED,4FAA4F;IACtF,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAErE;IAED,yEAAyE;IACnE,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE7D;IAED,yFAAyF;IACzF,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CA0B5D;IAED,OAAO,CAAC,OAAO;CAUlB"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
import 'reflect-metadata';
|
|
4
4
|
import { JsonSerializer } from '@cratis/fundamentals';
|
|
5
|
+
import { DefaultClientArtifactsProvider } from '../artifacts/DefaultClientArtifactsProvider.js';
|
|
5
6
|
import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
6
7
|
import { getFromEventMetadata } from '../projections/modelBound/fromEvent.js';
|
|
7
8
|
import { getRemovedWithClassMetadata } from '../projections/modelBound/removedWith.js';
|
|
@@ -10,47 +11,65 @@ import { getClearWithClassMetadata } from '../projections/modelBound/clearWith.j
|
|
|
10
11
|
import { getVariantOfMetadata } from '../projections/modelBound/variantOf.js';
|
|
11
12
|
import { getEntersOnMetadata } from '../projections/modelBound/entersOn.js';
|
|
12
13
|
import { getProjectionMetadata } from '../projections/declarative/projection.js';
|
|
14
|
+
import { ProjectionDefinitionCompiler } from '../projections/ProjectionDefinitionCompiler.js';
|
|
15
|
+
import { getReadModelId } from '../readModels/readModel.js';
|
|
13
16
|
import { getReducerMetadata } from '../reducers/reducer.js';
|
|
14
|
-
import { ReducerEventDispatcher } from '../reducers/ReducerEventDispatcher.js';
|
|
15
|
-
import { DecoratorType } from '../types/DecoratorType.js';
|
|
16
17
|
import { hasModelBoundProperties, TypeDiscoverer } from '../types/TypeDiscoverer.js';
|
|
17
18
|
import { ReadModelScenarioGivenBuilder } from './ReadModelScenarioGivenBuilder.js';
|
|
19
|
+
import { ReducerReadModelProcessor } from './ReducerReadModelProcessor.js';
|
|
20
|
+
import { ProjectionReadModelProcessor } from './projections/ProjectionReadModelProcessor.js';
|
|
21
|
+
import { UnsupportedProjectionOperation } from './projections/UnsupportedProjectionOperation.js';
|
|
18
22
|
/**
|
|
19
|
-
* Folds seeded events through a reducer
|
|
20
|
-
*
|
|
23
|
+
* Folds seeded events through a reducer or a validated flat projection, without a Chronicle kernel.
|
|
24
|
+
* Observer scheduling, storage, migrations, and compliance are not simulated.
|
|
21
25
|
* Supply an artifact catalog to isolate a scenario from process-wide decorator discovery.
|
|
22
26
|
*/
|
|
23
27
|
export class ReadModelScenario {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_dispatcher;
|
|
28
|
+
_processor;
|
|
29
|
+
_modelName;
|
|
27
30
|
_events = [];
|
|
28
31
|
_results;
|
|
29
|
-
/** Selects the reducer
|
|
32
|
+
/** Selects the reducer (when present) or a single applicable compiled projection. */
|
|
30
33
|
constructor(readModelType, artifacts) {
|
|
31
|
-
this.
|
|
32
|
-
const registered = artifacts ??
|
|
33
|
-
reducers: TypeDiscoverer.default.getTypesByDecoratorType(DecoratorType.Reducer),
|
|
34
|
-
eventTypes: TypeDiscoverer.default.getTypesByDecoratorType(DecoratorType.EventType),
|
|
35
|
-
projections: TypeDiscoverer.default.getTypesByDecoratorType(DecoratorType.Projection)
|
|
36
|
-
};
|
|
34
|
+
this._modelName = readModelType.name;
|
|
35
|
+
const registered = artifacts ?? new DefaultClientArtifactsProvider(TypeDiscoverer.default);
|
|
37
36
|
const reducerTypes = registered.reducers.filter(type => getReducerMetadata(type)?.readModel === readModelType);
|
|
38
37
|
if (reducerTypes.length > 1) {
|
|
39
38
|
throw new Error(`Multiple reducers found for read model '${readModelType.name}'.`);
|
|
40
39
|
}
|
|
41
|
-
if (reducerTypes.length
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
if (reducerTypes.length) {
|
|
41
|
+
this._processor = new ReducerReadModelProcessor(readModelType, reducerTypes[0], registered.eventTypes);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const modelBound = getFromEventMetadata(readModelType).length > 0 || hasModelBoundProperties(readModelType) ||
|
|
45
|
+
getRemovedWithClassMetadata(readModelType).length > 0 || getRemovedWithJoinClassMetadata(readModelType).length > 0 ||
|
|
46
|
+
getClearWithClassMetadata(readModelType).length > 0 || getVariantOfMetadata(readModelType) !== undefined ||
|
|
47
|
+
getEntersOnMetadata(readModelType).length > 0;
|
|
48
|
+
// Definitions without a declared model can be associated by the registration compiler's
|
|
49
|
+
// unambiguous schema inference; never guess their association from decorators here.
|
|
50
|
+
const declarative = registered.projections.filter(type => {
|
|
51
|
+
const metadata = getProjectionMetadata(type);
|
|
52
|
+
return metadata !== undefined && (metadata.readModelType === readModelType ||
|
|
53
|
+
(metadata.readModelType === undefined && registered.readModels?.includes(readModelType)));
|
|
54
|
+
});
|
|
55
|
+
if (Number(modelBound) + declarative.filter(type => getProjectionMetadata(type)?.readModelType === readModelType).length > 1) {
|
|
56
|
+
throw new Error(`Multiple projections found for read model '${readModelType.name}'.`);
|
|
57
|
+
}
|
|
58
|
+
if (!modelBound && !declarative.length) {
|
|
59
|
+
throw new Error(`No reducer or projection found for read model '${readModelType.name}'.`);
|
|
60
|
+
}
|
|
61
|
+
const catalog = {
|
|
62
|
+
eventTypes: registered.eventTypes, reducers: registered.reducers, projections: declarative,
|
|
63
|
+
readModels: [...new Set([...(registered.readModels ?? []), readModelType])],
|
|
64
|
+
globalForHandlers: artifacts?.globalForHandlers ?? [], reactors: [], seeders: [], constraints: [],
|
|
65
|
+
webhooks: [], eventTypeMigrations: []
|
|
66
|
+
};
|
|
67
|
+
const compiled = new ProjectionDefinitionCompiler(catalog, 'scenario').compile(declarative, modelBound ? [readModelType] : []);
|
|
68
|
+
const definitions = compiled.definitions.filter(definition => definition.ReadModel === getReadModelId(readModelType));
|
|
69
|
+
if (definitions.length !== 1) {
|
|
70
|
+
throw new Error(`Expected one projection for read model '${readModelType.name}', found ${definitions.length}.`);
|
|
51
71
|
}
|
|
52
|
-
this.
|
|
53
|
-
this._dispatcher = new ReducerEventDispatcher(this._reducerType, registered.eventTypes);
|
|
72
|
+
this._processor = new ProjectionReadModelProcessor(readModelType, compiled, definitions[0]);
|
|
54
73
|
}
|
|
55
74
|
/** Fluent entry point for event history. */
|
|
56
75
|
get given() {
|
|
@@ -106,32 +125,13 @@ export class ReadModelScenario {
|
|
|
106
125
|
process() {
|
|
107
126
|
if (!this._results) {
|
|
108
127
|
const events = [...this._events];
|
|
109
|
-
this._results = this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async reduce(events) {
|
|
114
|
-
const results = new Map();
|
|
115
|
-
const reducer = new this._reducerType();
|
|
116
|
-
for (const event of events) {
|
|
117
|
-
const handler = this._dispatcher.handlerFor(event.context.eventType.id.value);
|
|
118
|
-
if (!handler)
|
|
119
|
-
continue;
|
|
120
|
-
const prior = results.get(event.sourceId);
|
|
121
|
-
const previous = prior?.deleted ? undefined : prior?.instance;
|
|
122
|
-
let next;
|
|
123
|
-
try {
|
|
124
|
-
next = await this._dispatcher.invoke(reducer, handler, event.content, previous, event.context);
|
|
125
|
-
}
|
|
126
|
-
catch (cause) {
|
|
127
|
-
throw new Error(`Reducer '${this._reducerType.name}' for read model '${this._readModelType.name}' failed`, { cause });
|
|
128
|
-
}
|
|
129
|
-
results.set(event.sourceId, {
|
|
130
|
-
instance: next === undefined ? null : next,
|
|
131
|
-
deleted: next === undefined
|
|
128
|
+
this._results = this._processor.process(events).catch(error => {
|
|
129
|
+
if (!(this._processor instanceof ProjectionReadModelProcessor) || error instanceof UnsupportedProjectionOperation)
|
|
130
|
+
throw error;
|
|
131
|
+
throw new Error(`Projection replay for read model '${this._modelName}' failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
-
return
|
|
134
|
+
return this._results;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
//# sourceMappingURL=ReadModelScenario.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReadModelScenario.js","sourceRoot":"","sources":["../../testing/ReadModelScenario.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ReadModelScenario.js","sourceRoot":"","sources":["../../testing/ReadModelScenario.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,gDAAgD,CAAC;AAEhG,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,EAAE,+BAA+B,EAAE,MAAM,8CAA8C,CAAC;AAC/F,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,4BAA4B,EAAE,MAAM,gDAAgD,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAGnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAC;AAKjG;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IACT,UAAU,CAAkC;IAC5C,UAAU,CAAS;IACnB,OAAO,GAAoB,EAAE,CAAC;IACvC,QAAQ,CAA+D;IAE/E,qFAAqF;IACrF,YAAY,aAAsC,EAAE,SAA6B;QAC7E,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC;QACrC,MAAM,UAAU,GAAG,SAAS,IAAI,IAAI,8BAA8B,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC3F,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,aAAa,CAAC,CAAC;QAC/G,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,2CAA2C,aAAa,CAAC,IAAI,IAAI,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;YACtB,IAAI,CAAC,UAAU,GAAG,IAAI,yBAAyB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;YACvG,OAAO;QACX,CAAC;QACD,MAAM,UAAU,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,uBAAuB,CAAC,aAAa,CAAC;YACvG,2BAA2B,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,+BAA+B,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC;YAClH,yBAAyB,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,oBAAoB,CAAC,aAAa,CAAC,KAAK,SAAS;YACxG,mBAAmB,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAClD,wFAAwF;QACxF,oFAAoF;QACpF,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YACrD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;YAC7C,OAAO,QAAQ,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,aAAa,KAAK,aAAa;gBACtE,CAAC,QAAQ,CAAC,aAAa,KAAK,SAAS,IAAI,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QAClG,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,aAAa,KAAK,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3H,MAAM,IAAI,KAAK,CAAC,8CAA8C,aAAa,CAAC,IAAI,IAAI,CAAC,CAAC;QAC1F,CAAC;QACD,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,kDAAkD,aAAa,CAAC,IAAI,IAAI,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,OAAO,GAA6B;YACtC,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW;YAC1F,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;YAC3E,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE;YACjG,QAAQ,EAAE,EAAE,EAAE,mBAAmB,EAAE,EAAE;SACxC,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,4BAA4B,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/H,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,KAAK,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;QACtH,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,2CAA2C,aAAa,CAAC,IAAI,YAAY,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;QACpH,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,IAAI,4BAA4B,CAAC,aAAa,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChG,CAAC;IAED,4CAA4C;IAC5C,IAAI,KAAK;QACL,OAAO,IAAI,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,yEAAyE;IACzE,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YACjC,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;YACtF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAAC;YACrG,CAAC;YACD,OAAO,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;QAC7C,CAAC,CAAC,CAAC;IACP,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,wBAAwB,CAAC,EAAU;QACrC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;IAC5D,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,0BAA0B,CAAC,EAAU;QACvC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,IAAI,KAAK,CAAC;IAC5D,CAAC;IAED,yFAAyF;IACzF,gBAAgB,CAAC,EAAU,EAAE,MAAyB;QAClD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACzD,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC,WAAW,CAAC,IAAI,+BAA+B,CAAC,CAAC;YAChG,8EAA8E;YAC9E,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACrE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBACd,QAAQ,EAAE,EAAE;gBACZ,OAAO;gBACP,OAAO,EAAE;oBACL,aAAa,EAAE,EAAE;oBACjB,eAAe,EAAE,SAAS;oBAC1B,eAAe,EAAE,KAAK;oBACtB,aAAa,EAAE,SAAS;oBACxB,UAAU,EAAE,UAAU;oBACtB,SAAS,EAAE,UAAU;oBACrB,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;oBAC3C,SAAS,EAAE,QAAQ,CAAC,SAAS;oBAC7B,QAAQ,EAAE,IAAI,IAAI,EAAE;oBACpB,aAAa,EAAE,sCAAsC;oBACrD,SAAS,EAAE,EAAE;oBACb,IAAI,EAAE,EAAE;iBACX;aACJ,CAAC,CAAC;QACP,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC9B,CAAC;IAEO,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;gBAC1D,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,YAAY,4BAA4B,CAAC,IAAI,KAAK,YAAY,8BAA8B;oBAAE,MAAM,KAAK,CAAC;gBAC/H,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,UAAU,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjK,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReadModelState.d.ts","sourceRoot":"","sources":["../../testing/ReadModelState.ts"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReadModelState.js","sourceRoot":"","sources":["../../testing/ReadModelState.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
2
|
+
import type { IReadModelProcessor } from './IReadModelProcessor.js';
|
|
3
|
+
import type { ReadModelState } from './ReadModelState.js';
|
|
4
|
+
import type { ScenarioEvent } from './ScenarioEvent.js';
|
|
5
|
+
/** Preserves the SDK reducer dispatch and the scenario's null-versus-deletion semantics. */
|
|
6
|
+
export declare class ReducerReadModelProcessor<TReadModel extends object> implements IReadModelProcessor<TReadModel> {
|
|
7
|
+
private readonly _readModelType;
|
|
8
|
+
private readonly _reducerType;
|
|
9
|
+
private readonly _dispatcher;
|
|
10
|
+
constructor(_readModelType: Constructor<TReadModel>, _reducerType: Constructor, eventTypes: Constructor[]);
|
|
11
|
+
process(events: readonly ScenarioEvent[]): Promise<Map<string, ReadModelState<TReadModel>>>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ReducerReadModelProcessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReducerReadModelProcessor.d.ts","sourceRoot":"","sources":["../../testing/ReducerReadModelProcessor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,4FAA4F;AAC5F,qBAAa,yBAAyB,CAAC,UAAU,SAAS,MAAM,CAAE,YAAW,mBAAmB,CAAC,UAAU,CAAC;IAG5F,OAAO,CAAC,QAAQ,CAAC,cAAc;IAA2B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFnG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAErD,YAA6B,cAAc,EAAE,WAAW,CAAC,UAAU,CAAC,EAAmB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,EAE1I;IAEK,OAAO,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAoBhG;CACJ"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
import { ReducerEventDispatcher } from '../reducers/ReducerEventDispatcher.js';
|
|
4
|
+
/** Preserves the SDK reducer dispatch and the scenario's null-versus-deletion semantics. */
|
|
5
|
+
export class ReducerReadModelProcessor {
|
|
6
|
+
_readModelType;
|
|
7
|
+
_reducerType;
|
|
8
|
+
_dispatcher;
|
|
9
|
+
constructor(_readModelType, _reducerType, eventTypes) {
|
|
10
|
+
this._readModelType = _readModelType;
|
|
11
|
+
this._reducerType = _reducerType;
|
|
12
|
+
this._dispatcher = new ReducerEventDispatcher(_reducerType, eventTypes);
|
|
13
|
+
}
|
|
14
|
+
async process(events) {
|
|
15
|
+
const results = new Map();
|
|
16
|
+
const reducer = new this._reducerType();
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
const handler = this._dispatcher.handlerFor(event.context.eventType.id.value);
|
|
19
|
+
if (!handler)
|
|
20
|
+
continue;
|
|
21
|
+
const prior = results.get(event.sourceId);
|
|
22
|
+
const previous = prior?.deleted ? undefined : prior?.instance;
|
|
23
|
+
let next;
|
|
24
|
+
try {
|
|
25
|
+
next = await this._dispatcher.invoke(reducer, handler, event.content, previous, event.context);
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
throw new Error(`Reducer '${this._reducerType.name}' for read model '${this._readModelType.name}' failed`, { cause });
|
|
29
|
+
}
|
|
30
|
+
results.set(event.sourceId, {
|
|
31
|
+
instance: next === undefined ? null : next,
|
|
32
|
+
deleted: next === undefined
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ReducerReadModelProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReducerReadModelProcessor.js","sourceRoot":"","sources":["../../testing/ReducerReadModelProcessor.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAGrG,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAK/E,4FAA4F;AAC5F,MAAM,OAAO,yBAAyB;IAGL,cAAc;IAA4C,YAAY;IAFlF,WAAW,CAAyB;IAErD,YAA6B,cAAuC,EAAmB,YAAyB,EAAE,UAAyB;8BAA9G,cAAc;4BAA4C,YAAY;QAC/F,IAAI,CAAC,WAAW,GAAG,IAAI,sBAAsB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAgC;QAC1C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAK,IAAI,CAAC,YAAmD,EAAE,CAAC;QAChF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;YAC9E,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC;YAC9D,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACD,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACnG,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,IAAI,qBAAqB,IAAI,CAAC,cAAc,CAAC,IAAI,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1H,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE;gBACxB,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAyB;gBAC/D,OAAO,EAAE,IAAI,KAAK,SAAS;aAC9B,CAAC,CAAC;QACP,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScenarioEvent.d.ts","sourceRoot":"","sources":["../../testing/ScenarioEvent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,oDAAoD;AACpD,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScenarioEvent.js","sourceRoot":"","sources":["../../testing/ScenarioEvent.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG"}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ReadModelScenario } from './ReadModelScenario.js';
|
|
2
2
|
export { ReadModelScenarioGiven } from './ReadModelScenarioGiven.js';
|
|
3
3
|
export { ReadModelScenarioGivenBuilder } from './ReadModelScenarioGivenBuilder.js';
|
|
4
|
+
export { UnsupportedProjectionOperation } from './projections/UnsupportedProjectionOperation.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../testing/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../testing/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAC"}
|
package/dist/testing/index.js
CHANGED
|
@@ -3,4 +3,5 @@
|
|
|
3
3
|
export { ReadModelScenario } from './ReadModelScenario.js';
|
|
4
4
|
export { ReadModelScenarioGiven } from './ReadModelScenarioGiven.js';
|
|
5
5
|
export { ReadModelScenarioGivenBuilder } from './ReadModelScenarioGivenBuilder.js';
|
|
6
|
+
export { UnsupportedProjectionOperation } from './projections/UnsupportedProjectionOperation.js';
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../testing/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../testing/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAC"}
|
|
@@ -10,8 +10,11 @@ export declare class ProjectionCapabilities {
|
|
|
10
10
|
static validate(compiled: CompiledProjectionDefinitions, definition: ProjectionDefinition): void;
|
|
11
11
|
private static checkAutoMap;
|
|
12
12
|
private static checkMapping;
|
|
13
|
+
private static compatible;
|
|
14
|
+
private static checkInitialValue;
|
|
13
15
|
private static propertyAt;
|
|
14
16
|
private static numeric;
|
|
17
|
+
private static checkProtection;
|
|
15
18
|
private static checkSchema;
|
|
16
19
|
}
|
|
17
20
|
//# sourceMappingURL=ProjectionCapabilities.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectionCapabilities.d.ts","sourceRoot":"","sources":["../../../testing/projections/ProjectionCapabilities.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEjF,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"ProjectionCapabilities.d.ts","sourceRoot":"","sources":["../../../testing/projections/ProjectionCapabilities.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,KAAK,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEjF,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAOxG,gGAAgG;AAChG,qBAAa,sBAAsB;IAC/B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,6BAA6B,EAAE,UAAU,EAAE,oBAAoB,GAAG,IAAI,CA6F/F;IAED,OAAO,CAAC,MAAM,CAAC,YAAY;IAa3B,OAAO,CAAC,MAAM,CAAC,YAAY;IA0E3B,OAAO,CAAC,MAAM,CAAC,UAAU;IAczB,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAuBhC,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB,OAAO,CAAC,MAAM,CAAC,OAAO;IAKtB,OAAO,CAAC,MAAM,CAAC,eAAe;IAS9B,OAAO,CAAC,MAAM,CAAC,WAAW;CAiB7B"}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import { AutoMap } from '@cratis/chronicle.contracts';
|
|
4
4
|
import { EventSequenceId } from '../../eventSequences/EventSequenceId.js';
|
|
5
5
|
import { eventContractPath } from '../../projections/eventContractPath.js';
|
|
6
|
-
import { eventContextPropertyExpression } from '../../projections/eventContextPropertyExpression.js';
|
|
7
|
-
import { InvalidEventContextPropertyError } from '../../projections/InvalidEventContextPropertyError.js';
|
|
8
6
|
import { getEventTypeMapKey } from '../../projections/modelBound/childrenAndNestedBuilder.js';
|
|
9
7
|
import { UnsupportedProjectionOperation } from './UnsupportedProjectionOperation.js';
|
|
10
8
|
/** Validates *all* subscribed operations against the bounded scenario subset, before replay. */
|
|
@@ -34,6 +32,8 @@ export class ProjectionCapabilities {
|
|
|
34
32
|
reject('Variant', 'variants require a kernel-backed test');
|
|
35
33
|
if (wire.IsActive === false)
|
|
36
34
|
reject('IsActive', 'passive projections require a kernel-backed test');
|
|
35
|
+
if (wire.SubscribesToAllEvents === true)
|
|
36
|
+
reject('SubscribesToAllEvents', 'subscribe-to-all projections require a kernel-backed test');
|
|
37
37
|
if (wire.EventSequenceId !== EventSequenceId.eventLog.value)
|
|
38
38
|
reject('EventSequenceId', 'non-default event sequences require a kernel-backed test');
|
|
39
39
|
for (const section of ['Join', 'Children', 'Nested', 'RemovedWithJoin']) {
|
|
@@ -61,11 +61,17 @@ export class ProjectionCapabilities {
|
|
|
61
61
|
catch {
|
|
62
62
|
reject('ReadModel.Schema', 'read-model schema is not valid JSON');
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
(identifier.type === '
|
|
68
|
-
reject('ReadModel.Schema.id', '
|
|
64
|
+
this.checkProtection(schema, 'ReadModel.Schema', reject);
|
|
65
|
+
const identifier = schema.properties?.id;
|
|
66
|
+
if (!identifier || !((identifier.type === 'string' && (!identifier.format || identifier.format === 'guid')) ||
|
|
67
|
+
(identifier.type === 'number' && identifier.format === 'double'))) {
|
|
68
|
+
reject('ReadModel.Schema.id', 'only a lowercase id with a fixture-backed string, GUID, or number/double schema is supported; other identifiers require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
69
|
+
}
|
|
70
|
+
const names = Object.keys(schema.properties ?? {});
|
|
71
|
+
for (const [index, name] of names.entries()) {
|
|
72
|
+
if (names.slice(index + 1).some(other => other.toLowerCase() === name.toLowerCase())) {
|
|
73
|
+
reject(`ReadModel.Schema.${name}`, 'case-insensitively colliding read-model properties require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
74
|
+
}
|
|
69
75
|
}
|
|
70
76
|
let initial;
|
|
71
77
|
try {
|
|
@@ -80,14 +86,7 @@ export class ProjectionCapabilities {
|
|
|
80
86
|
const target = schema.properties?.[property]
|
|
81
87
|
?? reject(`InitialModelState.${property}`, 'initial value has no read-model schema');
|
|
82
88
|
this.checkSchema(target, `InitialModelState.${property}`, reject);
|
|
83
|
-
|
|
84
|
-
const outsideIntegerRange = !Number.isSafeInteger(value) ||
|
|
85
|
-
(target.format === 'int32' && (value < -2147483648 || value > 2147483647)) ||
|
|
86
|
-
(target.format === 'uint32' && (value < 0 || value > 4294967295));
|
|
87
|
-
if (!Number.isFinite(value) || (target.type === 'integer' && outsideIntegerRange)) {
|
|
88
|
-
reject(`InitialModelState.${property}`, 'initial numeric value is outside the supported finite/integer range');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
89
|
+
this.checkInitialValue(value, target, `InitialModelState.${property}`, reject);
|
|
91
90
|
}
|
|
92
91
|
const generations = new Map();
|
|
93
92
|
const requireEventSchema = (eventType, path) => {
|
|
@@ -100,6 +99,11 @@ export class ProjectionCapabilities {
|
|
|
100
99
|
};
|
|
101
100
|
const from = wire.From ?? [];
|
|
102
101
|
const removedWith = wire.RemovedWith ?? [];
|
|
102
|
+
for (const entry of from) {
|
|
103
|
+
if (removedWith.some(removal => removal.Key.Id === entry.Key.Id)) {
|
|
104
|
+
reject(eventContractPath('From', entry.Key), 'events subscribed through both From and RemovedWith require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
103
107
|
for (const entry of [...from, ...removedWith]) {
|
|
104
108
|
const section = from.includes(entry) ? 'From' : 'RemovedWith';
|
|
105
109
|
const path = eventContractPath(section, entry.Key);
|
|
@@ -115,16 +119,15 @@ export class ProjectionCapabilities {
|
|
|
115
119
|
const mappingPath = `${path}.Properties.${property}`;
|
|
116
120
|
this.checkMapping(schema, eventSchema, property, expression, mappingPath, reject);
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
const aggregateOnly = expressions.length > 0 && expressions.every(expression => /^(?:\$add\([^()]+\)|\$subtract\([^()]+\)|\$count|\$increment|\$decrement)$/.test(expression));
|
|
120
|
-
if (wire.AutoMap === AutoMap.Enabled && !aggregateOnly) {
|
|
122
|
+
if (wire.AutoMap !== AutoMap.Disabled) {
|
|
121
123
|
this.checkAutoMap(schema, eventSchema, properties, wire.NoAutoMapProperties ?? [], path, reject);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
static checkAutoMap(modelSchema, eventSchema, explicit, exclusions, path, reject) {
|
|
126
128
|
for (const [destination] of Object.entries(modelSchema.properties ?? {})) {
|
|
127
|
-
if (
|
|
129
|
+
if (Object.keys(explicit).some(name => name.toLowerCase() === destination.toLowerCase()) ||
|
|
130
|
+
exclusions.some(name => name.toLowerCase() === destination.toLowerCase()))
|
|
128
131
|
continue;
|
|
129
132
|
const candidates = Object.keys(eventSchema.properties ?? {}).filter(source => source.toLowerCase() === destination.toLowerCase());
|
|
130
133
|
if (candidates.length > 1)
|
|
@@ -139,31 +142,41 @@ export class ProjectionCapabilities {
|
|
|
139
142
|
?? fail('dynamic or unknown destination paths require a kernel-backed test');
|
|
140
143
|
if (destination.includes('.') || destination.includes('$'))
|
|
141
144
|
fail('dynamic or unknown destination paths require a kernel-backed test');
|
|
145
|
+
if (destination.toLowerCase() === 'id' || Object.keys(modelSchema.properties ?? {}).some(name => name !== destination && name.toLowerCase() === destination.toLowerCase())) {
|
|
146
|
+
fail('identifier or case-insensitively colliding target mappings require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
147
|
+
}
|
|
148
|
+
if (target.type === 'object' || target.type === 'array')
|
|
149
|
+
fail('object/array target mappings require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
142
150
|
this.checkSchema(target, path, (_path, reason) => fail(reason));
|
|
143
|
-
if (expression === '$eventSourceId'
|
|
151
|
+
if (expression === '$eventSourceId') {
|
|
152
|
+
if (target.type !== 'string' || (target.format && target.format !== 'guid')) {
|
|
153
|
+
fail('$eventSourceId requires a string or GUID target; other targets require a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (expression === '$null')
|
|
144
158
|
return;
|
|
145
159
|
if (expression.startsWith('$eventContext(')) {
|
|
160
|
+
if (expression === '$eventContext(Occurred)')
|
|
161
|
+
fail('raw Occurred is converted inconsistently by the kernel; only its Year, Month and Day paths are supported');
|
|
146
162
|
if (/^\$eventContext\(.+\(\)\)$/.test(expression))
|
|
147
163
|
fail('derived event-context functions require a kernel-backed test');
|
|
148
164
|
const path = /^\$eventContext\(([^()]*)\)$/.exec(expression)?.[1];
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
fail('event-context path is not supported by the client');
|
|
165
|
+
const strings = ['EventSourceId', 'EventStore', 'Namespace', 'EventSourceType', 'EventStreamType',
|
|
166
|
+
'EventStreamId', 'Subject', 'Subject.Value', 'Hash', 'CorrelationId', 'CorrelationId.Value',
|
|
167
|
+
'CausedBy.Subject', 'CausedBy.Name', 'CausedBy.UserName', 'EventType.Id.Value', 'SequenceNumber', 'SequenceNumber.Value'];
|
|
168
|
+
const integers = ['Occurred.Year', 'Occurred.Month', 'Occurred.Day', 'EventType.Generation.Value'];
|
|
169
|
+
if (path && ((strings.includes(path) && target.type === 'string' && !target.format) ||
|
|
170
|
+
(integers.includes(path) && target.type === 'integer' && target.format === 'int32')))
|
|
171
|
+
return;
|
|
172
|
+
fail('event-context path and target schema require a kernel-backed test');
|
|
160
173
|
}
|
|
161
174
|
if (/^\$value\([\p{L}\p{Mn}\p{Nd}\p{Pc} ._/:*+-]*\)$/u.test(expression)) {
|
|
162
175
|
const text = expression.slice(7, -1);
|
|
163
176
|
if (target.format === 'date-time')
|
|
164
177
|
fail('$value date-time literals require a kernel-backed test');
|
|
165
178
|
if (this.numeric(target)) {
|
|
166
|
-
if (
|
|
179
|
+
if (!(target.type === 'integer' ? /^[+-]?\d+$/.test(text) : /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/.test(text)) || !Number.isFinite(Number(text)) ||
|
|
167
180
|
(target.type === 'integer' && (!Number.isSafeInteger(Number(text)) ||
|
|
168
181
|
(target.format === 'int32' && (Number(text) < -2147483648 || Number(text) > 2147483647)) ||
|
|
169
182
|
(target.format === 'uint32' && (Number(text) < 0 || Number(text) > 4294967295))))) {
|
|
@@ -183,21 +196,21 @@ export class ProjectionCapabilities {
|
|
|
183
196
|
}
|
|
184
197
|
const operation = /^(\$add|\$subtract)\(([^()]+)\)$/.exec(expression);
|
|
185
198
|
const arithmetic = operation || ['$count', '$increment', '$decrement'].includes(expression);
|
|
186
|
-
if (arithmetic)
|
|
187
|
-
|
|
188
|
-
fail('arithmetic requires a supported finite number/double or int32/uint32 schema (not float, decimal, duration, or int64)');
|
|
189
|
-
if (operation) {
|
|
190
|
-
const operand = this.propertyAt(eventSchema, operation[2]);
|
|
191
|
-
if (!operand || !this.numeric(operand))
|
|
192
|
-
fail('arithmetic operand requires a supported numeric event schema');
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
199
|
+
if (arithmetic)
|
|
200
|
+
fail('arithmetic requires a kernel-backed test (ChronicleKernelScenario / live kernel)');
|
|
196
201
|
if (/^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*$/.test(expression)) {
|
|
202
|
+
if (['true', 'True', 'false', 'False'].includes(expression)) {
|
|
203
|
+
fail('kernel resolves this expression as a boolean literal before event content');
|
|
204
|
+
}
|
|
205
|
+
if (expression.includes('.'))
|
|
206
|
+
fail('nested event property paths require a kernel-backed test');
|
|
197
207
|
const source = this.propertyAt(eventSchema, expression);
|
|
198
208
|
if (!source)
|
|
199
209
|
return fail(`event property '${expression}' is absent from the participating event schema`);
|
|
200
210
|
this.checkSchema(source, path, (_path, reason) => fail(reason));
|
|
211
|
+
if (!this.compatible(source, target)) {
|
|
212
|
+
fail(`source '${expression}' (${source.type}${source.format ? `/${source.format}` : ''}) to target '${destination}' (${target.type}${target.format ? `/${target.format}` : ''}) requires a kernel-backed test`);
|
|
213
|
+
}
|
|
201
214
|
return;
|
|
202
215
|
}
|
|
203
216
|
if (expression.startsWith('$context.')) {
|
|
@@ -205,6 +218,52 @@ export class ProjectionCapabilities {
|
|
|
205
218
|
}
|
|
206
219
|
fail('requires a kernel-backed test');
|
|
207
220
|
}
|
|
221
|
+
static compatible(source, target) {
|
|
222
|
+
if (JSON.stringify(source.type) === JSON.stringify(target.type) && source.format === target.format) {
|
|
223
|
+
return source.type !== 'object' && source.type !== 'array';
|
|
224
|
+
}
|
|
225
|
+
if (source.type === 'string' && !source.format && Array.isArray(target.type) &&
|
|
226
|
+
target.type[0] === 'string' && target.type[1] === 'null')
|
|
227
|
+
return true;
|
|
228
|
+
if (source.type === 'string' && !source.format &&
|
|
229
|
+
((target.type === 'number' && (!target.format || target.format === 'double')) ||
|
|
230
|
+
(target.type === 'integer' && ['int32', 'uint32'].includes(target.format ?? ''))))
|
|
231
|
+
return true;
|
|
232
|
+
if (source.type === 'string' && source.format === 'guid' && target.type === 'string' && !target.format)
|
|
233
|
+
return true;
|
|
234
|
+
return source.type === 'integer' && ['int32', 'uint32'].includes(source.format ?? '') &&
|
|
235
|
+
target.type === 'number' && (!target.format || target.format === 'double');
|
|
236
|
+
}
|
|
237
|
+
static checkInitialValue(value, target, path, reject) {
|
|
238
|
+
if (value === null && Array.isArray(target.type) && target.type.includes('null'))
|
|
239
|
+
return;
|
|
240
|
+
if (target.type === 'object' || target.type === 'array' || target.format === 'date-time') {
|
|
241
|
+
reject(path, 'initial object, array, or date-time values require a kernel-backed test');
|
|
242
|
+
}
|
|
243
|
+
if (target.type === 'integer' || target.type === 'number') {
|
|
244
|
+
if (typeof value !== 'number')
|
|
245
|
+
reject(path, 'initial numeric value must be a JSON number');
|
|
246
|
+
const outsideIntegerRange = !Number.isSafeInteger(value) ||
|
|
247
|
+
(target.format === 'int32' && (value < -2147483648 || value > 2147483647)) ||
|
|
248
|
+
(target.format === 'uint32' && (value < 0 || value > 4294967295));
|
|
249
|
+
if (!Number.isFinite(value) || (target.type === 'integer' && outsideIntegerRange)) {
|
|
250
|
+
reject(path, 'initial numeric value is outside the supported finite/integer range');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (target.type === 'boolean') {
|
|
254
|
+
if (typeof value !== 'boolean')
|
|
255
|
+
reject(path, 'initial boolean value must be a JSON boolean');
|
|
256
|
+
}
|
|
257
|
+
else if (target.type === 'string' || Array.isArray(target.type)) {
|
|
258
|
+
if (typeof value !== 'string')
|
|
259
|
+
reject(path, 'initial string value must be a JSON string');
|
|
260
|
+
if (target.format === 'guid' && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value)) {
|
|
261
|
+
reject(path, 'initial GUID value must be canonical lowercase text');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else
|
|
265
|
+
reject(path, 'initial value requires a kernel-backed test');
|
|
266
|
+
}
|
|
208
267
|
static propertyAt(schema, path) {
|
|
209
268
|
return path.split('.').reduce((current, segment) => current?.properties?.[segment], schema);
|
|
210
269
|
}
|
|
@@ -212,8 +271,23 @@ export class ProjectionCapabilities {
|
|
|
212
271
|
return (schema.type === 'number' && (!schema.format || schema.format === 'double')) ||
|
|
213
272
|
(schema.type === 'integer' && ['int32', 'uint32'].includes(schema.format ?? ''));
|
|
214
273
|
}
|
|
274
|
+
static checkProtection(schema, path, reject) {
|
|
275
|
+
if (schema.compliance?.length || schema.security?.length)
|
|
276
|
+
reject(path, 'protected fields require a kernel-backed test');
|
|
277
|
+
for (const [name, property] of Object.entries(schema.properties ?? {}))
|
|
278
|
+
this.checkProtection(property, `${path}.${name}`, reject);
|
|
279
|
+
if (schema.items)
|
|
280
|
+
this.checkProtection(schema.items, `${path}.items`, reject);
|
|
281
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
282
|
+
this.checkProtection(schema.additionalProperties, `${path}.additionalProperties`, reject);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
215
285
|
static checkSchema(schema, path, reject) {
|
|
216
|
-
if (
|
|
286
|
+
if (schema.compliance?.length || schema.security?.length)
|
|
287
|
+
reject(path, 'protected fields require a kernel-backed test');
|
|
288
|
+
if (!schema.type || schema.type === 'null' || (Array.isArray(schema.type) &&
|
|
289
|
+
(schema.type.length !== 2 || schema.type[0] !== 'string' || schema.type[1] !== 'null')) ||
|
|
290
|
+
(schema.format && !['guid', 'date-time', 'double', 'int32', 'uint32'].includes(schema.format)) ||
|
|
217
291
|
(schema.format === 'date-time' && schema.type !== 'string') ||
|
|
218
292
|
(schema.format === 'guid' && schema.type !== 'string') ||
|
|
219
293
|
(schema.format === 'double' && schema.type !== 'number') ||
|