@cratis/chronicle 6.16.1 → 6.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +1 -0
- package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
- package/dist/projections/ProjectionDefinitionCompiler.js +130 -4
- 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/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 +10 -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.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js +9 -1
- package/dist/projections/declarative/ProjectionBuilderCore.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/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/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/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +12 -3
- package/dist/reactors/Reactors.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 +2 -2
- package/projections/CompiledProjectionDefinitions.ts +6 -0
- package/projections/ProjectionCapabilityProvenance.ts +10 -0
- package/projections/ProjectionDefinitionCompiler.registration.spec.ts +4 -2
- package/projections/ProjectionDefinitionCompiler.ts +127 -4
- package/projections/ProjectionEventSchema.ts +13 -0
- package/projections/Projections.childrenAndNested.spec.ts +250 -1
- package/projections/VariantReclassifier.ts +2 -0
- package/projections/captureProjectionProvenance.ts +50 -0
- package/projections/declarative/AddChildBuilder.ts +33 -2
- package/projections/declarative/FromBuilder.ts +18 -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 +8 -1
- package/projections/declarative/addChildCompatibility.standard.spec.ts +80 -0
- package/projections/declarative/projectionBuilderProvenance.ts +40 -0
- package/projections/eventContextExpressions.spec.ts +20 -0
- package/projections/eventContractPath.ts +9 -0
- package/projections/modelBound/childrenAndNestedBuilder.ts +45 -11
- package/reactors/Reactors.ts +13 -3
- package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +93 -2
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
|
|
5
|
+
import { eventContextPropertyExpression } from '../eventContextPropertyExpression.js';
|
|
5
6
|
import { IAddChildBuilder } from './IAddChildBuilder.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -14,8 +15,10 @@ export interface ChildAdditionEntry {
|
|
|
14
15
|
targetProperty: string;
|
|
15
16
|
/** The child model property used to identify instances, when set via the builder callback overload. */
|
|
16
17
|
identifiedBy?: string;
|
|
17
|
-
/** The event property used as the key, when set via the builder callback overload. */
|
|
18
|
+
/** The event property or context expression used as the key, when set via the builder callback overload. */
|
|
18
19
|
usingKey?: string;
|
|
20
|
+
/** The event context expression used as the parent key, when set via the builder callback overload. */
|
|
21
|
+
usingParentKey?: string;
|
|
19
22
|
/** The event property whose value becomes the child directly, when set via the plain accessor overload. */
|
|
20
23
|
fromEventProperty?: string;
|
|
21
24
|
}
|
|
@@ -36,8 +39,9 @@ export class AddChildBuilder<TChildModel, TEvent> implements IAddChildBuilder<TC
|
|
|
36
39
|
private _usedAsBuilder = false;
|
|
37
40
|
private _identifiedByProperty: string | undefined;
|
|
38
41
|
private _usingKeyProperty: string | undefined;
|
|
42
|
+
private _usingParentKeyProperty: string | undefined;
|
|
39
43
|
|
|
40
|
-
/** Whether any builder member (`identifiedBy
|
|
44
|
+
/** Whether any builder member (`identifiedBy`, `usingKey`, `usingKeyFromContext`, or `usingParentKeyFromContext`) was invoked on the probe. */
|
|
41
45
|
get usedAsBuilder(): boolean {
|
|
42
46
|
return this._usedAsBuilder;
|
|
43
47
|
}
|
|
@@ -52,6 +56,11 @@ export class AddChildBuilder<TChildModel, TEvent> implements IAddChildBuilder<TC
|
|
|
52
56
|
return this._usingKeyProperty;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
/** The parent key expression captured via `usingParentKeyFromContext()`, if any. */
|
|
60
|
+
get usingParentKeyProperty(): string | undefined {
|
|
61
|
+
return this._usingParentKeyProperty;
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
/** The event property path captured when the probe was used as a plain accessor. */
|
|
56
65
|
get capturedEventProperty(): string {
|
|
57
66
|
return this._pathHandler.property;
|
|
@@ -77,10 +86,26 @@ export class AddChildBuilder<TChildModel, TEvent> implements IAddChildBuilder<TC
|
|
|
77
86
|
return this;
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
/** @inheritdoc */
|
|
90
|
+
usingKeyFromContext(contextPropertyName: string): IAddChildBuilder<TChildModel, TEvent> {
|
|
91
|
+
this._usingKeyProperty = eventContextPropertyExpression(contextPropertyName);
|
|
92
|
+
this._usedAsBuilder = true;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @inheritdoc */
|
|
97
|
+
usingParentKeyFromContext(contextPropertyName: string): IAddChildBuilder<TChildModel, TEvent> {
|
|
98
|
+
this._usingParentKeyProperty = eventContextPropertyExpression(contextPropertyName);
|
|
99
|
+
this._usedAsBuilder = true;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
80
103
|
/**
|
|
81
104
|
* Proxy `get` trap: known builder members dispatch to the real implementation above;
|
|
82
105
|
* anything else (a plain event property accessed by the accessor overload) falls through
|
|
83
106
|
* to path-capturing so the accessor overload keeps working against the same probe.
|
|
107
|
+
* The builder member names are therefore reserved: an event property with one of these names
|
|
108
|
+
* cannot be selected through the accessor overload.
|
|
84
109
|
* @param _target - The proxy's (unused) empty target object.
|
|
85
110
|
* @param prop - The accessed property name.
|
|
86
111
|
* @returns The resolved member.
|
|
@@ -92,6 +117,12 @@ export class AddChildBuilder<TChildModel, TEvent> implements IAddChildBuilder<TC
|
|
|
92
117
|
if (prop === 'usingKey') {
|
|
93
118
|
return this.usingKey.bind(this);
|
|
94
119
|
}
|
|
120
|
+
if (prop === 'usingKeyFromContext') {
|
|
121
|
+
return this.usingKeyFromContext.bind(this);
|
|
122
|
+
}
|
|
123
|
+
if (prop === 'usingParentKeyFromContext') {
|
|
124
|
+
return this.usingParentKeyFromContext.bind(this);
|
|
125
|
+
}
|
|
95
126
|
return Reflect.get(this._pathProxy as object, prop);
|
|
96
127
|
}
|
|
97
128
|
}
|
|
@@ -15,6 +15,7 @@ import { ISetBuilder } from './ISetBuilder.js';
|
|
|
15
15
|
import { ISubtractBuilder } from './ISubtractBuilder.js';
|
|
16
16
|
import { SetBuilder } from './SetBuilder.js';
|
|
17
17
|
import { SubtractBuilder } from './SubtractBuilder.js';
|
|
18
|
+
import { recordKeyDeclaration } from './projectionBuilderProvenance.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Accumulated property mapping for a from clause.
|
|
@@ -58,12 +59,14 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
58
59
|
const proxy = new Proxy({}, handler);
|
|
59
60
|
keyAccessor(proxy as TEvent);
|
|
60
61
|
this.entry.key = handler.property;
|
|
62
|
+
recordKeyDeclaration(this, 'Key', '.from().usingKey');
|
|
61
63
|
return this;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/** @inheritdoc */
|
|
65
67
|
usingKeyFromContext(contextPropertyName: string): this {
|
|
66
68
|
this.entry.key = eventContextPropertyExpression(contextPropertyName);
|
|
69
|
+
recordKeyDeclaration(this, 'Key', '.from().usingKeyFromContext');
|
|
67
70
|
return this;
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -73,12 +76,14 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
73
76
|
const proxy = new Proxy({}, handler);
|
|
74
77
|
keyAccessor(proxy as TEvent);
|
|
75
78
|
this.entry.parentKey = handler.property;
|
|
79
|
+
recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKey');
|
|
76
80
|
return this;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
/** @inheritdoc */
|
|
80
84
|
usingParentKeyFromContext(contextPropertyName: string): this {
|
|
81
85
|
this.entry.parentKey = eventContextPropertyExpression(contextPropertyName);
|
|
86
|
+
recordKeyDeclaration(this, 'ParentKey', '.from().usingParentKeyFromContext');
|
|
82
87
|
return this;
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -87,6 +92,7 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
87
92
|
const compositeKeyBuilder = new CompositeKeyBuilder<TKeyType, TEvent>();
|
|
88
93
|
builderCallback(compositeKeyBuilder);
|
|
89
94
|
this.entry.key = compositeKeyBuilder.build();
|
|
95
|
+
recordKeyDeclaration(this, 'Key', '.from().usingCompositeKey');
|
|
90
96
|
return this;
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -95,18 +101,21 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
95
101
|
const compositeKeyBuilder = new CompositeKeyBuilder<TKeyType, TEvent>();
|
|
96
102
|
builderCallback(compositeKeyBuilder);
|
|
97
103
|
this.entry.parentKey = compositeKeyBuilder.build();
|
|
104
|
+
recordKeyDeclaration(this, 'ParentKey', '.from().usingParentCompositeKey');
|
|
98
105
|
return this;
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
/** @inheritdoc */
|
|
102
109
|
usingConstantKey(value: string): this {
|
|
103
110
|
this.entry.key = constantValueExpression(value);
|
|
111
|
+
recordKeyDeclaration(this, 'Key', '.from().usingConstantKey');
|
|
104
112
|
return this;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
/** @inheritdoc */
|
|
108
116
|
usingConstantParentKey(value: string): this {
|
|
109
117
|
this.entry.parentKey = constantValueExpression(value);
|
|
118
|
+
recordKeyDeclaration(this, 'ParentKey', '.from().usingConstantParentKey');
|
|
110
119
|
return this;
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -162,6 +171,14 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
/** @inheritdoc */
|
|
174
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, builderCallback: (builder: IAddChildBuilder<TChildModel, TEvent> & TEvent) => void): this;
|
|
175
|
+
/** @inheritdoc */
|
|
176
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, eventPropertyAccessor: PropertyAccessor<TEvent>): this;
|
|
177
|
+
/** @inheritdoc */
|
|
178
|
+
addChild<TChildModel>(
|
|
179
|
+
targetPropertyAccessor: PropertyAccessor<TReadModel>,
|
|
180
|
+
eventPropertyAccessorOrBuilderCallback: PropertyAccessor<TEvent> | ((builder: IAddChildBuilder<TChildModel, TEvent>) => void)
|
|
181
|
+
): this;
|
|
165
182
|
addChild<TChildModel>(
|
|
166
183
|
targetPropertyAccessor: PropertyAccessor<TReadModel>,
|
|
167
184
|
eventPropertyAccessorOrBuilderCallback: PropertyAccessor<TEvent> | ((builder: IAddChildBuilder<TChildModel, TEvent>) => void)
|
|
@@ -175,7 +192,7 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
175
192
|
(eventPropertyAccessorOrBuilderCallback as (value: unknown) => void)(probeProxy);
|
|
176
193
|
|
|
177
194
|
this.entry.children.push(probe.usedAsBuilder
|
|
178
|
-
? { targetProperty: targetHandler.property, identifiedBy: probe.identifiedByProperty, usingKey: probe.usingKeyProperty }
|
|
195
|
+
? { targetProperty: targetHandler.property, identifiedBy: probe.identifiedByProperty, usingKey: probe.usingKeyProperty, usingParentKey: probe.usingParentKeyProperty }
|
|
179
196
|
: { targetProperty: targetHandler.property, fromEventProperty: probe.capturedEventProperty });
|
|
180
197
|
return this;
|
|
181
198
|
}
|
|
@@ -22,4 +22,18 @@ export interface IAddChildBuilder<TChildModel, TEvent> {
|
|
|
22
22
|
* @returns This builder for fluent chaining.
|
|
23
23
|
*/
|
|
24
24
|
usingKey(eventPropertyAccessor: PropertyAccessor<TEvent>): IAddChildBuilder<TChildModel, TEvent>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Specifies an event context property used as the key when adding a child.
|
|
28
|
+
* @param contextPropertyName - Property path on EventContext, using TypeScript casing.
|
|
29
|
+
* @returns This builder for fluent chaining.
|
|
30
|
+
*/
|
|
31
|
+
usingKeyFromContext(contextPropertyName: string): IAddChildBuilder<TChildModel, TEvent>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Specifies an event context property used as the parent key when adding a child.
|
|
35
|
+
* @param contextPropertyName - Property path on EventContext, using TypeScript casing.
|
|
36
|
+
* @returns This builder for fluent chaining.
|
|
37
|
+
*/
|
|
38
|
+
usingParentKeyFromContext(contextPropertyName: string): IAddChildBuilder<TChildModel, TEvent>;
|
|
25
39
|
}
|
|
@@ -107,7 +107,24 @@ export interface IReadModelPropertiesBuilder<TReadModel, TEvent, TBuilder> {
|
|
|
107
107
|
count(readModelPropertyAccessor: PropertyAccessor<TReadModel>): TBuilder;
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* Adds a child model
|
|
110
|
+
* Adds a child model configured through a child builder to a collection on the read model.
|
|
111
|
+
* Event members remain available while TypeScript resolves the two function-shaped overloads.
|
|
112
|
+
* @param targetPropertyAccessor - Accessor for the collection property on the read model.
|
|
113
|
+
* @param builderCallback - Callback configuring the child identifier and keys.
|
|
114
|
+
* @returns This builder for fluent chaining.
|
|
115
|
+
*/
|
|
116
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, builderCallback: (builder: IAddChildBuilder<TChildModel, TEvent> & TEvent) => void): TBuilder;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Adds a child model directly from an event property to a collection on the read model.
|
|
120
|
+
* @param targetPropertyAccessor - Accessor for the collection property on the read model.
|
|
121
|
+
* @param eventPropertyAccessor - Accessor for the event property holding the child value.
|
|
122
|
+
* @returns This builder for fluent chaining.
|
|
123
|
+
*/
|
|
124
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, eventPropertyAccessor: PropertyAccessor<TEvent>): TBuilder;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Retains support for arbitrary read model accessors and explicitly selected child types.
|
|
111
128
|
* @param targetPropertyAccessor - Accessor for the collection property on the read model.
|
|
112
129
|
* @param eventPropertyAccessorOrBuilderCallback - Either a direct event property accessor or a builder callback.
|
|
113
130
|
* @returns This builder for fluent chaining.
|
|
@@ -170,6 +170,14 @@ export class JoinBuilder<TReadModel, TEvent> implements IJoinBuilder<TReadModel,
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/** @inheritdoc */
|
|
173
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, builderCallback: (builder: IAddChildBuilder<TChildModel, TEvent> & TEvent) => void): this;
|
|
174
|
+
/** @inheritdoc */
|
|
175
|
+
addChild<TChildModel>(targetPropertyAccessor: (model: TReadModel) => readonly TChildModel[] | null | undefined, eventPropertyAccessor: PropertyAccessor<TEvent>): this;
|
|
176
|
+
/** @inheritdoc */
|
|
177
|
+
addChild<TChildModel>(
|
|
178
|
+
targetPropertyAccessor: PropertyAccessor<TReadModel>,
|
|
179
|
+
eventPropertyAccessorOrBuilderCallback: PropertyAccessor<TEvent> | ((builder: IAddChildBuilder<TChildModel, TEvent>) => void)
|
|
180
|
+
): this;
|
|
173
181
|
addChild<TChildModel>(
|
|
174
182
|
targetPropertyAccessor: PropertyAccessor<TReadModel>,
|
|
175
183
|
eventPropertyAccessorOrBuilderCallback: PropertyAccessor<TEvent> | ((builder: IAddChildBuilder<TChildModel, TEvent>) => void)
|
|
@@ -183,7 +191,7 @@ export class JoinBuilder<TReadModel, TEvent> implements IJoinBuilder<TReadModel,
|
|
|
183
191
|
(eventPropertyAccessorOrBuilderCallback as (value: unknown) => void)(probeProxy);
|
|
184
192
|
|
|
185
193
|
this.entry.children.push(probe.usedAsBuilder
|
|
186
|
-
? { targetProperty: targetHandler.property, identifiedBy: probe.identifiedByProperty, usingKey: probe.usingKeyProperty }
|
|
194
|
+
? { targetProperty: targetHandler.property, identifiedBy: probe.identifiedByProperty, usingKey: probe.usingKeyProperty, usingParentKey: probe.usingParentKeyProperty }
|
|
187
195
|
: { targetProperty: targetHandler.property, fromEventProperty: probe.capturedEventProperty });
|
|
188
196
|
return this;
|
|
189
197
|
}
|
|
@@ -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 = {
|
|
@@ -249,7 +256,7 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
|
|
|
249
256
|
Key: eventType,
|
|
250
257
|
Value: {
|
|
251
258
|
Key: child.usingKey ?? '$eventSourceId',
|
|
252
|
-
ParentKey: '$eventSourceId',
|
|
259
|
+
ParentKey: child.usingParentKey ?? '$eventSourceId',
|
|
253
260
|
Properties: {}
|
|
254
261
|
}
|
|
255
262
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { eventType } from '../../events/eventTypeDecorator.js';
|
|
6
|
+
import { FromBuilder } from './FromBuilder.js';
|
|
7
|
+
import { IAddChildBuilder } from './IAddChildBuilder.js';
|
|
8
|
+
import { IReadModelPropertiesBuilder } from './IReadModelPropertiesBuilder.js';
|
|
9
|
+
import { JoinBuilder } from './JoinBuilder.js';
|
|
10
|
+
import { ProjectionBuilderFor } from './ProjectionBuilderFor.js';
|
|
11
|
+
|
|
12
|
+
class Line {
|
|
13
|
+
id = '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class Base {
|
|
17
|
+
id = '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Derived extends Base {
|
|
21
|
+
derivedId = '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class LineAdded {
|
|
25
|
+
line = new Line();
|
|
26
|
+
}
|
|
27
|
+
eventType()(LineAdded);
|
|
28
|
+
|
|
29
|
+
class Order {
|
|
30
|
+
id = '';
|
|
31
|
+
lines: Line[] = [];
|
|
32
|
+
nullableLines: Line[] | null = null;
|
|
33
|
+
bases: Base[] = [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// This checks the shared public interface independently of either concrete builder.
|
|
37
|
+
function acceptOriginalCallSites(builder: IReadModelPropertiesBuilder<Order, LineAdded, unknown>): void {
|
|
38
|
+
builder.addChild<Line>(model => model.nullableLines, event => event.line);
|
|
39
|
+
builder.addChild(model => model.nullableLines, child => child.identifiedBy(item => item.id));
|
|
40
|
+
builder.addChild<Derived>(model => model.bases, (event: LineAdded) => event.line);
|
|
41
|
+
builder.addChild<Derived>(model => model.bases, (child: IAddChildBuilder<Derived, LineAdded>) => child
|
|
42
|
+
.identifiedBy(item => item.derivedId)
|
|
43
|
+
.usingKeyFromContext('sequenceNumber'));
|
|
44
|
+
builder.addChild<Line>(model => model.lines, (event: LineAdded) => event.line);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('addChild source compatibility', () => {
|
|
48
|
+
it('should accept nullable targets and explicitly narrower child types on the shared interface', () => {
|
|
49
|
+
const builder: IReadModelPropertiesBuilder<Order, LineAdded, unknown> = new FromBuilder<Order, LineAdded>();
|
|
50
|
+
acceptOriginalCallSites(builder);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should accept the original call shapes on the concrete from builder', () => {
|
|
54
|
+
const builder = new FromBuilder<Order, LineAdded>();
|
|
55
|
+
builder.addChild<Line>(model => model.nullableLines, event => event.line);
|
|
56
|
+
builder.addChild<Derived>(model => model.bases, (child: IAddChildBuilder<Derived, LineAdded>) => child.identifiedBy(item => item.derivedId));
|
|
57
|
+
builder.addChild<Derived>(model => model.bases, (event: LineAdded) => event.line);
|
|
58
|
+
builder.addChild<Line>(model => model.lines, (event: LineAdded) => event.line);
|
|
59
|
+
expect(builder.entry.children).toHaveLength(4);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should accept the original call shapes on the concrete join builder', () => {
|
|
63
|
+
const builder = new JoinBuilder<Order, LineAdded>();
|
|
64
|
+
builder.addChild<Line>(model => model.nullableLines, event => event.line);
|
|
65
|
+
builder.addChild<Derived>(model => model.bases, (child: IAddChildBuilder<Derived, LineAdded>) => child.identifiedBy(item => item.derivedId));
|
|
66
|
+
builder.addChild<Derived>(model => model.bases, (event: LineAdded) => event.line);
|
|
67
|
+
builder.addChild<Line>(model => model.lines, (event: LineAdded) => event.line);
|
|
68
|
+
expect(builder.entry.children).toHaveLength(4);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should contextually type the unannotated builder callback in the documentation example', () => {
|
|
72
|
+
const projection = new ProjectionBuilderFor<Order>();
|
|
73
|
+
projection.from(LineAdded, from => from
|
|
74
|
+
.addChild(model => model.lines, child => child
|
|
75
|
+
.identifiedBy(item => item.id)
|
|
76
|
+
.usingKeyFromContext('sequenceNumber')
|
|
77
|
+
.usingParentKeyFromContext('eventSourceId')));
|
|
78
|
+
expect(projection.build('order', 'Order').Children).toHaveProperty('lines');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -182,6 +182,24 @@ describe('event context expressions', () => {
|
|
|
182
182
|
.From[0].Value.Key.should.equal('$eventContext(EventSourceId)');
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
it('should use context properties as addChild keys in a from clause', () => {
|
|
186
|
+
const child = declarative(builder => builder.from(Recorded, from => from.addChild<Child>(model => model.children, added => added
|
|
187
|
+
.identifiedBy(model => model.happened)
|
|
188
|
+
.usingKeyFromContext('sequenceNumber')
|
|
189
|
+
.usingParentKeyFromContext('eventSourceId'))));
|
|
190
|
+
child.Children.children.From[0].Value.Key.should.equal('$eventContext(SequenceNumber)');
|
|
191
|
+
child.Children.children.From[0].Value.ParentKey.should.equal('$eventContext(EventSourceId)');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should use context properties as addChild keys in a join clause', () => {
|
|
195
|
+
const child = declarative(builder => builder.join(Recorded, join => join.on(model => model.occurred)
|
|
196
|
+
.addChild<Child>(model => model.children, added => added
|
|
197
|
+
.usingKeyFromContext('eventSourceId')
|
|
198
|
+
.usingParentKeyFromContext('sequenceNumber'))));
|
|
199
|
+
child.Children.children.From[0].Value.Key.should.equal('$eventContext(EventSourceId)');
|
|
200
|
+
child.Children.children.From[0].Value.ParentKey.should.equal('$eventContext(SequenceNumber)');
|
|
201
|
+
});
|
|
202
|
+
|
|
185
203
|
it('should use a context property as a from parent key', () => {
|
|
186
204
|
declarative(builder => builder.from(Recorded, from => from.usingParentKeyFromContext('eventSourceId')))
|
|
187
205
|
.From[0].Value.ParentKey.should.equal('$eventContext(EventSourceId)');
|
|
@@ -255,6 +273,8 @@ describe('event context expressions', () => {
|
|
|
255
273
|
const mappings = [
|
|
256
274
|
(builder: ProjectionBuilderFor<Model>) => builder.from(Recorded, from => from.usingKeyFromContext('missingKey')),
|
|
257
275
|
(builder: ProjectionBuilderFor<Model>) => builder.from(Recorded, from => from.usingParentKeyFromContext('missingParent')),
|
|
276
|
+
(builder: ProjectionBuilderFor<Model>) => builder.from(Recorded, from => from.addChild<Child>(model => model.children, child => child.usingKeyFromContext('missingChildKey'))),
|
|
277
|
+
(builder: ProjectionBuilderFor<Model>) => builder.join(Recorded, join => join.on(model => model.occurred).addChild<Child>(model => model.children, child => child.usingParentKeyFromContext('missingChildParent'))),
|
|
258
278
|
(builder: ProjectionBuilderFor<Model>) => builder.join(Recorded, join => join.on(model => model.occurred).usingKeyFromContext('missingJoinKey')),
|
|
259
279
|
(builder: ProjectionBuilderFor<Model>) => builder.removedWith(Removed, removed => removed.usingKeyFromContext('missingRemovalKey'))
|
|
260
280
|
];
|
|
@@ -0,0 +1,9 @@
|
|
|
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 { ContractEventType } from './declarative/ProjectionBuilderCore.js';
|
|
5
|
+
|
|
6
|
+
/** Stable, generation-aware path shared by provenance and capability diagnostics. */
|
|
7
|
+
export function eventContractPath(section: string, eventType: ContractEventType): string {
|
|
8
|
+
return `${section}[${eventType.Id}:${eventType.Generation}]`;
|
|
9
|
+
}
|
|
@@ -48,6 +48,8 @@ export interface ChildrenDefinitionLike {
|
|
|
48
48
|
NoAutoMapProperties: string[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const aggregateExpressions = ['$count', '$increment', '$decrement', '$add', '$subtract'];
|
|
52
|
+
|
|
51
53
|
/**
|
|
52
54
|
* Resolves the contract event type for a decorated event constructor.
|
|
53
55
|
* @param eventTypeConstructor - The event class constructor.
|
|
@@ -209,23 +211,20 @@ export function resolveNestedType(type: Function, property: string): Function |
|
|
|
209
211
|
|
|
210
212
|
/**
|
|
211
213
|
* Discovers the child model property used to identify instances, by convention: a property
|
|
212
|
-
* named `id` (case-insensitive), matching the
|
|
213
|
-
*
|
|
214
|
+
* named `id` (case-insensitive), or a property matching the event key when no id is found.
|
|
215
|
+
* TypeScript has no `[Key]` decorator equivalent.
|
|
214
216
|
* @param childType - The child/nested model type, when resolvable.
|
|
217
|
+
* @param eventKey - The configured event key, when present.
|
|
215
218
|
* @returns The discovered property name, or undefined when no convention match is found.
|
|
216
219
|
*/
|
|
217
|
-
function discoverIdentifiedBy(childType: Function | undefined): string | undefined {
|
|
220
|
+
function discoverIdentifiedBy(childType: Function | undefined, eventKey: string | undefined): string | undefined {
|
|
218
221
|
if (!childType) {
|
|
219
222
|
return undefined;
|
|
220
223
|
}
|
|
221
224
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return undefined;
|
|
225
|
+
const members = [...TypeIntrospector.getMembers(childType).keys()];
|
|
226
|
+
return members.find(name => name.toLowerCase() === 'id') ??
|
|
227
|
+
(eventKey !== undefined ? members.find(name => name.toLowerCase() === eventKey.toLowerCase()) : undefined);
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
/**
|
|
@@ -325,7 +324,7 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
|
|
|
325
324
|
const definition = createEmptyChildrenDefinition(type, childType);
|
|
326
325
|
|
|
327
326
|
const explicitIdentifiedBy = metadataList.find(metadata => metadata.identifiedBy)?.identifiedBy;
|
|
328
|
-
definition.IdentifiedBy = explicitIdentifiedBy ?? discoverIdentifiedBy(childType) ?? '$eventSourceId';
|
|
327
|
+
definition.IdentifiedBy = explicitIdentifiedBy ?? discoverIdentifiedBy(childType, metadataList[0]?.key) ?? '$eventSourceId';
|
|
329
328
|
|
|
330
329
|
for (const metadata of metadataList) {
|
|
331
330
|
const eventType = toContractEventType(metadata.eventType);
|
|
@@ -340,6 +339,41 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
|
|
|
340
339
|
}
|
|
341
340
|
|
|
342
341
|
populateFromType(definition, childType);
|
|
342
|
+
|
|
343
|
+
// For non-aggregate-only creating events, populate an unmapped child identifier from
|
|
344
|
+
// the creating event's key. IdentifiedBy selects the child property; it is not itself
|
|
345
|
+
// a property mapping. TypeScript has no [Key] decorator, so an explicit identifiedBy
|
|
346
|
+
// plays the same role as a discovered id for this default.
|
|
347
|
+
const identifier = definition.IdentifiedBy;
|
|
348
|
+
if (definition.AutoMap === AutoMap.Enabled && identifier !== '$eventSourceId') {
|
|
349
|
+
const prototype = childType?.prototype;
|
|
350
|
+
const hasExplicitMapping = prototype !== undefined && (
|
|
351
|
+
getSetFromMetadata(prototype, identifier).length > 0 ||
|
|
352
|
+
getSetFromContextMetadata(prototype, identifier).length > 0 ||
|
|
353
|
+
getSetValueMetadata(prototype, identifier).length > 0 ||
|
|
354
|
+
getAddFromMetadata(prototype, identifier).length > 0 ||
|
|
355
|
+
getSubtractFromMetadata(prototype, identifier).length > 0);
|
|
356
|
+
if (!hasExplicitMapping) {
|
|
357
|
+
for (const metadata of metadataList) {
|
|
358
|
+
const entry = definition.From.find(candidate => getEventTypeMapKey(candidate.Key) === getEventTypeMapKey(toContractEventType(metadata.eventType)))!;
|
|
359
|
+
const mappings = Object.values(entry.Value.Properties);
|
|
360
|
+
// Preserve the kernel's aggregate-only AutoMap exemption: Changeset.AddChild already
|
|
361
|
+
// initializes the child identifier from the resolved key before applying property mappers.
|
|
362
|
+
if (mappings.length > 0 && mappings.every(expression =>
|
|
363
|
+
aggregateExpressions.some(aggregate => expression.startsWith(aggregate)))) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
// Child property mappings may replace the creating key (for example, a constant-key count).
|
|
367
|
+
const key = entry.Value.Key;
|
|
368
|
+
// When an event property is also the identifier, AutoMap can fill it directly.
|
|
369
|
+
if (key.toLowerCase() !== identifier.toLowerCase() && !(identifier in entry.Value.Properties)) {
|
|
370
|
+
entry.Value.Properties[identifier] = key === '$eventSourceId'
|
|
371
|
+
? eventContextPropertyExpression('eventSourceId')
|
|
372
|
+
: key;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
343
377
|
return definition;
|
|
344
378
|
}
|
|
345
379
|
|
package/reactors/Reactors.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { ActivatedArtifact } from '../artifacts/ActivatedArtifact.js';
|
|
|
28
28
|
import { ArtifactKind } from '../artifacts/ArtifactKind.js';
|
|
29
29
|
import { ArtifactDelivery } from '../artifacts/ArtifactDelivery.js';
|
|
30
30
|
import { withActivatedArtifact, runActivated } from '../artifacts/withActivatedArtifact.js';
|
|
31
|
+
import { ArtifactCompletionFailed } from '../artifacts/ArtifactCompletionFailed.js';
|
|
31
32
|
|
|
32
33
|
/** Expression used to partition reactor observations by event source ID. */
|
|
33
34
|
const EVENT_SOURCE_ID_KEY = '$eventSourceId';
|
|
@@ -306,12 +307,16 @@ export class Reactors implements IReactors {
|
|
|
306
307
|
signal: controller.signal, delivery: ArtifactDelivery.ReplayNotification,
|
|
307
308
|
replayState: eventsToObserve.ReplayState
|
|
308
309
|
}, this._artifactActivator, artifact => runActivated(artifact, () =>
|
|
309
|
-
notifyReplayLifecycle(artifact.instance, eventsToObserve.ReplayState, eventsToObserve.Partition)
|
|
310
|
+
notifyReplayLifecycle(artifact.instance, eventsToObserve.ReplayState, eventsToObserve.Partition),
|
|
311
|
+
{ delivery: ArtifactDelivery.ReplayNotification, replayState: eventsToObserve.ReplayState }));
|
|
310
312
|
} else if (reactorInstance) {
|
|
311
313
|
await notifyReplayLifecycle(reactorInstance, eventsToObserve.ReplayState, eventsToObserve.Partition);
|
|
312
314
|
}
|
|
313
315
|
} catch (err) {
|
|
314
316
|
this._logger.error('Error notifying reactor of replay lifecycle transition', { reactorId: id, error: String(err) });
|
|
317
|
+
if (err instanceof ArtifactCompletionFailed && err.processingError !== undefined) {
|
|
318
|
+
exceptionMessages.push(String(err.processingError));
|
|
319
|
+
}
|
|
315
320
|
exceptionMessages.push(String(err));
|
|
316
321
|
exceptionStackTrace = err instanceof Error ? (err.stack ?? '') : '';
|
|
317
322
|
state = ObservationState.Failed;
|
|
@@ -368,7 +373,7 @@ export class Reactors implements IReactors {
|
|
|
368
373
|
const handlerResult = await artifact.instance[methodName](content, context, services);
|
|
369
374
|
await dispatchReactorSideEffects(this._eventLog, handlerResult, context, reactorType as Function,
|
|
370
375
|
this._eventStoreName, this._namespace, this._resultHandler);
|
|
371
|
-
});
|
|
376
|
+
}, { delivery: ArtifactDelivery.Events, eventContext: context, methodName });
|
|
372
377
|
|
|
373
378
|
lastSuccessfullyObservedEvent = event.Context!.SequenceNumber;
|
|
374
379
|
} catch (err) {
|
|
@@ -398,8 +403,13 @@ export class Reactors implements IReactors {
|
|
|
398
403
|
} catch (err) {
|
|
399
404
|
this._logger.error('Error activating reactor', { reactorId: id, error: String(err) });
|
|
400
405
|
exceptionMessages.push(String(err));
|
|
401
|
-
|
|
406
|
+
const completionStack = err instanceof Error ? (err.stack ?? '') : '';
|
|
407
|
+
exceptionStackTrace = err instanceof ArtifactCompletionFailed && exceptionStackTrace
|
|
408
|
+
? `${exceptionStackTrace}\n${completionStack}` : completionStack;
|
|
402
409
|
state = ObservationState.Failed;
|
|
410
|
+
if (err instanceof ArtifactCompletionFailed) {
|
|
411
|
+
lastSuccessfullyObservedEvent = SEQUENCE_NUMBER_UNAVAILABLE;
|
|
412
|
+
}
|
|
403
413
|
}
|
|
404
414
|
} else {
|
|
405
415
|
await processEvents({ instance: reactorInstance ?? {} });
|