@cratis/chronicle 6.9.1 → 6.10.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 +219 -0
- package/connection/ChronicleConnection.ts +70 -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 +74 -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/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/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 +2 -1
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +16 -10
- 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/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/MaterializedReadModels.spec.ts +22 -0
- package/readModels/MaterializedReadModels.ts +2 -4
- package/readModels/ReadModels.spec.ts +58 -1
- package/readModels/ReadModels.ts +17 -11
- 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,6 +20,12 @@ import { getSetFromMetadata, setFrom } from './setFrom.js';
|
|
|
20
20
|
import { getSetValueMetadata, setValue } from './setValue.js';
|
|
21
21
|
import { getSubtractFromMetadata, subtractFrom } from './subtractFrom.js';
|
|
22
22
|
import { getTrackedJsonSchemaProperties, jsonSchemaProperty } from '../../schemas/jsonSchemaProperty.js';
|
|
23
|
+
import { DecoratorType } from '../../types/DecoratorType.js';
|
|
24
|
+
import { TypeDiscoverer } from '../../types/TypeDiscoverer.js';
|
|
25
|
+
import { DefaultClientArtifactsProvider } from '../../artifacts/DefaultClientArtifactsProvider.js';
|
|
26
|
+
import { ReadModels } from '../../readModels/ReadModels.js';
|
|
27
|
+
import type { ChronicleConnection } from '../../connection/ChronicleConnection.js';
|
|
28
|
+
import { vi } from 'vitest';
|
|
23
29
|
|
|
24
30
|
class Changed {}
|
|
25
31
|
|
|
@@ -45,7 +51,46 @@ class Mappings {
|
|
|
45
51
|
@jsonSchemaProperty() tracked!: string;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
class StandardMappedOnly {
|
|
55
|
+
@setFrom(Changed) value = '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class UnconstructedMappedOnly {
|
|
59
|
+
@setFrom(Changed) value = '';
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
describe('standard model-bound decorators', () => {
|
|
63
|
+
it('collects a standard-mapped class for lazy root discovery when constructed without file discovery', () => {
|
|
64
|
+
new StandardMappedOnly();
|
|
65
|
+
new StandardMappedOnly();
|
|
66
|
+
expect(TypeDiscoverer.default.getTypeByDecoratorTypeAndName(DecoratorType.ReadModel, 'StandardMappedOnly'))
|
|
67
|
+
.toBeUndefined();
|
|
68
|
+
expect(DefaultClientArtifactsProvider.default.readModels.filter(type => type === StandardMappedOnly)).toHaveLength(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('registers the declaring class once even when subclasses and many instances are constructed', () => {
|
|
72
|
+
class Base {
|
|
73
|
+
@setFrom(Changed) value = '';
|
|
74
|
+
}
|
|
75
|
+
class Derived extends Base {}
|
|
76
|
+
const track = vi.spyOn(TypeDiscoverer.default, 'trackModelBoundProperty');
|
|
77
|
+
try {
|
|
78
|
+
for (let index = 0; index < 50; index++) new Base();
|
|
79
|
+
new Derived();
|
|
80
|
+
expect(track.mock.calls.filter(([type]) => type === Base)).toHaveLength(1);
|
|
81
|
+
expect(track.mock.calls.some(([type]) => type === Derived)).toBe(false);
|
|
82
|
+
} finally {
|
|
83
|
+
track.mockRestore();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should explain that an unconstructed property-only model was never registered', async () => {
|
|
88
|
+
const getInstanceByKey = vi.fn().mockResolvedValue({ ReadModel: '{"value":"stored"}' });
|
|
89
|
+
const connection = { readModels: { getInstanceByKey } } as unknown as ChronicleConnection;
|
|
90
|
+
const readModels = new ReadModels('store', 'Default', connection, DefaultClientArtifactsProvider.default, 'sink');
|
|
91
|
+
await expect(readModels.findInstanceById(UnconstructedMappedOnly, 'id')).rejects.toThrow(/add a class-level @fromEvent/);
|
|
92
|
+
expect(getInstanceByKey).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
49
94
|
it('stores class and property annotations without constructing an instance', () => {
|
|
50
95
|
const target = Mappings.prototype;
|
|
51
96
|
expect(isNoAutoMap(Mappings)).toBe(true);
|
|
@@ -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 decrement property decorator. */
|
|
9
10
|
export interface DecrementMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:decrement';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function decrement(eventType: Function, constantKey?: 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: DecrementMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -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 fromAll property decorator. */
|
|
9
10
|
export interface FromAllMetadata {
|
|
@@ -25,7 +26,7 @@ const METADATA_KEY = 'chronicle:projection:fromAll';
|
|
|
25
26
|
* @returns A property decorator.
|
|
26
27
|
*/
|
|
27
28
|
export function fromAll(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
|
|
28
|
-
return
|
|
29
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
29
30
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
|
|
30
31
|
const metadata: FromAllMetadata = { property, contextProperty };
|
|
31
32
|
Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
|
|
@@ -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 fromEvery property decorator. */
|
|
9
10
|
export interface FromEveryMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:fromEvery';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function fromEvery(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
|
|
25
|
-
return
|
|
26
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
26
27
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
|
|
27
28
|
const metadata: FromEveryMetadata = { property, contextProperty };
|
|
28
29
|
Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
|
|
@@ -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 increment property decorator. */
|
|
9
10
|
export interface IncrementMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:increment';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function increment(eventType: Function, constantKey?: 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: IncrementMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -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 join property decorator. */
|
|
9
10
|
export interface JoinMetadata {
|
|
@@ -25,7 +26,7 @@ const METADATA_KEY = 'chronicle:projection:join';
|
|
|
25
26
|
* @returns A property decorator.
|
|
26
27
|
*/
|
|
27
28
|
export function join(eventType: Function, on?: string, eventPropertyName?: string): ChroniclePropertyDecorator {
|
|
28
|
-
return
|
|
29
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
29
30
|
const key = propertyKey.toString();
|
|
30
31
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
|
|
31
32
|
const existing: JoinMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
7
|
+
import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
|
|
7
8
|
|
|
8
9
|
const METADATA_KEY = 'chronicle:projection:nested';
|
|
9
10
|
|
|
@@ -13,7 +14,7 @@ const METADATA_KEY = 'chronicle:projection:nested';
|
|
|
13
14
|
* @param target - The class prototype.
|
|
14
15
|
* @param propertyKey - The property name.
|
|
15
16
|
*/
|
|
16
|
-
const decorateNested =
|
|
17
|
+
const decorateNested = decorateModelBoundProperty((target: object, propertyKey: string | symbol): void => {
|
|
17
18
|
const key = propertyKey.toString();
|
|
18
19
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
|
|
19
20
|
Reflect.defineMetadata(METADATA_KEY, true, target, key);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { hasPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
7
|
+
import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
|
|
7
8
|
|
|
8
9
|
const CLASS_METADATA_KEY = 'chronicle:projection:noAutoMap:class';
|
|
9
10
|
const PROPERTY_METADATA_KEY = 'chronicle:projection:noAutoMap:property';
|
|
@@ -18,7 +19,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:noAutoMap:property';
|
|
|
18
19
|
* @param target - The class constructor, or the class prototype when used on a property.
|
|
19
20
|
* @param propertyKey - The property name, when used as a property decorator.
|
|
20
21
|
*/
|
|
21
|
-
const decorateNoAutoMap =
|
|
22
|
+
const decorateNoAutoMap = decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol): void => {
|
|
22
23
|
if (propertyKey !== undefined) {
|
|
23
24
|
const key = propertyKey.toString();
|
|
24
25
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
|
|
7
|
+
import { ChronicleClassOrPropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
7
8
|
|
|
8
9
|
/** Metadata stored by the removedWith class or property decorator. */
|
|
9
10
|
export interface RemovedWithMetadata {
|
|
@@ -28,7 +29,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:removedWith:property';
|
|
|
28
29
|
* @returns A class and property decorator.
|
|
29
30
|
*/
|
|
30
31
|
export function removedWith(eventType: Function, key?: string, parentKey?: string): ChronicleClassOrPropertyDecorator {
|
|
31
|
-
return
|
|
32
|
+
return decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol) => {
|
|
32
33
|
if (propertyKey !== undefined) {
|
|
33
34
|
const propKey = propertyKey.toString();
|
|
34
35
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { decorateModelBoundClassOrProperty } from '../../types/modelBoundProperty.js';
|
|
7
|
+
import { ChronicleClassOrPropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
7
8
|
|
|
8
9
|
/** Metadata stored by the removedWithJoin class or property decorator. */
|
|
9
10
|
export interface RemovedWithJoinMetadata {
|
|
@@ -23,7 +24,7 @@ const PROPERTY_METADATA_KEY = 'chronicle:projection:removedWithJoin:property';
|
|
|
23
24
|
* @returns A class and property decorator.
|
|
24
25
|
*/
|
|
25
26
|
export function removedWithJoin(eventType: Function, key?: string): ChronicleClassOrPropertyDecorator {
|
|
26
|
-
return
|
|
27
|
+
return decorateModelBoundClassOrProperty((target: object, propertyKey?: string | symbol) => {
|
|
27
28
|
if (propertyKey !== undefined) {
|
|
28
29
|
const propKey = propertyKey.toString();
|
|
29
30
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propKey);
|
|
@@ -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 setFrom property decorator. */
|
|
9
10
|
export interface SetFromMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:setFrom';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function setFrom(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: SetFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -5,7 +5,8 @@ import 'reflect-metadata';
|
|
|
5
5
|
import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
|
|
6
6
|
import type { EventContext } from '../../events/index.js';
|
|
7
7
|
import { TypeIntrospector } from '../../types/index.js';
|
|
8
|
-
import {
|
|
8
|
+
import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
|
|
9
|
+
import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
9
10
|
|
|
10
11
|
/** Metadata stored by the setFromContext property decorator. */
|
|
11
12
|
export interface SetFromContextMetadata {
|
|
@@ -29,7 +30,7 @@ export function setFromContext(
|
|
|
29
30
|
eventType: Function,
|
|
30
31
|
contextPropertyOrAccessor?: string | PropertyAccessor<EventContext>
|
|
31
32
|
): ChroniclePropertyDecorator {
|
|
32
|
-
return
|
|
33
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
33
34
|
const key = propertyKey.toString();
|
|
34
35
|
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, key);
|
|
35
36
|
const existing: SetFromContextMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -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 setValue property decorator. */
|
|
9
10
|
export interface SetValueMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:setValue';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function setValue(eventType: Function, value: unknown): 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: SetValueMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -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 subtractFrom property decorator. */
|
|
9
10
|
export interface SubtractFromMetadata {
|
|
@@ -22,7 +23,7 @@ const METADATA_KEY = 'chronicle:projection:subtractFrom';
|
|
|
22
23
|
* @returns A property decorator.
|
|
23
24
|
*/
|
|
24
25
|
export function subtractFrom(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: SubtractFromMetadata[] = Reflect.getMetadata(METADATA_KEY, target, key) ?? [];
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
/** The wire representation of PropertyPath.NotSet, used for nested projections. */
|
|
5
|
+
export const notSetPropertyPath = '*NotSet*';
|
package/reactors/Reactors.ts
CHANGED
|
@@ -105,7 +105,9 @@ export class Reactors implements IReactors {
|
|
|
105
105
|
private readonly _lifecycle: ConnectionLifecycle;
|
|
106
106
|
private readonly _reactors = new Map<string, Constructor>();
|
|
107
107
|
private readonly _queues = new Map<string, AsyncQueue<ReactorMessage>>();
|
|
108
|
+
private readonly _observations = new Map<string, AbortController>();
|
|
108
109
|
private _registered = false;
|
|
110
|
+
private _disposed = false;
|
|
109
111
|
|
|
110
112
|
/**
|
|
111
113
|
* Creates a new {@link Reactors} instance.
|
|
@@ -133,6 +135,13 @@ export class Reactors implements IReactors {
|
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
/** Stops observations permanently when the owning client is disposed. */
|
|
139
|
+
dispose(): void {
|
|
140
|
+
this._disposed = true;
|
|
141
|
+
this._registered = false;
|
|
142
|
+
this.disconnectAll();
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
/** @inheritdoc */
|
|
137
146
|
async discover(): Promise<void> {
|
|
138
147
|
this._reactors.clear();
|
|
@@ -147,7 +156,7 @@ export class Reactors implements IReactors {
|
|
|
147
156
|
|
|
148
157
|
/** @inheritdoc */
|
|
149
158
|
async register(): Promise<void> {
|
|
150
|
-
if (this._registered) {
|
|
159
|
+
if (this._registered || this._disposed) {
|
|
151
160
|
return;
|
|
152
161
|
}
|
|
153
162
|
|
|
@@ -155,6 +164,7 @@ export class Reactors implements IReactors {
|
|
|
155
164
|
await this.discover();
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
if (this._disposed) return;
|
|
158
168
|
this._logger.info('Registering reactors', { count: this._reactors.size });
|
|
159
169
|
for (const [id, reactorType] of this._reactors) {
|
|
160
170
|
this.startObservation(id, reactorType);
|
|
@@ -164,6 +174,7 @@ export class Reactors implements IReactors {
|
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
private startObservation(id: string, reactorType: Constructor): void {
|
|
177
|
+
if (this._disposed) return;
|
|
167
178
|
const metadata = getReactorMetadata(reactorType)!;
|
|
168
179
|
const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
|
|
169
180
|
const eventTypes = this.getEventTypesFor(reactorType);
|
|
@@ -205,12 +216,12 @@ export class Reactors implements IReactors {
|
|
|
205
216
|
private scheduleReobserve(id: string, reactorType: Constructor): void {
|
|
206
217
|
// A disconnect clears the registration; the reconnect re-registers every
|
|
207
218
|
// reactor from scratch, so retrying here as well would double up.
|
|
208
|
-
if (!this._registered) {
|
|
219
|
+
if (!this._registered || this._disposed) {
|
|
209
220
|
return;
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
const handle = setTimeout(() => {
|
|
213
|
-
if (!this._registered) {
|
|
224
|
+
if (!this._registered || this._disposed) {
|
|
214
225
|
return;
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -228,7 +239,9 @@ export class Reactors implements IReactors {
|
|
|
228
239
|
eventTypes: EventTypeEntry[]
|
|
229
240
|
): Promise<void> {
|
|
230
241
|
const queue = new AsyncQueue<ReactorMessage>();
|
|
242
|
+
const controller = new AbortController();
|
|
231
243
|
this._queues.set(id, queue);
|
|
244
|
+
this._observations.set(id, controller);
|
|
232
245
|
|
|
233
246
|
queue.send({
|
|
234
247
|
Content: {
|
|
@@ -259,7 +272,7 @@ export class Reactors implements IReactors {
|
|
|
259
272
|
try {
|
|
260
273
|
const reactorInstance = new (reactorType as new () => Record<string, Function>)();
|
|
261
274
|
|
|
262
|
-
for await (const eventsToObserve of this._connection.reactors.observe(queue)) {
|
|
275
|
+
for await (const eventsToObserve of this._connection.reactors.observe(queue, { signal: controller.signal })) {
|
|
263
276
|
let lastSuccessfullyObservedEvent = SEQUENCE_NUMBER_UNAVAILABLE;
|
|
264
277
|
let state = ObservationState.Success;
|
|
265
278
|
const exceptionMessages: string[] = [];
|
|
@@ -358,7 +371,9 @@ export class Reactors implements IReactors {
|
|
|
358
371
|
// leak a duplicate stream on every reconnect.
|
|
359
372
|
if (this._queues.get(id) === queue) {
|
|
360
373
|
this._queues.delete(id);
|
|
374
|
+
this._observations.delete(id);
|
|
361
375
|
}
|
|
376
|
+
queue.complete();
|
|
362
377
|
}
|
|
363
378
|
}
|
|
364
379
|
|
|
@@ -422,9 +437,9 @@ export class Reactors implements IReactors {
|
|
|
422
437
|
}
|
|
423
438
|
|
|
424
439
|
private disconnectAll(): void {
|
|
425
|
-
for (const
|
|
426
|
-
|
|
427
|
-
|
|
440
|
+
for (const controller of this._observations.values()) controller.abort();
|
|
441
|
+
for (const queue of this._queues.values()) queue.complete();
|
|
442
|
+
this._observations.clear();
|
|
428
443
|
this._queues.clear();
|
|
429
444
|
}
|
|
430
445
|
}
|
package/reactors/reactor.ts
CHANGED
|
@@ -25,8 +25,8 @@ export interface ReactorMetadata {
|
|
|
25
25
|
* identifier. This is the TypeScript equivalent of the C# `[Reactor]` attribute.
|
|
26
26
|
*
|
|
27
27
|
* Reactors observe events from an event sequence and produce side effects.
|
|
28
|
-
* Method dispatch is by convention:
|
|
29
|
-
*
|
|
28
|
+
* Method dispatch is by convention: a handler's name is the event class name with
|
|
29
|
+
* its first letter lowercased (for example, `projectRegistered` handles `ProjectRegistered`).
|
|
30
30
|
*
|
|
31
31
|
* @param id - The unique identifier for the reactor. Defaults to the class name if omitted.
|
|
32
32
|
* @param eventSequenceId - Optional explicit event sequence identifier.
|
|
@@ -25,6 +25,28 @@ function createMaterializedReadModels(instancesJson: string[], releaseResponse:
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
describe('MaterializedReadModels', () => {
|
|
28
|
+
describe('when materialized models have undecorated members', () => {
|
|
29
|
+
class Model { name = ''; count = 0; occurred = new Date(0); }
|
|
30
|
+
const json = JSON.stringify({ name: 'stored', count: 3, occurred: '2024-02-01T00:00:00.000Z' });
|
|
31
|
+
|
|
32
|
+
it('should restore members in paged reads', async () => {
|
|
33
|
+
const { readModels } = createMaterializedReadModels([json]);
|
|
34
|
+
const [instance] = await readModels.getInstances(Model);
|
|
35
|
+
expect(instance).toMatchObject({ name: 'stored', count: 3 });
|
|
36
|
+
expect(instance.occurred).toEqual(new Date('2024-02-01T00:00:00.000Z'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should restore members in observed pages', async () => {
|
|
40
|
+
const connection = { materializedReadModels: { observeInstances: async function* () {
|
|
41
|
+
yield { Instances: [json] };
|
|
42
|
+
} } } as unknown as ChronicleConnection;
|
|
43
|
+
const readModels = new MaterializedReadModels('store', 'tenant', connection);
|
|
44
|
+
const response = await readModels.observeInstances(Model)[Symbol.asyncIterator]().next();
|
|
45
|
+
expect(response.value?.[0].name).toBe('stored');
|
|
46
|
+
expect(response.value?.[0].occurred).toBeInstanceOf(Date);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
28
50
|
describe('when a read model has a property decorated with @subject()', () => {
|
|
29
51
|
class Employee {
|
|
30
52
|
id = '';
|
|
@@ -7,6 +7,7 @@ import { ChronicleConnection } from '../connection/index.js';
|
|
|
7
7
|
import { JsonSchemaGenerator } from '../schemas/index.js';
|
|
8
8
|
import { getReadModelMetadata } from './readModel.js';
|
|
9
9
|
import { ReadModelSubjectResolver } from './ReadModelSubjectResolver.js';
|
|
10
|
+
import { deserializeReadModel } from './deserializeReadModel.js';
|
|
10
11
|
import type { IMaterializedReadModels } from './IMaterializedReadModels.js';
|
|
11
12
|
|
|
12
13
|
const defaultTake = 50;
|
|
@@ -82,10 +83,7 @@ export class MaterializedReadModels implements IMaterializedReadModels {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
private deserialize<TReadModel>(readModelType: Constructor<TReadModel>, json: string): TReadModel {
|
|
85
|
-
|
|
86
|
-
return Object.create(readModelType.prototype) as TReadModel;
|
|
87
|
-
}
|
|
88
|
-
return JsonSerializer.deserialize(readModelType as Constructor<object>, json) as TReadModel;
|
|
86
|
+
return deserializeReadModel(readModelType, json);
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
private async releaseInstances<TReadModel>(readModelType: Constructor<TReadModel>, instances: TReadModel[]): Promise<TReadModel[]> {
|
|
@@ -2,7 +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 'reflect-metadata';
|
|
5
|
-
import { field, type Constructor } from '@cratis/fundamentals';
|
|
5
|
+
import { ConceptAs, field, Guid, type Constructor } from '@cratis/fundamentals';
|
|
6
6
|
import { describe, expect, it, vi } from 'vitest';
|
|
7
7
|
import type { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
8
8
|
import type { ChronicleConnection } from '../connection/index.js';
|
|
@@ -10,6 +10,7 @@ import { subject } from '../compliance/subject.js';
|
|
|
10
10
|
import { pii } from '../compliance/pii.js';
|
|
11
11
|
import { reducer } from '../reducers/reducer.js';
|
|
12
12
|
import { fromEvent } from '../projections/modelBound/fromEvent.js';
|
|
13
|
+
import { setFrom } from '../projections/modelBound/setFrom.js';
|
|
13
14
|
import { ReadModels } from './ReadModels.js';
|
|
14
15
|
|
|
15
16
|
// Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
|
|
@@ -91,6 +92,62 @@ describe('ReadModels', () => {
|
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
95
|
+
describe('when reading a model with undecorated and model-bound members', () => {
|
|
96
|
+
class Code extends ConceptAs<string> {}
|
|
97
|
+
class MappedModel {
|
|
98
|
+
id = '';
|
|
99
|
+
name = '';
|
|
100
|
+
count = 0;
|
|
101
|
+
active = false;
|
|
102
|
+
occurred = new Date(0);
|
|
103
|
+
mapped = '';
|
|
104
|
+
code = new Code('initial');
|
|
105
|
+
guid = Guid.empty;
|
|
106
|
+
typed = Guid.empty;
|
|
107
|
+
}
|
|
108
|
+
field(Guid)(MappedModel.prototype, 'typed');
|
|
109
|
+
setFrom(SomeEvent, 'value')(MappedModel.prototype, 'mapped');
|
|
110
|
+
fromEvent(SomeEvent)(MappedModel);
|
|
111
|
+
const json = JSON.stringify({ id: 'a', name: 'stored', count: 42, active: true,
|
|
112
|
+
occurred: '2024-01-02T00:00:00.000Z', mapped: 'from event', code: 'stored-code',
|
|
113
|
+
guid: 'e951d9a0-720f-4e37-835f-701707f7b678',
|
|
114
|
+
typed: 'e951d9a0-720f-4e37-835f-701707f7b678', __internal: 'ignored' });
|
|
115
|
+
|
|
116
|
+
it('should restore all declared members for single and multiple reads', async () => {
|
|
117
|
+
const { readModels, getInstanceByKey } = createReadModels(MappedModel);
|
|
118
|
+
getInstanceByKey.mockResolvedValue({ ReadModel: json });
|
|
119
|
+
const getAllInstances = vi.fn().mockResolvedValue({ Instances: [json] });
|
|
120
|
+
const connection = { readModels: { getAllInstances } } as unknown as ChronicleConnection;
|
|
121
|
+
const provider = { readModels: [MappedModel], projections: [], reducers: [] } as unknown as IClientArtifactsProvider;
|
|
122
|
+
const all = await new ReadModels('store', 'tenant', connection, provider, 'sink').getInstances(MappedModel);
|
|
123
|
+
for (const instance of [await readModels.findInstanceById(MappedModel, 'a'),
|
|
124
|
+
await readModels.getInstanceById(MappedModel, 'a'), ...all]) {
|
|
125
|
+
expect(instance).toMatchObject({ id: 'a', name: 'stored', count: 42, active: true, mapped: 'from event' });
|
|
126
|
+
expect(instance?.occurred).toEqual(new Date('2024-01-02T00:00:00.000Z'));
|
|
127
|
+
expect(instance?.code).toBeInstanceOf(Code);
|
|
128
|
+
expect(instance?.code.value).toBe('stored-code');
|
|
129
|
+
expect(instance?.guid).toBeInstanceOf(Guid);
|
|
130
|
+
expect(instance?.typed).toBeInstanceOf(Guid);
|
|
131
|
+
expect(Object.hasOwn(instance as object, '__internal')).toBe(false);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should restore snapshot and watched instances', async () => {
|
|
136
|
+
const connection = { readModelExplorer: { allSnapshotsForReadModel: vi.fn().mockResolvedValue({ Data: [{ Instance: json }] }) },
|
|
137
|
+
readModels: { watch: async function* () {
|
|
138
|
+
yield { Namespace: 'tenant', ModelKey: 'a', ReadModel: json, Removed: false };
|
|
139
|
+
} } } as unknown as ChronicleConnection;
|
|
140
|
+
const provider = { readModels: [MappedModel], projections: [], reducers: [] } as unknown as IClientArtifactsProvider;
|
|
141
|
+
const readModels = new ReadModels('store', 'tenant', connection, provider, 'sink');
|
|
142
|
+
const snapshots = await readModels.getSnapshotsById(MappedModel, 'a');
|
|
143
|
+
const watched = await readModels.watch(MappedModel)[Symbol.asyncIterator]().next();
|
|
144
|
+
expect(snapshots[0].readModel.mapped).toBe('from event');
|
|
145
|
+
expect(snapshots[0].readModel.occurred).toBeInstanceOf(Date);
|
|
146
|
+
expect(watched.value?.readModel.name).toBe('stored');
|
|
147
|
+
expect(watched.value?.readModel.occurred).toBeInstanceOf(Date);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
94
151
|
describe('when reading a compliance-bearing reducer', () => {
|
|
95
152
|
class PrivateModel { id = ''; ssn = ''; }
|
|
96
153
|
field(String)(PrivateModel.prototype, 'id');
|