@cratis/chronicle 6.9.1 → 6.11.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/ChronicleClient.ts +52 -13
- package/ChronicleOptions.spec.ts +54 -0
- package/ChronicleOptions.ts +20 -5
- package/EventStore.ts +7 -1
- package/README.md +2 -3
- package/artifacts/DefaultClientArtifactsProvider.ts +4 -1
- package/connection/ChronicleConnection.auth.spec.ts +270 -0
- package/connection/ChronicleConnection.ts +90 -31
- package/connection/ChronicleServices.ts +1 -1
- package/connection/CompatibilityPreflight.ts +1 -1
- package/connection/RejectedChronicleCredentials.ts +10 -0
- package/connection/TokenProvider.spec.ts +5 -6
- package/connection/TokenProvider.ts +26 -17
- package/connection/clientVersion.spec.ts +27 -0
- package/connection/clientVersion.ts +9 -0
- package/connection/describeRequestError.spec.ts +22 -0
- package/connection/fetchOAuthAccessToken.ts +33 -2
- package/dist/ChronicleClient.d.ts +3 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +55 -13
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/ChronicleOptions.d.ts +5 -1
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +20 -5
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/EventStore.d.ts +2 -0
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +6 -1
- package/dist/EventStore.js.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js +4 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +3 -3
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +98 -26
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/ChronicleServices.d.ts +1 -2
- package/dist/connection/ChronicleServices.d.ts.map +1 -1
- package/dist/connection/CompatibilityPreflight.js +1 -1
- package/dist/connection/CompatibilityPreflight.js.map +1 -1
- package/dist/connection/RejectedChronicleCredentials.d.ts +5 -0
- package/dist/connection/RejectedChronicleCredentials.d.ts.map +1 -0
- package/dist/connection/RejectedChronicleCredentials.js +10 -0
- package/dist/connection/RejectedChronicleCredentials.js.map +1 -0
- package/dist/connection/TokenProvider.d.ts +8 -6
- package/dist/connection/TokenProvider.d.ts.map +1 -1
- package/dist/connection/TokenProvider.js +25 -11
- package/dist/connection/TokenProvider.js.map +1 -1
- package/dist/connection/clientVersion.d.ts +3 -0
- package/dist/connection/clientVersion.d.ts.map +1 -0
- package/dist/connection/clientVersion.js +7 -0
- package/dist/connection/clientVersion.js.map +1 -0
- package/dist/connection/fetchOAuthAccessToken.d.ts +12 -0
- package/dist/connection/fetchOAuthAccessToken.d.ts.map +1 -1
- package/dist/connection/fetchOAuthAccessToken.js +37 -2
- package/dist/connection/fetchOAuthAccessToken.js.map +1 -1
- package/dist/eventSequences/AppendResult.d.ts +7 -0
- package/dist/eventSequences/AppendResult.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +26 -17
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventSequences/WaitForCompletionOptions.d.ts +8 -0
- package/dist/eventSequences/WaitForCompletionOptions.d.ts.map +1 -0
- package/dist/eventSequences/WaitForCompletionOptions.js +4 -0
- package/dist/eventSequences/WaitForCompletionOptions.js.map +1 -0
- package/dist/eventSequences/index.d.ts +1 -0
- package/dist/eventSequences/index.d.ts.map +1 -1
- package/dist/eventSequences/index.js.map +1 -1
- package/dist/externalServices/ExternalServiceBuilder.d.ts +1 -1
- package/dist/externalServices/ExternalServiceBuilder.d.ts.map +1 -1
- package/dist/externalServices/ExternalServiceBuilder.js +1 -1
- package/dist/externalServices/ExternalServiceBuilder.js.map +1 -1
- package/dist/externalServices/ExternalServices.d.ts.map +1 -1
- package/dist/externalServices/ExternalServices.js +3 -1
- package/dist/externalServices/ExternalServices.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/projections/Projections.d.ts.map +1 -1
- package/dist/projections/Projections.js +9 -7
- package/dist/projections/Projections.js.map +1 -1
- package/dist/projections/constantValueExpression.d.ts +3 -0
- package/dist/projections/constantValueExpression.d.ts.map +1 -0
- package/dist/projections/constantValueExpression.js +26 -0
- package/dist/projections/constantValueExpression.js.map +1 -0
- package/dist/projections/declarative/ChildrenBuilder.d.ts +2 -0
- package/dist/projections/declarative/ChildrenBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/ChildrenBuilder.js +5 -0
- package/dist/projections/declarative/ChildrenBuilder.js.map +1 -1
- package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/FromBuilder.js +3 -2
- package/dist/projections/declarative/FromBuilder.js.map +1 -1
- package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/JoinBuilder.js +2 -1
- package/dist/projections/declarative/JoinBuilder.js.map +1 -1
- package/dist/projections/declarative/NestedBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/NestedBuilder.js +3 -1
- package/dist/projections/declarative/NestedBuilder.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.d.ts +5 -0
- package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js +18 -2
- package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderFor.d.ts +6 -0
- package/dist/projections/declarative/ProjectionBuilderFor.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderFor.js +5 -0
- package/dist/projections/declarative/ProjectionBuilderFor.js.map +1 -1
- package/dist/projections/declarative/SetBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/SetBuilder.js +2 -1
- package/dist/projections/declarative/SetBuilder.js.map +1 -1
- package/dist/projections/modelBound/addFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/addFrom.js +3 -2
- package/dist/projections/modelBound/addFrom.js.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +20 -0
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js +14 -11
- package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
- package/dist/projections/modelBound/childrenFrom.d.ts +4 -2
- package/dist/projections/modelBound/childrenFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenFrom.js +8 -4
- package/dist/projections/modelBound/childrenFrom.js.map +1 -1
- package/dist/projections/modelBound/clearWith.d.ts.map +1 -1
- package/dist/projections/modelBound/clearWith.js +3 -2
- package/dist/projections/modelBound/clearWith.js.map +1 -1
- package/dist/projections/modelBound/count.d.ts.map +1 -1
- package/dist/projections/modelBound/count.js +3 -2
- package/dist/projections/modelBound/count.js.map +1 -1
- package/dist/projections/modelBound/decrement.d.ts.map +1 -1
- package/dist/projections/modelBound/decrement.js +3 -2
- package/dist/projections/modelBound/decrement.js.map +1 -1
- package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
- package/dist/projections/modelBound/fromAll.js +3 -2
- package/dist/projections/modelBound/fromAll.js.map +1 -1
- package/dist/projections/modelBound/fromEvery.d.ts.map +1 -1
- package/dist/projections/modelBound/fromEvery.js +3 -2
- package/dist/projections/modelBound/fromEvery.js.map +1 -1
- package/dist/projections/modelBound/increment.d.ts.map +1 -1
- package/dist/projections/modelBound/increment.js +3 -2
- package/dist/projections/modelBound/increment.js.map +1 -1
- package/dist/projections/modelBound/join.d.ts.map +1 -1
- package/dist/projections/modelBound/join.js +3 -2
- package/dist/projections/modelBound/join.js.map +1 -1
- package/dist/projections/modelBound/nested.d.ts.map +1 -1
- package/dist/projections/modelBound/nested.js +3 -2
- package/dist/projections/modelBound/nested.js.map +1 -1
- package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -1
- package/dist/projections/modelBound/noAutoMap.js +3 -2
- package/dist/projections/modelBound/noAutoMap.js.map +1 -1
- package/dist/projections/modelBound/removedWith.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWith.js +3 -2
- package/dist/projections/modelBound/removedWith.js.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.js +3 -2
- package/dist/projections/modelBound/removedWithJoin.js.map +1 -1
- package/dist/projections/modelBound/setFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/setFrom.js +3 -2
- package/dist/projections/modelBound/setFrom.js.map +1 -1
- package/dist/projections/modelBound/setFromContext.d.ts.map +1 -1
- package/dist/projections/modelBound/setFromContext.js +3 -2
- package/dist/projections/modelBound/setFromContext.js.map +1 -1
- package/dist/projections/modelBound/setValue.d.ts.map +1 -1
- package/dist/projections/modelBound/setValue.js +3 -2
- package/dist/projections/modelBound/setValue.js.map +1 -1
- package/dist/projections/modelBound/subtractFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/subtractFrom.js +3 -2
- package/dist/projections/modelBound/subtractFrom.js.map +1 -1
- package/dist/projections/notSetPropertyPath.d.ts +3 -0
- package/dist/projections/notSetPropertyPath.d.ts.map +1 -0
- package/dist/projections/notSetPropertyPath.js +5 -0
- package/dist/projections/notSetPropertyPath.js.map +1 -0
- package/dist/reactors/Reactors.d.ts +4 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +24 -6
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/reactors/reactor.d.ts +2 -2
- package/dist/reactors/reactor.js +2 -2
- package/dist/readModels/IReadModels.d.ts +3 -2
- package/dist/readModels/IReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.js +2 -4
- package/dist/readModels/MaterializedReadModels.js.map +1 -1
- package/dist/readModels/ReadModels.d.ts +3 -5
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +17 -14
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/deserializeReadModel.d.ts +5 -0
- package/dist/readModels/deserializeReadModel.d.ts.map +1 -0
- package/dist/readModels/deserializeReadModel.js +84 -0
- package/dist/readModels/deserializeReadModel.js.map +1 -0
- package/dist/readModels/rootReadModelTypes.d.ts +5 -0
- package/dist/readModels/rootReadModelTypes.d.ts.map +1 -0
- package/dist/readModels/rootReadModelTypes.js +69 -0
- package/dist/readModels/rootReadModelTypes.js.map +1 -0
- package/dist/reducers/Reducers.d.ts +4 -0
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +24 -6
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.js +4 -4
- package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TypeDiscoverer.d.ts +8 -1
- package/dist/types/TypeDiscoverer.d.ts.map +1 -1
- package/dist/types/TypeDiscoverer.js +25 -5
- package/dist/types/TypeDiscoverer.js.map +1 -1
- package/dist/types/TypeIntrospector.d.ts.map +1 -1
- package/dist/types/TypeIntrospector.js +3 -2
- package/dist/types/TypeIntrospector.js.map +1 -1
- package/dist/types/conceptValueType.d.ts +4 -0
- package/dist/types/conceptValueType.d.ts.map +1 -0
- package/dist/types/conceptValueType.js +10 -0
- package/dist/types/conceptValueType.js.map +1 -0
- package/dist/types/modelBoundProperty.d.ts +6 -0
- package/dist/types/modelBoundProperty.d.ts.map +1 -0
- package/dist/types/modelBoundProperty.js +53 -0
- package/dist/types/modelBoundProperty.js.map +1 -0
- package/eventSequences/AppendResult.ts +8 -0
- package/eventSequences/EventSequence.spec.ts +76 -1
- package/eventSequences/EventSequence.ts +28 -20
- package/eventSequences/WaitForCompletionOptions.ts +11 -0
- package/eventSequences/index.ts +1 -0
- package/externalServices/ExternalServiceBuilder.ts +4 -4
- package/externalServices/ExternalServices.spec.ts +87 -0
- package/externalServices/ExternalServices.ts +3 -1
- package/for_ChronicleClient/connectionAuthentication.spec.ts +67 -0
- package/index.ts +1 -0
- package/observation/observationDisposal.spec.ts +74 -0
- package/package.json +2 -1
- package/projections/Projections.childrenAndNested.spec.ts +2 -0
- package/projections/Projections.modelBoundCompleteness.spec.ts +47 -0
- package/projections/Projections.ts +9 -7
- package/projections/constantValueExpression.spec.ts +31 -0
- package/projections/constantValueExpression.ts +28 -0
- package/projections/declarative/ChildrenBuilder.spec.ts +46 -1
- package/projections/declarative/ChildrenBuilder.ts +6 -0
- package/projections/declarative/FromBuilder.ts +3 -2
- package/projections/declarative/JoinBuilder.ts +2 -1
- package/projections/declarative/NestedBuilder.ts +3 -1
- package/projections/declarative/ProjectionBuilderCore.ts +20 -2
- package/projections/declarative/ProjectionBuilderFor.spec.ts +61 -4
- package/projections/declarative/ProjectionBuilderFor.ts +7 -0
- package/projections/declarative/SetBuilder.ts +2 -1
- package/projections/modelBound/addFrom.ts +3 -2
- package/projections/modelBound/childrenAndNestedBuilder.ts +14 -11
- package/projections/modelBound/childrenFrom.ts +11 -5
- package/projections/modelBound/clearWith.ts +3 -2
- package/projections/modelBound/count.ts +3 -2
- package/projections/modelBound/decorators.standard.spec.ts +45 -0
- package/projections/modelBound/decrement.ts +3 -2
- package/projections/modelBound/fromAll.ts +3 -2
- package/projections/modelBound/fromEvery.ts +3 -2
- package/projections/modelBound/increment.ts +3 -2
- package/projections/modelBound/join.ts +3 -2
- package/projections/modelBound/nested.ts +3 -2
- package/projections/modelBound/noAutoMap.ts +3 -2
- package/projections/modelBound/removedWith.ts +3 -2
- package/projections/modelBound/removedWithJoin.ts +3 -2
- package/projections/modelBound/setFrom.ts +3 -2
- package/projections/modelBound/setFromContext.ts +3 -2
- package/projections/modelBound/setValue.ts +3 -2
- package/projections/modelBound/subtractFrom.ts +3 -2
- package/projections/notSetPropertyPath.ts +5 -0
- package/reactors/Reactors.ts +22 -7
- package/reactors/reactor.ts +2 -2
- package/readModels/IReadModels.ts +3 -2
- package/readModels/MaterializedReadModels.spec.ts +22 -0
- package/readModels/MaterializedReadModels.ts +2 -4
- package/readModels/ReadModels.spec.ts +71 -6
- package/readModels/ReadModels.ts +18 -15
- package/readModels/conceptValueType.spec.ts +20 -0
- package/readModels/deserializeReadModel.spec.ts +84 -0
- package/readModels/deserializeReadModel.ts +82 -0
- package/readModels/discovery.spec.ts +27 -0
- package/readModels/rootReadModelTypes.spec.ts +115 -0
- package/readModels/rootReadModelTypes.ts +65 -0
- package/reducers/Reducers.ts +22 -7
- package/schemas/JsonSchemaGenerator.ts +4 -4
- package/schemas/legacyDesignMetadata.spec.ts +24 -2
- package/types/TypeDiscoverer.spec.ts +57 -0
- package/types/TypeDiscoverer.ts +27 -6
- package/types/TypeIntrospector.ts +3 -2
- package/types/conceptValueType.ts +11 -0
- package/types/modelBoundProperty.ts +53 -0
- package/dist/externalServices/ExternalServicesContracts.d.ts +0 -137
- package/dist/externalServices/ExternalServicesContracts.d.ts.map +0 -1
- package/dist/externalServices/ExternalServicesContracts.js +0 -654
- package/dist/externalServices/ExternalServicesContracts.js.map +0 -1
- package/externalServices/ExternalServicesContracts.ts +0 -709
|
@@ -20,10 +20,12 @@ import { toObserverRunningState } from '../observation/toObserverRunningState.js
|
|
|
20
20
|
import { getReadModelMetadata } from '../readModels/index.js';
|
|
21
21
|
import { getReadModelId } from '../readModels/readModel.js';
|
|
22
22
|
import { assertUniqueReadModelIds } from '../readModels/assertUniqueReadModelIds.js';
|
|
23
|
+
import { rootReadModelTypes } from '../readModels/rootReadModelTypes.js';
|
|
23
24
|
import { JsonSchemaGenerator } from '../schemas/index.js';
|
|
24
25
|
import { TypeIntrospector } from '../types/index.js';
|
|
25
26
|
import { hasModelBoundProperties } from '../types/TypeDiscoverer.js';
|
|
26
27
|
import { IProjections } from './IProjections.js';
|
|
28
|
+
import { constantValueExpression } from './constantValueExpression.js';
|
|
27
29
|
import { getProjectionMetadata } from './declarative/projection.js';
|
|
28
30
|
import { ProjectionBuilderFor } from './declarative/ProjectionBuilderFor.js';
|
|
29
31
|
import type { IProjectionFor } from './declarative/IProjectionFor.js';
|
|
@@ -101,7 +103,7 @@ export class Projections implements IProjections {
|
|
|
101
103
|
this._modelBound.clear();
|
|
102
104
|
|
|
103
105
|
const declarativeTypes = this._clientArtifacts.projections;
|
|
104
|
-
const readModelTypes = this._clientArtifacts
|
|
106
|
+
const readModelTypes = rootReadModelTypes(this._clientArtifacts);
|
|
105
107
|
this._logger.debug('Discovering projections', { declarativeCount: declarativeTypes.length, readModelCount: readModelTypes.length });
|
|
106
108
|
|
|
107
109
|
for (const type of declarativeTypes) {
|
|
@@ -143,7 +145,7 @@ export class Projections implements IProjections {
|
|
|
143
145
|
const inferred = this._clientArtifacts.projections
|
|
144
146
|
.map(type => getProjectionMetadata(type)?.readModelType)
|
|
145
147
|
.filter((type): type is Constructor => type !== undefined);
|
|
146
|
-
assertUniqueReadModelIds([...this._clientArtifacts
|
|
148
|
+
assertUniqueReadModelIds([...rootReadModelTypes(this._clientArtifacts), ...inferred]);
|
|
147
149
|
const builtProjections: BuiltProjection[] = [
|
|
148
150
|
...Array.from(this._declarative.values()).map(type => this.buildDeclarativeDefinition(type)),
|
|
149
151
|
...Array.from(this._modelBound.values()).map(type => this.buildModelBoundDefinition(type))
|
|
@@ -396,7 +398,7 @@ export class Projections implements IProjections {
|
|
|
396
398
|
|
|
397
399
|
private getReadModelSchema(readModelIdentifier: string): string {
|
|
398
400
|
const types = [
|
|
399
|
-
...this._clientArtifacts
|
|
401
|
+
...rootReadModelTypes(this._clientArtifacts),
|
|
400
402
|
...this._clientArtifacts.projections
|
|
401
403
|
.map(projectionType => getProjectionMetadata(projectionType)?.readModelType)
|
|
402
404
|
.filter((type): type is Constructor => type !== undefined)
|
|
@@ -457,7 +459,7 @@ export class Projections implements IProjections {
|
|
|
457
459
|
return undefined;
|
|
458
460
|
}
|
|
459
461
|
|
|
460
|
-
const matchingReadModels = this._clientArtifacts
|
|
462
|
+
const matchingReadModels = rootReadModelTypes(this._clientArtifacts)
|
|
461
463
|
.map(type => ({ type, metadata: getReadModelMetadata(type) }))
|
|
462
464
|
.filter(candidate => candidate.metadata)
|
|
463
465
|
.filter(candidate => {
|
|
@@ -496,7 +498,7 @@ export class Projections implements IProjections {
|
|
|
496
498
|
Key: eventType,
|
|
497
499
|
Value: {
|
|
498
500
|
Properties: {},
|
|
499
|
-
Key: fromEvent.constantKey
|
|
501
|
+
Key: fromEvent.constantKey ? constantValueExpression(fromEvent.constantKey) : (fromEvent.key ?? '$eventSourceId'),
|
|
500
502
|
ParentKey: fromEvent.parentKey ?? ''
|
|
501
503
|
}
|
|
502
504
|
});
|
|
@@ -525,7 +527,7 @@ export class Projections implements IProjections {
|
|
|
525
527
|
|
|
526
528
|
for (const mapping of getJoinMetadata(prototype, property)) {
|
|
527
529
|
const entry = this.ensureJoinEntry(joinByEventType, mapping.eventType);
|
|
528
|
-
entry.Value.On = mapping.on ?? entry.Value.On;
|
|
530
|
+
entry.Value.On = mapping.on ?? (entry.Value.On || property);
|
|
529
531
|
entry.Value.Properties[property] = mapping.eventPropertyName ?? property;
|
|
530
532
|
}
|
|
531
533
|
|
|
@@ -652,7 +654,7 @@ export class Projections implements IProjections {
|
|
|
652
654
|
Key: eventType,
|
|
653
655
|
Value: {
|
|
654
656
|
Properties: {},
|
|
655
|
-
Key: fromEvent.constantKey
|
|
657
|
+
Key: fromEvent.constantKey ? constantValueExpression(fromEvent.constantKey) : (fromEvent.key ?? '$eventSourceId'),
|
|
656
658
|
ParentKey: fromEvent.parentKey ?? ''
|
|
657
659
|
}
|
|
658
660
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { constantValueExpression } from './constantValueExpression.js';
|
|
6
|
+
|
|
7
|
+
describe('constantValueExpression', () => {
|
|
8
|
+
it.each([
|
|
9
|
+
[true, '$value(true)'],
|
|
10
|
+
[false, '$value(false)'],
|
|
11
|
+
[42, '$value(42)'],
|
|
12
|
+
[-1.5, '$value(-1.5)'],
|
|
13
|
+
['on-loan', '$value(on-loan)'],
|
|
14
|
+
['å', '$value(å)'],
|
|
15
|
+
['', '$value()'],
|
|
16
|
+
[new Date('2025-01-02T03:04:05.006Z'), '$value(2025-01-02T03:04:05.006Z)'],
|
|
17
|
+
[null, '$null'],
|
|
18
|
+
[undefined, '$null'],
|
|
19
|
+
[{ value: 'wrapped' }, '$value(wrapped)'],
|
|
20
|
+
[{ value: { value: 17 } }, '$value(17)'],
|
|
21
|
+
[{ value: null }, '$null'],
|
|
22
|
+
['$null', '$null'],
|
|
23
|
+
['$value(already)', '$value(already)']
|
|
24
|
+
])('should encode %s as %s', (value, expression) => {
|
|
25
|
+
expect(constantValueExpression(value)).toBe(expression);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it.each(['a(b)', 'a,b', 'a\nb', '$bad', 'a)', '$value(a,b)', '$value(a(b))', '$value(a', '$value(a)garbage'])('rejects unsupported characters in %s', value => {
|
|
29
|
+
expect(() => constantValueExpression(value)).toThrow(/unsupported by the kernel/);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
/** Converts a constant to the projection expression understood by the Chronicle kernel. */
|
|
5
|
+
export function constantValueExpression(value: unknown): string {
|
|
6
|
+
if (value === null || value === undefined) {
|
|
7
|
+
return '$null';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (value instanceof Date) {
|
|
11
|
+
return `$value(${value.toISOString()})`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof value === 'object' && 'value' in value) {
|
|
15
|
+
return constantValueExpression(value.value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const text = String(value);
|
|
19
|
+
if (text === '$null') return text;
|
|
20
|
+
const wrapped = text.startsWith('$value(');
|
|
21
|
+
const content = wrapped && text.endsWith(')') ? text.slice(7, -1) : text;
|
|
22
|
+
// ValueExpressionResolver accepts only word characters, spaces, and ._/:*+-.
|
|
23
|
+
// .NET's \w also accepts Unicode letters, non-spacing marks, decimal digits and connector punctuation.
|
|
24
|
+
if ((wrapped && !text.endsWith(')')) || !/^[\p{L}\p{Mn}\p{Nd}\p{Pc} ._/:*+-]*$/u.test(content)) {
|
|
25
|
+
throw new Error(`Constant value '${text}' contains characters unsupported by the kernel's $value expression.`);
|
|
26
|
+
}
|
|
27
|
+
return wrapped ? text : `$value(${text})`;
|
|
28
|
+
}
|
|
@@ -36,10 +36,12 @@ eventType()(WholeLineReceived);
|
|
|
36
36
|
class OrderLine {
|
|
37
37
|
productId!: string;
|
|
38
38
|
quantity!: number;
|
|
39
|
+
summary!: OrderSummary | undefined;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
class OrderSummary {
|
|
42
43
|
total!: number;
|
|
44
|
+
details!: OrderSummary | undefined;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
class Order {
|
|
@@ -59,6 +61,8 @@ interface ChildrenDefinition {
|
|
|
59
61
|
From: FromRecord[];
|
|
60
62
|
RemovedWith: Array<{ Key: { Id: string } }>;
|
|
61
63
|
FromEventProperty?: { Event: { Id: string } | undefined; PropertyExpression: string };
|
|
64
|
+
Nested: Record<string, ChildrenDefinition>;
|
|
65
|
+
Join: Array<{ Value: { On: string } }>;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
interface BuiltDefinition {
|
|
@@ -68,6 +72,21 @@ interface BuiltDefinition {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
describe('ChildrenBuilder and NestedBuilder', () => {
|
|
75
|
+
it('resolves a child join after identifiedBy is configured', () => {
|
|
76
|
+
const builder = new ProjectionBuilderFor<Order>();
|
|
77
|
+
builder.children<OrderLine>(order => order.lines, child => child
|
|
78
|
+
.join(LineAdded, join => join.set(line => line.quantity).to(event => event.quantity))
|
|
79
|
+
.identifiedBy(line => line.productId));
|
|
80
|
+
const definition = builder.build('order', 'Order') as unknown as BuiltDefinition;
|
|
81
|
+
expect(definition.Children.lines.Join[0].Value.On).toBe('productId');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('rejects a root join without on at build time', () => {
|
|
85
|
+
const builder = new ProjectionBuilderFor<Order>();
|
|
86
|
+
expect(() => builder.join(LineAdded)).not.toThrow();
|
|
87
|
+
expect(() => builder.build('order', 'Order')).toThrow("A join with event 'LineAdded' requires an on property.");
|
|
88
|
+
});
|
|
89
|
+
|
|
71
90
|
describe('when using children on the top-level builder', () => {
|
|
72
91
|
it('should produce a children definition keyed by the target property', () => {
|
|
73
92
|
const builder = new ProjectionBuilderFor<Order>();
|
|
@@ -94,12 +113,26 @@ describe('ChildrenBuilder and NestedBuilder', () => {
|
|
|
94
113
|
const definition = builder.build('order', 'Order') as unknown as BuiltDefinition;
|
|
95
114
|
const summaryDefinition = definition.Nested.summary;
|
|
96
115
|
|
|
116
|
+
expect(summaryDefinition.IdentifiedBy).toBe('*NotSet*');
|
|
97
117
|
const fromEntry = summaryDefinition.From.find(candidate => candidate.Key.Id === 'SummaryUpdated')!;
|
|
98
118
|
expect(fromEntry.Value.Properties.total).toBe('total');
|
|
99
119
|
expect(summaryDefinition.RemovedWith[0].Key.Id).toBe('SummaryCleared');
|
|
100
120
|
});
|
|
101
121
|
});
|
|
102
122
|
|
|
123
|
+
describe('when nesting within a child or nested definition', () => {
|
|
124
|
+
it('should mark every nested definition as not identified by a property', () => {
|
|
125
|
+
const builder = new ProjectionBuilderFor<Order>();
|
|
126
|
+
builder.children<OrderLine>(order => order.lines, child => child
|
|
127
|
+
.nested<OrderSummary>(line => line.summary, nested => nested
|
|
128
|
+
.nested<OrderSummary>(summary => summary.details, () => {})));
|
|
129
|
+
|
|
130
|
+
const definition = builder.build('order', 'Order') as unknown as BuiltDefinition;
|
|
131
|
+
expect(definition.Children.lines.Nested.summary.IdentifiedBy).toBe('*NotSet*');
|
|
132
|
+
expect(definition.Children.lines.Nested.summary.Nested.details.IdentifiedBy).toBe('*NotSet*');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
103
136
|
describe('when using addChild with a builder callback', () => {
|
|
104
137
|
it('should produce a children definition keyed by identifiedBy and usingKey', () => {
|
|
105
138
|
const builder = new ProjectionBuilderFor<Order>();
|
|
@@ -132,7 +165,7 @@ describe('ChildrenBuilder and NestedBuilder', () => {
|
|
|
132
165
|
describe('when using addChild on a join builder', () => {
|
|
133
166
|
it('should produce a children definition from the join clause', () => {
|
|
134
167
|
const builder = new ProjectionBuilderFor<Order>();
|
|
135
|
-
builder.join(WholeLineReceived, join => join.addChild<OrderLine>(order => order.lines, child => child
|
|
168
|
+
builder.join(WholeLineReceived, join => join.on(order => order.id).addChild<OrderLine>(order => order.lines, child => child
|
|
136
169
|
.identifiedBy(line => line.productId)));
|
|
137
170
|
|
|
138
171
|
const definition = builder.build('order', 'Order') as unknown as BuiltDefinition;
|
|
@@ -143,6 +176,18 @@ describe('ChildrenBuilder and NestedBuilder', () => {
|
|
|
143
176
|
});
|
|
144
177
|
});
|
|
145
178
|
|
|
179
|
+
describe('when joining from a child projection', () => {
|
|
180
|
+
it('should use the child identifier when on is omitted', () => {
|
|
181
|
+
const builder = new ProjectionBuilderFor<Order>();
|
|
182
|
+
builder.children<OrderLine>(order => order.lines, child => child
|
|
183
|
+
.identifiedBy(line => line.productId)
|
|
184
|
+
.join(LineAdded, join => join.set(line => line.quantity).to(event => event.quantity)));
|
|
185
|
+
|
|
186
|
+
const definition = builder.build('order', 'Order') as unknown as BuiltDefinition;
|
|
187
|
+
expect(definition.Children.lines.Join[0].Value.On).toBe('productId');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
146
191
|
describe('when using setThisValue on a from builder', () => {
|
|
147
192
|
it('should map the whole event property onto the $this well-known expression', () => {
|
|
148
193
|
const builder = new ProjectionBuilderFor<Order>();
|
|
@@ -67,11 +67,17 @@ export class ChildrenBuilder<TParentReadModel, TChildReadModel>
|
|
|
67
67
|
return this;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/** @inheritdoc */
|
|
71
|
+
protected defaultJoinOn(): string | undefined {
|
|
72
|
+
return this._identifiedBy;
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
/**
|
|
71
76
|
* Builds the {@link ChildrenDefinitionLike} representing this children collection.
|
|
72
77
|
* @returns The accumulated children definition.
|
|
73
78
|
*/
|
|
74
79
|
buildDefinition(): ChildrenDefinitionLike {
|
|
80
|
+
this.resolveJoins();
|
|
75
81
|
return {
|
|
76
82
|
IdentifiedBy: this._identifiedBy ?? '$eventSourceId',
|
|
77
83
|
From: this._from,
|
|
@@ -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 { constantValueExpression } from '../constantValueExpression.js';
|
|
5
6
|
import { AddBuilder } from './AddBuilder.js';
|
|
6
7
|
import { AddChildBuilder, ChildAdditionEntry } from './AddChildBuilder.js';
|
|
7
8
|
import { CompositeKeyBuilder } from './CompositeKeyBuilder.js';
|
|
@@ -98,13 +99,13 @@ export class FromBuilder<TReadModel, TEvent> implements IFromBuilder<TReadModel,
|
|
|
98
99
|
|
|
99
100
|
/** @inheritdoc */
|
|
100
101
|
usingConstantKey(value: string): this {
|
|
101
|
-
this.entry.key = value;
|
|
102
|
+
this.entry.key = constantValueExpression(value);
|
|
102
103
|
return this;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/** @inheritdoc */
|
|
106
107
|
usingConstantParentKey(value: string): this {
|
|
107
|
-
this.entry.parentKey = value;
|
|
108
|
+
this.entry.parentKey = constantValueExpression(value);
|
|
108
109
|
return this;
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -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 { constantValueExpression } from '../constantValueExpression.js';
|
|
5
6
|
import { AddBuilder } from './AddBuilder.js';
|
|
6
7
|
import { AddChildBuilder, ChildAdditionEntry } from './AddChildBuilder.js';
|
|
7
8
|
import { CompositeKeyBuilder } from './CompositeKeyBuilder.js';
|
|
@@ -106,7 +107,7 @@ export class JoinBuilder<TReadModel, TEvent> implements IJoinBuilder<TReadModel,
|
|
|
106
107
|
|
|
107
108
|
/** @inheritdoc */
|
|
108
109
|
usingConstantKey(value: string): this {
|
|
109
|
-
this.entry.key = value;
|
|
110
|
+
this.entry.key = constantValueExpression(value);
|
|
110
111
|
return this;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -6,6 +6,7 @@ import { ChildrenDefinitionLike, ProjectionBuilderCore } from './ProjectionBuild
|
|
|
6
6
|
import { IChildrenBuilder } from './IChildrenBuilder.js';
|
|
7
7
|
import { INestedBuilder } from './INestedBuilder.js';
|
|
8
8
|
import { ChildrenBuilder } from './ChildrenBuilder.js';
|
|
9
|
+
import { notSetPropertyPath } from '../notSetPropertyPath.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Concrete implementation of {@link INestedBuilder}, building the `ChildrenDefinition` for a
|
|
@@ -61,8 +62,9 @@ export class NestedBuilder<TParentReadModel, TNestedReadModel>
|
|
|
61
62
|
* @returns The accumulated nested definition.
|
|
62
63
|
*/
|
|
63
64
|
buildDefinition(): ChildrenDefinitionLike {
|
|
65
|
+
this.resolveJoins();
|
|
64
66
|
return {
|
|
65
|
-
IdentifiedBy:
|
|
67
|
+
IdentifiedBy: notSetPropertyPath,
|
|
66
68
|
From: this._from,
|
|
67
69
|
Join: this._join,
|
|
68
70
|
Children: this._children,
|
|
@@ -73,6 +73,7 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
|
|
|
73
73
|
protected _initialState: string = '{}';
|
|
74
74
|
protected readonly _from: FromRecord[] = [];
|
|
75
75
|
protected readonly _join: JoinRecord[] = [];
|
|
76
|
+
private readonly _joinEventNames = new Map<JoinRecord, string>();
|
|
76
77
|
protected readonly _removedWith: RemovedWithRecord[] = [];
|
|
77
78
|
protected readonly _removedWithJoin: RemovedWithJoinRecord[] = [];
|
|
78
79
|
protected readonly _children: Record<string, ChildrenDefinitionLike> = {};
|
|
@@ -129,14 +130,16 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
|
|
|
129
130
|
const contractType = this.toContractEventType(eventType);
|
|
130
131
|
const joinBuilder = new JoinBuilder<TReadModel, TEvent>();
|
|
131
132
|
builderCallback?.(joinBuilder);
|
|
132
|
-
|
|
133
|
+
const record: JoinRecord = {
|
|
133
134
|
Key: contractType,
|
|
134
135
|
Value: {
|
|
135
136
|
On: joinBuilder.entry.on,
|
|
136
137
|
Properties: joinBuilder.entry.properties,
|
|
137
138
|
Key: joinBuilder.entry.key
|
|
138
139
|
}
|
|
139
|
-
}
|
|
140
|
+
};
|
|
141
|
+
this._join.push(record);
|
|
142
|
+
this._joinEventNames.set(record, eventType.name);
|
|
140
143
|
this.mergeChildAdditions(contractType, joinBuilder.entry.children);
|
|
141
144
|
return this as unknown as TBuilder;
|
|
142
145
|
}
|
|
@@ -191,6 +194,21 @@ export abstract class ProjectionBuilderCore<TReadModel, TBuilder> {
|
|
|
191
194
|
return this as unknown as TBuilder;
|
|
192
195
|
}
|
|
193
196
|
|
|
197
|
+
/** Resolves join defaults once the entire projection (including identifiedBy) is configured. */
|
|
198
|
+
protected resolveJoins(): void {
|
|
199
|
+
for (const record of this._join) {
|
|
200
|
+
record.Value.On ||= this.defaultJoinOn() ?? '';
|
|
201
|
+
if (!record.Value.On) {
|
|
202
|
+
throw new Error(`A join with event '${this._joinEventNames.get(record)}' requires an on property.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Returns a default join-on path for children, if one is available. */
|
|
208
|
+
protected defaultJoinOn(): string | undefined {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
/**
|
|
195
213
|
* Creates an empty {@link ChildrenDefinitionLike} accumulator for a children/nested entry.
|
|
196
214
|
* @returns A fresh, empty children definition.
|
|
@@ -28,6 +28,10 @@ class Inventory {
|
|
|
28
28
|
removedTotal!: number;
|
|
29
29
|
thingsHappenedCount!: number;
|
|
30
30
|
countsByEventType!: Record<string, number>;
|
|
31
|
+
active!: boolean;
|
|
32
|
+
status!: string;
|
|
33
|
+
started!: Date;
|
|
34
|
+
cleared!: string | null;
|
|
31
35
|
incrementsByEventType!: Record<string, number>;
|
|
32
36
|
decrementsByEventType!: Record<string, number>;
|
|
33
37
|
}
|
|
@@ -81,14 +85,48 @@ describe('ProjectionBuilderFor', () => {
|
|
|
81
85
|
const entry = definition.From.find(candidate => candidate.Key.Id === 'ThingHappened')!;
|
|
82
86
|
|
|
83
87
|
expect(entry.Value.Properties.thingsHappenedCount).toBe('$count');
|
|
84
|
-
expect(entry.Value.Key).toBe('singleton');
|
|
88
|
+
expect(entry.Value.Key).toBe('$value(singleton)');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('when setting constants on a from builder', () => {
|
|
93
|
+
it('should encode constants as value expressions and null as a clear', () => {
|
|
94
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
95
|
+
builder.from(ThingHappened, from => {
|
|
96
|
+
from.set(model => model.active).toValue(true);
|
|
97
|
+
from.set(model => model.status).toValue('on-loan');
|
|
98
|
+
from.set(model => model.total).toValue(2.5);
|
|
99
|
+
from.set(model => model.started).toValue(new Date('2025-01-02T03:04:05.006Z'));
|
|
100
|
+
from.set(model => model.cleared).toValue(null);
|
|
101
|
+
from.set(model => model.id).toValue({ value: 'wrapped' });
|
|
102
|
+
from.setThisValue().toValue('whole');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { From: FromRecord[] };
|
|
106
|
+
expect(definition.From[0].Value.Properties).toMatchObject({
|
|
107
|
+
active: '$value(true)',
|
|
108
|
+
status: '$value(on-loan)',
|
|
109
|
+
total: '$value(2.5)',
|
|
110
|
+
started: '$value(2025-01-02T03:04:05.006Z)',
|
|
111
|
+
cleared: '$null',
|
|
112
|
+
id: '$value(wrapped)',
|
|
113
|
+
$this: '$value(whole)'
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should encode constant keys and parent keys as expressions', () => {
|
|
118
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
119
|
+
builder.from(ThingHappened, from => from.usingConstantKey('singleton').usingConstantParentKey('parent'));
|
|
120
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { From: Array<{ Value: { Key: string; ParentKey: string } }> };
|
|
121
|
+
expect(definition.From[0].Value.Key).toBe('$value(singleton)');
|
|
122
|
+
expect(definition.From[0].Value.ParentKey).toBe('$value(parent)');
|
|
85
123
|
});
|
|
86
124
|
});
|
|
87
125
|
|
|
88
126
|
describe('when using add on a join builder', () => {
|
|
89
127
|
it('should produce a $add expression targeting the event property', () => {
|
|
90
128
|
const builder = new ProjectionBuilderFor<Inventory>();
|
|
91
|
-
builder.join(ItemAdded, join => join.add(model => model.total).with(event => event.quantity));
|
|
129
|
+
builder.join(ItemAdded, join => join.on(model => model.id).add(model => model.total).with(event => event.quantity));
|
|
92
130
|
|
|
93
131
|
const definition = builder.build('inventory', 'Inventory') as unknown as { Join: JoinRecord[] };
|
|
94
132
|
const entry = definition.Join.find(candidate => candidate.Key.Id === 'ItemAdded')!;
|
|
@@ -97,10 +135,20 @@ describe('ProjectionBuilderFor', () => {
|
|
|
97
135
|
});
|
|
98
136
|
});
|
|
99
137
|
|
|
138
|
+
describe('when setting a constant on a join builder', () => {
|
|
139
|
+
it('should send a value expression for the property and key', () => {
|
|
140
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
141
|
+
builder.join(ItemAdded, join => join.on(model => model.id).usingConstantKey('singleton').set(model => model.status).toValue('on-loan'));
|
|
142
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { Join: Array<{ Value: { Key: string; Properties: Record<string, string> } }> };
|
|
143
|
+
expect(definition.Join[0].Value.Key).toBe('$value(singleton)');
|
|
144
|
+
expect(definition.Join[0].Value.Properties.status).toBe('$value(on-loan)');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
100
148
|
describe('when using subtract on a join builder', () => {
|
|
101
149
|
it('should produce a $subtract expression targeting the event property', () => {
|
|
102
150
|
const builder = new ProjectionBuilderFor<Inventory>();
|
|
103
|
-
builder.join(ItemRemoved, join => join.subtract(model => model.removedTotal).with(event => event.quantity));
|
|
151
|
+
builder.join(ItemRemoved, join => join.on(model => model.id).subtract(model => model.removedTotal).with(event => event.quantity));
|
|
104
152
|
|
|
105
153
|
const definition = builder.build('inventory', 'Inventory') as unknown as { Join: JoinRecord[] };
|
|
106
154
|
const entry = definition.Join.find(candidate => candidate.Key.Id === 'ItemRemoved')!;
|
|
@@ -112,7 +160,7 @@ describe('ProjectionBuilderFor', () => {
|
|
|
112
160
|
describe('when using count on a join builder', () => {
|
|
113
161
|
it('should produce a $count expression', () => {
|
|
114
162
|
const builder = new ProjectionBuilderFor<Inventory>();
|
|
115
|
-
builder.join(ThingHappened, join => join.count(model => model.thingsHappenedCount));
|
|
163
|
+
builder.join(ThingHappened, join => join.on(model => model.id).count(model => model.thingsHappenedCount));
|
|
116
164
|
|
|
117
165
|
const definition = builder.build('inventory', 'Inventory') as unknown as { Join: JoinRecord[] };
|
|
118
166
|
const entry = definition.Join.find(candidate => candidate.Key.Id === 'ThingHappened')!;
|
|
@@ -121,6 +169,15 @@ describe('ProjectionBuilderFor', () => {
|
|
|
121
169
|
});
|
|
122
170
|
});
|
|
123
171
|
|
|
172
|
+
describe('when joining without an on property', () => {
|
|
173
|
+
it('should reject a definition that the kernel cannot convert', () => {
|
|
174
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
175
|
+
builder.join(ThingHappened, join => join.count(model => model.thingsHappenedCount));
|
|
176
|
+
expect(() => builder.build('inventory', 'Inventory'))
|
|
177
|
+
.toThrow("A join with event 'ThingHappened' requires an on property.");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
124
181
|
describe('when using count with event context property on fromEvery', () => {
|
|
125
182
|
it('should produce a $count expression with dictionary key from event context', () => {
|
|
126
183
|
const builder = new ProjectionBuilderFor<Inventory>();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { AutoMap } from '@cratis/chronicle.contracts';
|
|
5
5
|
import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
|
|
6
6
|
import { EventSequenceId } from '../../eventSequences/EventSequenceId.js';
|
|
7
|
+
import type { ChildrenDefinitionLike } from '../modelBound/childrenAndNestedBuilder.js';
|
|
7
8
|
import { ChildrenBuilder } from './ChildrenBuilder.js';
|
|
8
9
|
import { IChildrenBuilder } from './IChildrenBuilder.js';
|
|
9
10
|
import { INestedBuilder } from './INestedBuilder.js';
|
|
@@ -120,6 +121,11 @@ export class ProjectionBuilderFor<TReadModel> extends ProjectionBuilderCore<TRea
|
|
|
120
121
|
return this;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/** Names of children and nested members configured by declarative builders. */
|
|
125
|
+
getSubobjectDefinitions(): { children: Record<string, ChildrenDefinitionLike>; nested: Record<string, ChildrenDefinitionLike> } {
|
|
126
|
+
return { children: this._children, nested: this._nested };
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
/**
|
|
124
130
|
* Builds the contract-compatible projection definition payload.
|
|
125
131
|
* @param identifier - The projection identifier.
|
|
@@ -127,6 +133,7 @@ export class ProjectionBuilderFor<TReadModel> extends ProjectionBuilderCore<TRea
|
|
|
127
133
|
* @returns The projection definition object ready to send to the kernel.
|
|
128
134
|
*/
|
|
129
135
|
build(identifier: string, readModelName: string): Record<string, unknown> {
|
|
136
|
+
this.resolveJoins();
|
|
130
137
|
const definition: Record<string, unknown> = {
|
|
131
138
|
EventSequenceId: this._eventSequenceId,
|
|
132
139
|
Identifier: identifier,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
|
|
5
5
|
import { ISetBuilder } from './ISetBuilder.js';
|
|
6
|
+
import { constantValueExpression } from '../constantValueExpression.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Concrete implementation of {@link ISetBuilder} that records the property mapping
|
|
@@ -28,7 +29,7 @@ export class SetBuilder<TEvent, TParentBuilder> implements ISetBuilder<TEvent, T
|
|
|
28
29
|
|
|
29
30
|
/** @inheritdoc */
|
|
30
31
|
toValue<TProperty>(value: TProperty): TParentBuilder {
|
|
31
|
-
this._setProperty(this._readModelProperty,
|
|
32
|
+
this._setProperty(this._readModelProperty, constantValueExpression(value));
|
|
32
33
|
return this._parent;
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
|
|
7
|
+
import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
7
8
|
|
|
8
9
|
/** Metadata stored by the addFrom property decorator. */
|
|
9
10
|
export interface AddFromMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:addFrom';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function addFrom(eventType: Function, eventPropertyName?: string): ChroniclePropertyDecorator {
|
|
25
|
-
return
|
|
26
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
26
27
|
const key = propertyKey.toString();
|
|
27
28
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
|
|
28
29
|
const existing: AddFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -5,6 +5,8 @@ import 'reflect-metadata';
|
|
|
5
5
|
import { AutoMap } from '@cratis/chronicle.contracts';
|
|
6
6
|
import { Constructor, Fields } from '@cratis/fundamentals';
|
|
7
7
|
import { TypeIntrospector } from '../../types/index.js';
|
|
8
|
+
import { constantValueExpression } from '../constantValueExpression.js';
|
|
9
|
+
import { notSetPropertyPath } from '../notSetPropertyPath.js';
|
|
8
10
|
import { getEventTypeFor } from '../../events/eventTypeDecorator.js';
|
|
9
11
|
import { getAddFromMetadata } from './addFrom.js';
|
|
10
12
|
import { ChildrenFromMetadata, getChildrenFromMetadata } from './childrenFrom.js';
|
|
@@ -110,7 +112,7 @@ export function applyPropertyMappings(prototype: object, property: string, fromB
|
|
|
110
112
|
|
|
111
113
|
for (const mapping of getSetValueMetadata(prototype, property)) {
|
|
112
114
|
const entry = ensureFromEntry(fromByEventType, mapping.eventType);
|
|
113
|
-
entry.Value.Properties[property] =
|
|
115
|
+
entry.Value.Properties[property] = constantValueExpression(mapping.value);
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
for (const mapping of getAddFromMetadata(prototype, property)) {
|
|
@@ -127,7 +129,7 @@ export function applyPropertyMappings(prototype: object, property: string, fromB
|
|
|
127
129
|
const entry = ensureFromEntry(fromByEventType, mapping.eventType);
|
|
128
130
|
entry.Value.Properties[property] = '$increment';
|
|
129
131
|
if (mapping.constantKey) {
|
|
130
|
-
entry.Value.Key =
|
|
132
|
+
entry.Value.Key = constantValueExpression(mapping.constantKey);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
@@ -135,7 +137,7 @@ export function applyPropertyMappings(prototype: object, property: string, fromB
|
|
|
135
137
|
const entry = ensureFromEntry(fromByEventType, mapping.eventType);
|
|
136
138
|
entry.Value.Properties[property] = '$decrement';
|
|
137
139
|
if (mapping.constantKey) {
|
|
138
|
-
entry.Value.Key =
|
|
140
|
+
entry.Value.Key = constantValueExpression(mapping.constantKey);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -143,7 +145,7 @@ export function applyPropertyMappings(prototype: object, property: string, fromB
|
|
|
143
145
|
const entry = ensureFromEntry(fromByEventType, mapping.eventType);
|
|
144
146
|
entry.Value.Properties[property] = '$count';
|
|
145
147
|
if (mapping.constantKey) {
|
|
146
|
-
entry.Value.Key =
|
|
148
|
+
entry.Value.Key = constantValueExpression(mapping.constantKey);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
}
|
|
@@ -164,17 +166,17 @@ function createEmptyChildrenDefinition(): ChildrenDefinitionLike {
|
|
|
164
166
|
|
|
165
167
|
/**
|
|
166
168
|
* Resolves the element type of a `@childrenFrom` collection property. TypeScript erases
|
|
167
|
-
* generic type arguments at runtime, so the element type
|
|
168
|
-
*
|
|
169
|
+
* generic type arguments at runtime, so the element type must be supplied by @childrenFrom
|
|
170
|
+
* or `@field(Array, { genericArguments: [ItemType] })` - without it,
|
|
169
171
|
* the children definition still registers correctly (structural Key/ParentKey wiring plus
|
|
170
172
|
* AutoMap), it just cannot also translate the child type's own decorators.
|
|
171
173
|
* @param type - The declaring class constructor.
|
|
172
174
|
* @param property - The property name.
|
|
173
175
|
* @returns The child element type constructor, or undefined when it cannot be resolved.
|
|
174
176
|
*/
|
|
175
|
-
function resolveChildElementType(type: Function, property: string): Function | undefined {
|
|
177
|
+
export function resolveChildElementType(type: Function, property: string): Function | undefined {
|
|
176
178
|
const field = Fields.getFieldsForType(type as Constructor).find(candidate => candidate.name === property);
|
|
177
|
-
return field?.genericArguments?.[0];
|
|
179
|
+
return getChildrenFromMetadata(type.prototype, property).find(metadata => metadata.childType)?.childType ?? field?.genericArguments?.[0];
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/**
|
|
@@ -185,7 +187,7 @@ function resolveChildElementType(type: Function, property: string): Function | u
|
|
|
185
187
|
* @param property - The property name.
|
|
186
188
|
* @returns The nested type constructor, or undefined when it cannot be resolved.
|
|
187
189
|
*/
|
|
188
|
-
function resolveNestedType(type: Function, property: string): Function | undefined {
|
|
190
|
+
export function resolveNestedType(type: Function, property: string): Function | undefined {
|
|
189
191
|
const field = Fields.getFieldsForType(type as Constructor).find(candidate => candidate.name === property);
|
|
190
192
|
if (field?.type && field.type !== Object && field.type !== Array) {
|
|
191
193
|
return field.type;
|
|
@@ -196,7 +198,8 @@ function resolveNestedType(type: Function, property: string): Function | undefin
|
|
|
196
198
|
return designType;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
const inferred = TypeIntrospector.getMembers(type).get(property);
|
|
202
|
+
return inferred && inferred !== Object && inferred !== Array ? inferred : undefined;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -340,7 +343,7 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
|
|
|
340
343
|
export function buildNestedEntry(type: Function, property: string): ChildrenDefinitionLike {
|
|
341
344
|
const nestedType = resolveNestedType(type, property);
|
|
342
345
|
const definition = createEmptyChildrenDefinition();
|
|
343
|
-
definition.IdentifiedBy =
|
|
346
|
+
definition.IdentifiedBy = notSetPropertyPath;
|
|
344
347
|
|
|
345
348
|
// A @clearWith on the property carrying @nested clears this nested object, the same as a
|
|
346
349
|
// class-level @clearWith on the nested type itself (which populateFromType also honors).
|