@cratis/chronicle 3.1.0 → 4.0.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 +4 -3
- package/EventStore.ts +4 -3
- package/README.md +40 -28
- package/compliance/ComplianceContracts.ts +59 -0
- package/compliance/IPIIManager.ts +12 -0
- package/compliance/PIIManager.spec.ts +50 -0
- package/compliance/PIIManager.ts +9 -0
- package/compliance/PIINotSupportedOnEventSourceId.ts +21 -0
- package/compliance/index.ts +2 -0
- package/compliance/pii.spec.ts +37 -0
- package/compliance/pii.ts +16 -1
- package/compliance/subject.spec.ts +45 -0
- package/compliance/subject.ts +64 -0
- package/connection/callResults.spec.ts +90 -0
- package/connection/callResults.ts +112 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +4 -3
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +4 -3
- package/dist/EventStore.js.map +1 -1
- package/dist/compliance/ComplianceContracts.d.ts +31 -0
- package/dist/compliance/ComplianceContracts.d.ts.map +1 -1
- package/dist/compliance/ComplianceContracts.js +52 -0
- package/dist/compliance/ComplianceContracts.js.map +1 -1
- package/dist/compliance/IPIIManager.d.ts +11 -0
- package/dist/compliance/IPIIManager.d.ts.map +1 -1
- package/dist/compliance/PIIManager.d.ts +2 -0
- package/dist/compliance/PIIManager.d.ts.map +1 -1
- package/dist/compliance/PIIManager.js +8 -0
- package/dist/compliance/PIIManager.js.map +1 -1
- package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts +11 -0
- package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts.map +1 -0
- package/dist/compliance/PIINotSupportedOnEventSourceId.js +19 -0
- package/dist/compliance/PIINotSupportedOnEventSourceId.js.map +1 -0
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.d.ts.map +1 -1
- package/dist/compliance/index.js +2 -0
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/pii.d.ts.map +1 -1
- package/dist/compliance/pii.js +13 -1
- package/dist/compliance/pii.js.map +1 -1
- package/dist/compliance/subject.d.ts +39 -0
- package/dist/compliance/subject.d.ts.map +1 -0
- package/dist/compliance/subject.js +58 -0
- package/dist/compliance/subject.js.map +1 -0
- package/dist/connection/callResults.d.ts +69 -0
- package/dist/connection/callResults.d.ts.map +1 -0
- package/dist/connection/callResults.js +74 -0
- package/dist/connection/callResults.js.map +1 -0
- package/dist/eventSequences/AppendOptions.d.ts +8 -0
- package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
- package/dist/eventSequences/EventForEventSourceId.d.ts +7 -0
- package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +17 -6
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/events/EventContext.d.ts +3 -0
- package/dist/events/EventContext.d.ts.map +1 -1
- package/dist/events/Tag.d.ts +10 -0
- package/dist/events/Tag.d.ts.map +1 -0
- package/dist/events/Tag.js +16 -0
- package/dist/events/Tag.js.map +1 -0
- package/dist/events/constraints/Constraints.js +3 -3
- package/dist/events/constraints/Constraints.js.map +1 -1
- package/dist/events/filterEventsByTagDecorator.d.ts +35 -0
- package/dist/events/filterEventsByTagDecorator.d.ts.map +1 -0
- package/dist/events/filterEventsByTagDecorator.js +48 -0
- package/dist/events/filterEventsByTagDecorator.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +4 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/mergeTags.d.ts +12 -0
- package/dist/events/mergeTags.d.ts.map +1 -0
- package/dist/events/mergeTags.js +26 -0
- package/dist/events/mergeTags.js.map +1 -0
- package/dist/events/tagDecorator.d.ts +38 -0
- package/dist/events/tagDecorator.d.ts.map +1 -0
- package/dist/events/tagDecorator.js +54 -0
- package/dist/events/tagDecorator.js.map +1 -0
- package/dist/jobs/IJobs.d.ts +4 -4
- package/dist/jobs/IJobs.d.ts.map +1 -1
- package/dist/jobs/Jobs.d.ts +4 -4
- package/dist/jobs/Jobs.d.ts.map +1 -1
- package/dist/jobs/Jobs.js +12 -10
- package/dist/jobs/Jobs.js.map +1 -1
- package/dist/observation/ObserverRunningState.d.ts +9 -15
- package/dist/observation/ObserverRunningState.d.ts.map +1 -1
- package/dist/observation/ObserverRunningState.js +8 -14
- package/dist/observation/ObserverRunningState.js.map +1 -1
- package/dist/observation/index.d.ts +1 -0
- package/dist/observation/index.d.ts.map +1 -1
- package/dist/observation/index.js +1 -0
- package/dist/observation/index.js.map +1 -1
- package/dist/observation/toObserverRunningState.d.ts +9 -0
- package/dist/observation/toObserverRunningState.d.ts.map +1 -0
- package/dist/observation/toObserverRunningState.js +26 -0
- package/dist/observation/toObserverRunningState.js.map +1 -0
- package/dist/projections/IProjections.d.ts +71 -1
- package/dist/projections/IProjections.d.ts.map +1 -1
- package/dist/projections/ProjectionQueryResult.d.ts +8 -0
- package/dist/projections/ProjectionQueryResult.d.ts.map +1 -0
- package/dist/projections/ProjectionQueryResult.js +4 -0
- package/dist/projections/ProjectionQueryResult.js.map +1 -0
- package/dist/projections/ProjectionState.d.ts +18 -0
- package/dist/projections/ProjectionState.d.ts.map +1 -0
- package/dist/projections/ProjectionState.js +4 -0
- package/dist/projections/ProjectionState.js.map +1 -0
- package/dist/projections/Projections.d.ts +46 -1
- package/dist/projections/Projections.d.ts.map +1 -1
- package/dist/projections/Projections.js +153 -6
- package/dist/projections/Projections.js.map +1 -1
- package/dist/projections/UnableToQueryProjection.d.ts +12 -0
- package/dist/projections/UnableToQueryProjection.d.ts.map +1 -0
- package/dist/projections/UnableToQueryProjection.js +17 -0
- package/dist/projections/UnableToQueryProjection.js.map +1 -0
- package/dist/projections/index.d.ts +3 -0
- package/dist/projections/index.d.ts.map +1 -1
- package/dist/projections/index.js +1 -0
- package/dist/projections/index.js.map +1 -1
- package/dist/projections/modelBound/eventSequence.d.ts +22 -0
- package/dist/projections/modelBound/eventSequence.d.ts.map +1 -0
- package/dist/projections/modelBound/eventSequence.js +34 -0
- package/dist/projections/modelBound/eventSequence.js.map +1 -0
- package/dist/projections/modelBound/fromAll.d.ts +26 -0
- package/dist/projections/modelBound/fromAll.d.ts.map +1 -0
- package/dist/projections/modelBound/fromAll.js +31 -0
- package/dist/projections/modelBound/fromAll.js.map +1 -0
- package/dist/projections/modelBound/index.d.ts +4 -0
- package/dist/projections/modelBound/index.d.ts.map +1 -1
- package/dist/projections/modelBound/index.js +3 -0
- package/dist/projections/modelBound/index.js.map +1 -1
- package/dist/projections/modelBound/noAutoMap.d.ts +26 -0
- package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -0
- package/dist/projections/modelBound/noAutoMap.js +44 -0
- package/dist/projections/modelBound/noAutoMap.js.map +1 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +7 -3
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/readModels/MaterializedReadModels.d.ts +0 -1
- package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.js +2 -8
- package/dist/readModels/MaterializedReadModels.js.map +1 -1
- package/dist/readModels/ReadModelSubjectResolver.d.ts +22 -0
- package/dist/readModels/ReadModelSubjectResolver.d.ts.map +1 -0
- package/dist/readModels/ReadModelSubjectResolver.js +38 -0
- package/dist/readModels/ReadModelSubjectResolver.js.map +1 -0
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +7 -7
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/index.d.ts +1 -0
- package/dist/readModels/index.d.ts.map +1 -1
- package/dist/readModels/index.js +1 -0
- package/dist/readModels/index.js.map +1 -1
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +21 -3
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/schemas/JsonSchema.d.ts +14 -0
- package/dist/schemas/JsonSchema.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts +61 -5
- package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.js +130 -26
- package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/seeding/EventSeeding.d.ts.map +1 -1
- package/dist/seeding/EventSeeding.js +3 -1
- package/dist/seeding/EventSeeding.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eventSequences/AppendOptions.ts +9 -0
- package/eventSequences/EventForEventSourceId.ts +9 -0
- package/eventSequences/EventSequence.ts +20 -6
- package/events/EventContext.ts +4 -0
- package/events/Tag.ts +14 -0
- package/events/constraints/Constraints.ts +3 -3
- package/events/filterEventsByTagDecorator.ts +51 -0
- package/events/index.ts +4 -0
- package/events/mergeTags.spec.ts +50 -0
- package/events/mergeTags.ts +27 -0
- package/events/tagDecorator.spec.ts +115 -0
- package/events/tagDecorator.ts +59 -0
- package/jobs/IJobs.ts +4 -4
- package/jobs/Jobs.ts +16 -14
- package/observation/ObserverRunningState.ts +9 -18
- package/observation/index.ts +1 -0
- package/observation/toObserverRunningState.ts +27 -0
- package/package.json +25 -10
- package/projections/IProjections.ts +81 -1
- package/projections/ProjectionQueryResult.ts +10 -0
- package/projections/ProjectionState.ts +25 -0
- package/projections/Projections.childrenAndNested.spec.ts +1 -1
- package/projections/Projections.modelBoundCompleteness.spec.ts +224 -0
- package/projections/Projections.operationalSurface.spec.ts +257 -0
- package/projections/Projections.spec.ts +1 -1
- package/projections/Projections.ts +180 -5
- package/projections/UnableToQueryProjection.ts +17 -0
- package/projections/index.ts +3 -0
- package/projections/modelBound/eventSequence.ts +38 -0
- package/projections/modelBound/fromAll.ts +42 -0
- package/projections/modelBound/index.ts +4 -0
- package/projections/modelBound/noAutoMap.ts +47 -0
- package/reactors/Reactors.ts +7 -3
- package/readModels/MaterializedReadModels.spec.ts +98 -0
- package/readModels/MaterializedReadModels.ts +2 -8
- package/readModels/ReadModelSubjectResolver.spec.ts +84 -0
- package/readModels/ReadModelSubjectResolver.ts +44 -0
- package/readModels/ReadModels.spec.ts +118 -0
- package/readModels/ReadModels.ts +7 -7
- package/readModels/index.ts +1 -0
- package/reducers/Reducers.spec.ts +80 -0
- package/reducers/Reducers.ts +22 -3
- package/schemas/JsonSchema.ts +16 -0
- package/schemas/JsonSchemaGenerator.spec.ts +195 -0
- package/schemas/JsonSchemaGenerator.ts +145 -27
- package/schemas/index.ts +1 -1
- package/seeding/EventSeeding.ts +3 -1
|
@@ -0,0 +1,257 @@
|
|
|
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 'reflect-metadata';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { IClientArtifactsProvider } from '../artifacts';
|
|
7
|
+
import { ChronicleConnection } from '../connection';
|
|
8
|
+
import { ObserverRunningState } from '../observation/ObserverRunningState';
|
|
9
|
+
import { eventType } from '../events/eventTypeDecorator';
|
|
10
|
+
import { readModel } from '../readModels/readModel';
|
|
11
|
+
import { eventSequence } from './modelBound/eventSequence';
|
|
12
|
+
import { fromEvent } from './modelBound/fromEvent';
|
|
13
|
+
import { projection } from './declarative/projection';
|
|
14
|
+
import type { IProjectionBuilderFor } from './declarative/IProjectionBuilderFor';
|
|
15
|
+
import type { IProjectionFor } from './declarative/IProjectionFor';
|
|
16
|
+
import { Projections } from './Projections';
|
|
17
|
+
import { UnableToQueryProjection } from './UnableToQueryProjection';
|
|
18
|
+
|
|
19
|
+
// Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
|
|
20
|
+
// fixtures don't depend on the test runner's decorator-syntax support.
|
|
21
|
+
|
|
22
|
+
class OpStateChanged {
|
|
23
|
+
value!: string;
|
|
24
|
+
}
|
|
25
|
+
eventType()(OpStateChanged);
|
|
26
|
+
|
|
27
|
+
class OpSummary {
|
|
28
|
+
id!: string;
|
|
29
|
+
value!: string;
|
|
30
|
+
}
|
|
31
|
+
fromEvent(OpStateChanged)(OpSummary);
|
|
32
|
+
eventSequence('custom-op-sequence')(OpSummary);
|
|
33
|
+
readModel()(OpSummary);
|
|
34
|
+
|
|
35
|
+
class UndiscoveredSummary {
|
|
36
|
+
id!: string;
|
|
37
|
+
}
|
|
38
|
+
readModel()(UndiscoveredSummary);
|
|
39
|
+
|
|
40
|
+
class DeclarativeReadModel {
|
|
41
|
+
id!: string;
|
|
42
|
+
}
|
|
43
|
+
readModel()(DeclarativeReadModel);
|
|
44
|
+
|
|
45
|
+
class DeclarativeSummaryProjection implements IProjectionFor<DeclarativeReadModel> {
|
|
46
|
+
// discover() never calls define() - only register() does - so a no-op body is sufficient here.
|
|
47
|
+
define(_builder: IProjectionBuilderFor<DeclarativeReadModel>): void {}
|
|
48
|
+
}
|
|
49
|
+
projection('DeclarativeSummary', DeclarativeReadModel)(DeclarativeSummaryProjection);
|
|
50
|
+
|
|
51
|
+
function wireObserverInformation() {
|
|
52
|
+
return {
|
|
53
|
+
RunningState: 1, // Active
|
|
54
|
+
IsSubscribed: true,
|
|
55
|
+
NextEventSequenceNumber: 5n,
|
|
56
|
+
LastHandledEventSequenceNumber: 4n,
|
|
57
|
+
TailEventSequenceNumber: 4n
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function createDiscoveredProjections() {
|
|
62
|
+
const getObserverInformation = vi.fn().mockResolvedValue(wireObserverInformation());
|
|
63
|
+
const replay = vi.fn().mockResolvedValue({ JobId: '11111111-1111-1111-1111-111111111111' });
|
|
64
|
+
const getFailedPartitions = vi.fn().mockResolvedValue({ items: [] });
|
|
65
|
+
const preview = vi.fn().mockResolvedValue({ Value0: { ReadModelEntries: ['{"id":"1"}'] } });
|
|
66
|
+
const registerMock = vi.fn().mockResolvedValue(undefined);
|
|
67
|
+
const registerManyMock = vi.fn().mockResolvedValue(undefined);
|
|
68
|
+
|
|
69
|
+
const connection = {
|
|
70
|
+
observers: { getObserverInformation, replay },
|
|
71
|
+
failedPartitions: { getFailedPartitions },
|
|
72
|
+
projections: { register: registerMock, preview },
|
|
73
|
+
readModels: { registerMany: registerManyMock }
|
|
74
|
+
} as unknown as ChronicleConnection;
|
|
75
|
+
|
|
76
|
+
const clientArtifacts: IClientArtifactsProvider = {
|
|
77
|
+
eventTypes: [],
|
|
78
|
+
readModels: [OpSummary, UndiscoveredSummary, DeclarativeReadModel] as unknown as IClientArtifactsProvider['readModels'],
|
|
79
|
+
reactors: [],
|
|
80
|
+
reducers: [],
|
|
81
|
+
seeders: [],
|
|
82
|
+
constraints: [],
|
|
83
|
+
projections: [DeclarativeSummaryProjection] as unknown as IClientArtifactsProvider['projections'],
|
|
84
|
+
webhooks: [],
|
|
85
|
+
eventTypeMigrations: []
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const projections = new Projections('test-store', 'test-namespace', connection, clientArtifacts, 'test-sink');
|
|
89
|
+
await projections.discover();
|
|
90
|
+
|
|
91
|
+
return { projections, getObserverInformation, replay, getFailedPartitions, preview };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('Projections operational surface', () => {
|
|
95
|
+
describe('when checking hasFor with a discovered projection identifier', () => {
|
|
96
|
+
it('should return true', async () => {
|
|
97
|
+
const { projections } = await createDiscoveredProjections();
|
|
98
|
+
expect(projections.hasFor('OpSummary')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('when checking hasFor with an unknown projection identifier', () => {
|
|
103
|
+
it('should return false', async () => {
|
|
104
|
+
const { projections } = await createDiscoveredProjections();
|
|
105
|
+
expect(projections.hasFor('unknown-projection')).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('when checking hasForModel for a discovered model-bound read model', () => {
|
|
110
|
+
it('should return true', async () => {
|
|
111
|
+
const { projections } = await createDiscoveredProjections();
|
|
112
|
+
expect(projections.hasForModel(OpSummary)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('when checking hasForModel for an undiscovered read model', () => {
|
|
117
|
+
it('should return false', async () => {
|
|
118
|
+
const { projections } = await createDiscoveredProjections();
|
|
119
|
+
expect(projections.hasForModel(UndiscoveredSummary)).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('when getting the projection identifier for a model-bound read model', () => {
|
|
124
|
+
it('should resolve the read model identifier', async () => {
|
|
125
|
+
const { projections } = await createDiscoveredProjections();
|
|
126
|
+
expect(projections.getProjectionIdFor(OpSummary).value).toBe('OpSummary');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('when getting the projection identifier for a declarative projection with an explicit read model type', () => {
|
|
131
|
+
it('should resolve the declared projection identifier', async () => {
|
|
132
|
+
const { projections } = await createDiscoveredProjections();
|
|
133
|
+
expect(projections.getProjectionIdFor(DeclarativeReadModel).value).toBe('DeclarativeSummary');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('when getting the projection identifier for an undiscovered read model', () => {
|
|
138
|
+
it('should throw', async () => {
|
|
139
|
+
const { projections } = await createDiscoveredProjections();
|
|
140
|
+
expect(() => projections.getProjectionIdFor(UndiscoveredSummary)).toThrow();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('when getting state for a projection by identifier', () => {
|
|
145
|
+
it('should call GetObserverInformation and map the response', async () => {
|
|
146
|
+
const { projections, getObserverInformation } = await createDiscoveredProjections();
|
|
147
|
+
|
|
148
|
+
const state = await projections.getStateFor('OpSummary');
|
|
149
|
+
|
|
150
|
+
expect(getObserverInformation).toHaveBeenCalledTimes(1);
|
|
151
|
+
const request = getObserverInformation.mock.calls[0][0];
|
|
152
|
+
expect(request.EventStore).toEqual('test-store');
|
|
153
|
+
expect(request.Namespace).toEqual('test-namespace');
|
|
154
|
+
expect(request.ObserverId).toEqual('OpSummary');
|
|
155
|
+
|
|
156
|
+
expect(state.runningState).toBe(ObserverRunningState.Active);
|
|
157
|
+
expect(state.isSubscribed).toBe(true);
|
|
158
|
+
expect(state.nextEventSequenceNumber.value).toEqual(5n);
|
|
159
|
+
expect(state.lastHandledEventSequenceNumber.value).toEqual(4n);
|
|
160
|
+
expect(state.tailEventSequenceNumber.value).toEqual(4n);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('when getting state for a model-bound read model with a custom event sequence', () => {
|
|
165
|
+
it('should resolve the identifier and pass the model-declared event sequence', async () => {
|
|
166
|
+
const { projections, getObserverInformation } = await createDiscoveredProjections();
|
|
167
|
+
|
|
168
|
+
await projections.getStateForModel(OpSummary);
|
|
169
|
+
|
|
170
|
+
const request = getObserverInformation.mock.calls[0][0];
|
|
171
|
+
expect(request.ObserverId).toEqual('OpSummary');
|
|
172
|
+
expect(request.EventSequenceId).toEqual('custom-op-sequence');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('when getting failed partitions for a model-bound read model', () => {
|
|
177
|
+
it('should call GetFailedPartitions with the resolved projection identifier', async () => {
|
|
178
|
+
const { projections, getFailedPartitions } = await createDiscoveredProjections();
|
|
179
|
+
|
|
180
|
+
await projections.getFailedPartitionsForModel(OpSummary);
|
|
181
|
+
|
|
182
|
+
expect(getFailedPartitions).toHaveBeenCalledTimes(1);
|
|
183
|
+
const request = getFailedPartitions.mock.calls[0][0];
|
|
184
|
+
expect(request.ObserverId).toEqual('OpSummary');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('when replaying a projection by identifier', () => {
|
|
189
|
+
it('should call Replay and parse the returned job id', async () => {
|
|
190
|
+
const { projections, replay } = await createDiscoveredProjections();
|
|
191
|
+
|
|
192
|
+
const jobId = await projections.replay('OpSummary');
|
|
193
|
+
|
|
194
|
+
expect(replay).toHaveBeenCalledTimes(1);
|
|
195
|
+
const request = replay.mock.calls[0][0];
|
|
196
|
+
expect(request.ObserverId).toEqual('OpSummary');
|
|
197
|
+
expect(jobId.toString()).toEqual('11111111-1111-1111-1111-111111111111');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('when replaying a projection that is not replayable', () => {
|
|
202
|
+
it('should resolve to a not-set job id', async () => {
|
|
203
|
+
const { projections, replay } = await createDiscoveredProjections();
|
|
204
|
+
replay.mockResolvedValueOnce({ JobId: '' });
|
|
205
|
+
|
|
206
|
+
const jobId = await projections.replay('OpSummary');
|
|
207
|
+
|
|
208
|
+
expect(jobId.value.toString()).toEqual('00000000-0000-0000-0000-000000000000');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('when replaying a model-bound read model', () => {
|
|
213
|
+
it('should resolve the identifier and replay it', async () => {
|
|
214
|
+
const { projections, replay } = await createDiscoveredProjections();
|
|
215
|
+
|
|
216
|
+
await projections.replayForModel(OpSummary);
|
|
217
|
+
|
|
218
|
+
const request = replay.mock.calls[0][0];
|
|
219
|
+
expect(request.ObserverId).toEqual('OpSummary');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('when querying a projection declaration', () => {
|
|
224
|
+
it('should call Preview and return the read model entries', async () => {
|
|
225
|
+
const { projections, preview } = await createDiscoveredProjections();
|
|
226
|
+
|
|
227
|
+
const result = await projections.query('projection Orders\n from OpStateChanged');
|
|
228
|
+
|
|
229
|
+
expect(preview).toHaveBeenCalledTimes(1);
|
|
230
|
+
const request = preview.mock.calls[0][0];
|
|
231
|
+
expect(request.EventSequenceId).toEqual('event-log');
|
|
232
|
+
expect(result.readModelEntries).toEqual(['{"id":"1"}']);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('when querying a projection declaration with a custom event sequence', () => {
|
|
237
|
+
it('should pass the given event sequence identifier', async () => {
|
|
238
|
+
const { projections, preview } = await createDiscoveredProjections();
|
|
239
|
+
|
|
240
|
+
await projections.query('projection Orders\n from OpStateChanged', 'inbox');
|
|
241
|
+
|
|
242
|
+
const request = preview.mock.calls[0][0];
|
|
243
|
+
expect(request.EventSequenceId).toEqual('inbox');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('when querying a projection declaration that fails to parse', () => {
|
|
248
|
+
it('should throw UnableToQueryProjection with the syntax errors', async () => {
|
|
249
|
+
const { projections, preview } = await createDiscoveredProjections();
|
|
250
|
+
preview.mockResolvedValueOnce({
|
|
251
|
+
Value1: { Errors: [{ Message: 'Unexpected token', Line: 2, Column: 3 }] }
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await expect(projections.query('projection Bad')).rejects.toThrow(UnableToQueryProjection);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -85,7 +85,7 @@ function createProjections(readModels: (new (...args: unknown[]) => unknown)[])
|
|
|
85
85
|
eventTypeMigrations: []
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
const projections = new Projections('test-store', connection, clientArtifacts, 'test-sink');
|
|
88
|
+
const projections = new Projections('test-store', 'test-namespace', connection, clientArtifacts, 'test-sink');
|
|
89
89
|
return { projections, registerMock };
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -12,6 +12,11 @@ import { ChronicleConnection } from '../connection';
|
|
|
12
12
|
import { toContractsGuid } from '../connection/Guid';
|
|
13
13
|
import { WellKnownSinks } from '../sinks';
|
|
14
14
|
import { EventSequenceId } from '../eventSequences/EventSequenceId';
|
|
15
|
+
import { EventSequenceNumber } from '../eventSequences/EventSequenceNumber';
|
|
16
|
+
import { JobId } from '../jobs/JobId';
|
|
17
|
+
import { FailedPartition } from '../observation/FailedPartition';
|
|
18
|
+
import { FailedPartitions } from '../observation/FailedPartitions';
|
|
19
|
+
import { toObserverRunningState } from '../observation/toObserverRunningState';
|
|
15
20
|
import { getReadModelMetadata } from '../readModels';
|
|
16
21
|
import { TypeIntrospector } from '../types';
|
|
17
22
|
import { IProjections } from './IProjections';
|
|
@@ -24,20 +29,28 @@ import {
|
|
|
24
29
|
buildNestedEntry,
|
|
25
30
|
ChildrenDefinitionLike,
|
|
26
31
|
ContractEventType,
|
|
32
|
+
ensureFromEntry,
|
|
27
33
|
FromRecord,
|
|
28
34
|
getEventTypeMapKey,
|
|
29
35
|
toContractEventType
|
|
30
36
|
} from './modelBound/childrenAndNestedBuilder';
|
|
31
37
|
import { getChildrenFromMetadata } from './modelBound/childrenFrom';
|
|
38
|
+
import { getClearWithPropertyMetadata } from './modelBound/clearWith';
|
|
39
|
+
import { getEventSequenceMetadata } from './modelBound/eventSequence';
|
|
40
|
+
import { getFromAllMetadata } from './modelBound/fromAll';
|
|
32
41
|
import { getFromEveryMetadata } from './modelBound/fromEvery';
|
|
33
42
|
import { getFromEventMetadata, hasFromEventMetadata } from './modelBound/fromEvent';
|
|
34
43
|
import { getJoinMetadata } from './modelBound/join';
|
|
44
|
+
import { isNoAutoMap, isPropertyNoAutoMap } from './modelBound/noAutoMap';
|
|
35
45
|
import { ProjectionId } from './ProjectionId';
|
|
46
|
+
import { ProjectionQueryResult } from './ProjectionQueryResult';
|
|
47
|
+
import { ProjectionState } from './ProjectionState';
|
|
36
48
|
import { isNested } from './modelBound/nested';
|
|
37
49
|
import { isNotRewindable } from './modelBound/notRewindable';
|
|
38
50
|
import { isPassive } from './modelBound/passive';
|
|
39
51
|
import { getRemovedWithClassMetadata, getRemovedWithPropertyMetadata } from './modelBound/removedWith';
|
|
40
52
|
import { getRemovedWithJoinClassMetadata, getRemovedWithJoinPropertyMetadata } from './modelBound/removedWithJoin';
|
|
53
|
+
import { UnableToQueryProjection } from './UnableToQueryProjection';
|
|
41
54
|
|
|
42
55
|
interface ResolvedModelBoundMetadata {
|
|
43
56
|
id: ProjectionId;
|
|
@@ -52,19 +65,27 @@ interface ResolvedModelBoundMetadata {
|
|
|
52
65
|
export class Projections implements IProjections {
|
|
53
66
|
private readonly _declarative = new Map<string, Constructor>();
|
|
54
67
|
private readonly _modelBound = new Map<string, Constructor>();
|
|
68
|
+
private readonly _failedPartitions: FailedPartitions;
|
|
55
69
|
|
|
56
70
|
private readonly _logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/projections' });
|
|
57
71
|
|
|
58
72
|
/**
|
|
59
73
|
* Creates a new {@link Projections} instance.
|
|
74
|
+
* @param _eventStore - The event store name.
|
|
75
|
+
* @param _namespace - The event store namespace.
|
|
76
|
+
* @param _connection - Chronicle connection.
|
|
60
77
|
* @param _clientArtifacts - Provider for discovered client artifact types.
|
|
78
|
+
* @param _defaultSinkTypeId - The identifier of the default read model sink.
|
|
61
79
|
*/
|
|
62
80
|
constructor(
|
|
63
81
|
private readonly _eventStore: string,
|
|
82
|
+
private readonly _namespace: string,
|
|
64
83
|
private readonly _connection: ChronicleConnection,
|
|
65
84
|
private readonly _clientArtifacts: IClientArtifactsProvider,
|
|
66
85
|
private readonly _defaultSinkTypeId: string
|
|
67
|
-
) {
|
|
86
|
+
) {
|
|
87
|
+
this._failedPartitions = new FailedPartitions(_eventStore, _namespace, _connection);
|
|
88
|
+
}
|
|
68
89
|
|
|
69
90
|
/** @inheritdoc */
|
|
70
91
|
async discover(): Promise<void> {
|
|
@@ -137,6 +158,148 @@ export class Projections implements IProjections {
|
|
|
137
158
|
}
|
|
138
159
|
}
|
|
139
160
|
|
|
161
|
+
/** @inheritdoc */
|
|
162
|
+
hasFor(projectionId: ProjectionId | string): boolean {
|
|
163
|
+
const id = this.toProjectionIdValue(projectionId);
|
|
164
|
+
return this._declarative.has(id) || this._modelBound.has(id);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @inheritdoc */
|
|
168
|
+
hasForModel(readModelType: Constructor): boolean {
|
|
169
|
+
try {
|
|
170
|
+
this.resolveProjectionIdForModel(readModelType);
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @inheritdoc */
|
|
178
|
+
getProjectionIdFor(readModelType: Constructor): ProjectionId {
|
|
179
|
+
return this.resolveProjectionIdForModel(readModelType);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @inheritdoc */
|
|
183
|
+
async getStateFor(projectionId: ProjectionId | string): Promise<ProjectionState> {
|
|
184
|
+
const id = this.toProjectionIdValue(projectionId);
|
|
185
|
+
const eventSequenceId = this.resolveEventSequenceIdFor(id);
|
|
186
|
+
|
|
187
|
+
const response = await this._connection.observers.getObserverInformation({
|
|
188
|
+
EventStore: this._eventStore,
|
|
189
|
+
Namespace: this._namespace,
|
|
190
|
+
ObserverId: id,
|
|
191
|
+
EventSequenceId: eventSequenceId
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
runningState: toObserverRunningState(response.RunningState),
|
|
196
|
+
isSubscribed: response.IsSubscribed,
|
|
197
|
+
nextEventSequenceNumber: new EventSequenceNumber(response.NextEventSequenceNumber),
|
|
198
|
+
lastHandledEventSequenceNumber: new EventSequenceNumber(response.LastHandledEventSequenceNumber),
|
|
199
|
+
tailEventSequenceNumber: new EventSequenceNumber(response.TailEventSequenceNumber)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @inheritdoc */
|
|
204
|
+
getStateForModel(readModelType: Constructor): Promise<ProjectionState> {
|
|
205
|
+
return this.getStateFor(this.resolveProjectionIdForModel(readModelType));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @inheritdoc */
|
|
209
|
+
getFailedPartitionsForModel(readModelType: Constructor): Promise<FailedPartition[]> {
|
|
210
|
+
const projectionId = this.resolveProjectionIdForModel(readModelType);
|
|
211
|
+
return this._failedPartitions.getFailedPartitionsFor(projectionId.value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @inheritdoc */
|
|
215
|
+
async replay(projectionId: ProjectionId | string): Promise<JobId> {
|
|
216
|
+
const id = this.toProjectionIdValue(projectionId);
|
|
217
|
+
|
|
218
|
+
const response = await this._connection.observers.replay({
|
|
219
|
+
EventStore: this._eventStore,
|
|
220
|
+
Namespace: this._namespace,
|
|
221
|
+
ObserverId: id,
|
|
222
|
+
|
|
223
|
+
// The kernel resolves the observer's own event sequence from its identifier - it does not
|
|
224
|
+
// need to be told which one to replay.
|
|
225
|
+
EventSequenceId: ''
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return response.JobId ? JobId.from(response.JobId) : JobId.from(Guid.empty);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** @inheritdoc */
|
|
232
|
+
replayForModel(readModelType: Constructor): Promise<JobId> {
|
|
233
|
+
return this.replay(this.resolveProjectionIdForModel(readModelType));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** @inheritdoc */
|
|
237
|
+
async query(declaration: string, eventSequenceId: string = EventSequenceId.eventLog.value): Promise<ProjectionQueryResult> {
|
|
238
|
+
const result = await this._connection.projections.preview({
|
|
239
|
+
EventStore: this._eventStore,
|
|
240
|
+
Namespace: this._namespace,
|
|
241
|
+
EventSequenceId: eventSequenceId,
|
|
242
|
+
Declaration: declaration
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (result.Value1) {
|
|
246
|
+
throw new UnableToQueryProjection(result.Value1.Errors.map(error => error.Message));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
readModelEntries: result.Value0?.ReadModelEntries ?? []
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private toProjectionIdValue(projectionId: ProjectionId | string): string {
|
|
255
|
+
return typeof projectionId === 'string' ? projectionId : projectionId.value;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Resolves the {@link ProjectionId} of the projection that maintains a specific read model type,
|
|
260
|
+
* from what has already been discovered.
|
|
261
|
+
* @param readModelType - The read model type to resolve for.
|
|
262
|
+
* @returns The resolved {@link ProjectionId}.
|
|
263
|
+
*/
|
|
264
|
+
private resolveProjectionIdForModel(readModelType: Constructor): ProjectionId {
|
|
265
|
+
for (const [id, type] of this._modelBound) {
|
|
266
|
+
if (type === readModelType) {
|
|
267
|
+
return new ProjectionId(id);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const [id, type] of this._declarative) {
|
|
272
|
+
const metadata = getProjectionMetadata(type);
|
|
273
|
+
if (metadata?.readModelType === readModelType) {
|
|
274
|
+
return new ProjectionId(id);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
throw new Error(
|
|
279
|
+
`No projection found for read model '${readModelType.name}'. Make sure discover() has run and the ` +
|
|
280
|
+
'read model is model-bound, or its declarative projection() decorator specifies an explicit readModelType.');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Resolves the event sequence identifier a discovered projection reads from, without requiring
|
|
285
|
+
* its full definition to have been built.
|
|
286
|
+
* @param projectionId - The projection identifier to resolve for.
|
|
287
|
+
* @returns The resolved event sequence identifier.
|
|
288
|
+
*/
|
|
289
|
+
private resolveEventSequenceIdFor(projectionId: string): string {
|
|
290
|
+
const modelBoundType = this._modelBound.get(projectionId);
|
|
291
|
+
if (modelBoundType) {
|
|
292
|
+
return getEventSequenceMetadata(modelBoundType) ?? EventSequenceId.eventLog.value;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const declarativeType = this._declarative.get(projectionId);
|
|
296
|
+
if (declarativeType) {
|
|
297
|
+
return getProjectionMetadata(declarativeType)?.eventSequenceId ?? EventSequenceId.eventLog.value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return EventSequenceId.eventLog.value;
|
|
301
|
+
}
|
|
302
|
+
|
|
140
303
|
private async registerWithRetry(projection: unknown, identifier: string, maxAttempts = 5): Promise<void> {
|
|
141
304
|
let delay = 2000;
|
|
142
305
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -344,14 +507,25 @@ export class Projections implements IProjections {
|
|
|
344
507
|
childrenByProperty[property] = buildChildrenEntry(type, property, childrenFromList);
|
|
345
508
|
}
|
|
346
509
|
|
|
347
|
-
|
|
510
|
+
const propertyIsNested = isNested(prototype, property);
|
|
511
|
+
if (propertyIsNested) {
|
|
348
512
|
nestedByProperty[property] = buildNestedEntry(type, property);
|
|
349
513
|
}
|
|
514
|
+
|
|
515
|
+
// A scalar (non-nested, non-children-collection) root property clears back to no value
|
|
516
|
+
// every time the given event is observed. A nested single-object property's clearWith is
|
|
517
|
+
// handled by buildNestedEntry instead, which clears the whole nested object.
|
|
518
|
+
if (childrenFromList.length === 0 && !propertyIsNested) {
|
|
519
|
+
for (const clearWith of getClearWithPropertyMetadata(prototype, property)) {
|
|
520
|
+
const entry = ensureFromEntry(fromByEventType, clearWith.eventType);
|
|
521
|
+
entry.Value.Properties[property] = '$null';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
350
524
|
}
|
|
351
525
|
|
|
352
526
|
const allProperties: Record<string, string> = {};
|
|
353
527
|
for (const property of properties) {
|
|
354
|
-
const fromEvery = getFromEveryMetadata(prototype, property);
|
|
528
|
+
const fromEvery = getFromEveryMetadata(prototype, property) ?? getFromAllMetadata(prototype, property);
|
|
355
529
|
if (fromEvery) {
|
|
356
530
|
allProperties[property] = fromEvery.contextProperty
|
|
357
531
|
? fromEvery.contextProperty
|
|
@@ -379,7 +553,8 @@ export class Projections implements IProjections {
|
|
|
379
553
|
RemovedWithJoin: Array.from(removedWithJoinByEventType.values()),
|
|
380
554
|
LastUpdated: { Value: '' },
|
|
381
555
|
Tags: [],
|
|
382
|
-
AutoMap: AutoMap.Enabled,
|
|
556
|
+
AutoMap: isNoAutoMap(type) ? AutoMap.Disabled : AutoMap.Enabled,
|
|
557
|
+
NoAutoMapProperties: properties.filter(property => isPropertyNoAutoMap(prototype, property)),
|
|
383
558
|
Nested: nestedByProperty
|
|
384
559
|
};
|
|
385
560
|
definition.LastUpdated = { Value: this.computeStableLastUpdated(definition) };
|
|
@@ -392,7 +567,7 @@ export class Projections implements IProjections {
|
|
|
392
567
|
if (readModelMetadata && hasFromEventMetadata(type)) {
|
|
393
568
|
return {
|
|
394
569
|
id: new ProjectionId(readModelMetadata.id.value),
|
|
395
|
-
eventSequenceId:
|
|
570
|
+
eventSequenceId: getEventSequenceMetadata(type),
|
|
396
571
|
readModelIdentifier: readModelMetadata.id.value
|
|
397
572
|
};
|
|
398
573
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* Error thrown when a projection query fails because the Projection Declaration Language
|
|
6
|
+
* declaration contains errors.
|
|
7
|
+
*/
|
|
8
|
+
export class UnableToQueryProjection extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new {@link UnableToQueryProjection}.
|
|
11
|
+
* @param errors - The collection of error messages describing why the query failed.
|
|
12
|
+
*/
|
|
13
|
+
constructor(errors: ReadonlyArray<string>) {
|
|
14
|
+
super(`Unable to query projection. Errors:\n${errors.join('\n')}`);
|
|
15
|
+
this.name = 'UnableToQueryProjection';
|
|
16
|
+
}
|
|
17
|
+
}
|
package/projections/index.ts
CHANGED
|
@@ -4,5 +4,8 @@
|
|
|
4
4
|
export { ProjectionId } from './ProjectionId';
|
|
5
5
|
export type { IProjections } from './IProjections';
|
|
6
6
|
export { Projections } from './Projections';
|
|
7
|
+
export type { ProjectionState } from './ProjectionState';
|
|
8
|
+
export type { ProjectionQueryResult } from './ProjectionQueryResult';
|
|
9
|
+
export { UnableToQueryProjection } from './UnableToQueryProjection';
|
|
7
10
|
export * from './declarative';
|
|
8
11
|
export * from './modelBound';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import { EventSequenceId } from '../../eventSequences/EventSequenceId';
|
|
6
|
+
|
|
7
|
+
const METADATA_KEY = 'chronicle:projection:eventSequence';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Class decorator that overrides the event sequence a model-bound projection reads from.
|
|
11
|
+
* When applied, auto-inbox routing is suppressed - the explicit value is always honored.
|
|
12
|
+
* @param sequence - The event sequence identifier to read from.
|
|
13
|
+
* @returns A class decorator.
|
|
14
|
+
*/
|
|
15
|
+
export function eventSequence(sequence: string): ClassDecorator {
|
|
16
|
+
return (target: object) => {
|
|
17
|
+
Reflect.defineMetadata(METADATA_KEY, sequence, target);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convenience class decorator that pins a model-bound projection to the default event log
|
|
23
|
+
* sequence. Equivalent to `eventSequence(EventSequenceId.eventLog.value)`, but more expressive
|
|
24
|
+
* about the intent to read from the local event log rather than an inbox or another sequence.
|
|
25
|
+
* @param target - The class constructor.
|
|
26
|
+
*/
|
|
27
|
+
export function eventLog(target: Function): void {
|
|
28
|
+
Reflect.defineMetadata(METADATA_KEY, EventSequenceId.eventLog.value, target);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves the explicit event sequence identifier stored on a class, if any.
|
|
33
|
+
* @param target - The class constructor.
|
|
34
|
+
* @returns The event sequence identifier, or undefined when the class has no explicit override.
|
|
35
|
+
*/
|
|
36
|
+
export function getEventSequenceMetadata(target: Function): string | undefined {
|
|
37
|
+
return Reflect.getMetadata(METADATA_KEY, target);
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 'reflect-metadata';
|
|
5
|
+
import { TypeIntrospector } from '../../types';
|
|
6
|
+
|
|
7
|
+
/** Metadata stored by the fromAll property decorator. */
|
|
8
|
+
export interface FromAllMetadata {
|
|
9
|
+
/** The event property name to read the value from. */
|
|
10
|
+
readonly property?: string;
|
|
11
|
+
/** The event context property name to read the value from. */
|
|
12
|
+
readonly contextProperty?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const METADATA_KEY = 'chronicle:projection:fromAll';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Property decorator that sets the decorated read model property from a property present on every
|
|
19
|
+
* event type the projection is built from - the convention-based counterpart to declaring the same
|
|
20
|
+
* mapping on every individual `fromEvent`. Equivalent to {@link fromEvery} for model-bound
|
|
21
|
+
* projections; use whichever name reads better for the property being mapped.
|
|
22
|
+
* @param property - Optional event property name. If not specified, uses the model property name.
|
|
23
|
+
* @param contextProperty - Optional event context property name.
|
|
24
|
+
* @returns A property decorator.
|
|
25
|
+
*/
|
|
26
|
+
export function fromAll(property?: string, contextProperty?: string): PropertyDecorator {
|
|
27
|
+
return (target: object, propertyKey: string | symbol) => {
|
|
28
|
+
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
|
|
29
|
+
const metadata: FromAllMetadata = { property, contextProperty };
|
|
30
|
+
Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves fromAll metadata stored on the given property.
|
|
36
|
+
* @param target - The class prototype.
|
|
37
|
+
* @param propertyKey - The property name.
|
|
38
|
+
* @returns The fromAll metadata, or undefined if not decorated.
|
|
39
|
+
*/
|
|
40
|
+
export function getFromAllMetadata(target: object, propertyKey: string): FromAllMetadata | undefined {
|
|
41
|
+
return Reflect.getMetadata(METADATA_KEY, target, propertyKey);
|
|
42
|
+
}
|
|
@@ -35,3 +35,7 @@ export { setValue, getSetValueMetadata } from './setValue';
|
|
|
35
35
|
export type { SetValueMetadata } from './setValue';
|
|
36
36
|
export { fromEvery, getFromEveryMetadata } from './fromEvery';
|
|
37
37
|
export type { FromEveryMetadata } from './fromEvery';
|
|
38
|
+
export { fromAll, getFromAllMetadata } from './fromAll';
|
|
39
|
+
export type { FromAllMetadata } from './fromAll';
|
|
40
|
+
export { noAutoMap, isNoAutoMap, isPropertyNoAutoMap } from './noAutoMap';
|
|
41
|
+
export { eventSequence, eventLog, getEventSequenceMetadata } from './eventSequence';
|