@cratis/chronicle 3.1.1 → 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/EventStore.ts +1 -1
- 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/dist/EventStore.js +1 -1
- 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/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/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/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/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/observation/ObserverRunningState.ts +9 -18
- package/observation/index.ts +1 -0
- package/observation/toObserverRunningState.ts +27 -0
- package/package.json +8 -8
- 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,224 @@
|
|
|
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 { AutoMap } from '@cratis/chronicle.contracts';
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { IClientArtifactsProvider } from '../artifacts';
|
|
8
|
+
import { ChronicleConnection } from '../connection';
|
|
9
|
+
import { eventType } from '../events/eventTypeDecorator';
|
|
10
|
+
import { readModel } from '../readModels/readModel';
|
|
11
|
+
import { clearWith } from './modelBound/clearWith';
|
|
12
|
+
import { eventLog, eventSequence } from './modelBound/eventSequence';
|
|
13
|
+
import { fromAll } from './modelBound/fromAll';
|
|
14
|
+
import { fromEvent } from './modelBound/fromEvent';
|
|
15
|
+
import { noAutoMap } from './modelBound/noAutoMap';
|
|
16
|
+
import { setFrom } from './modelBound/setFrom';
|
|
17
|
+
import { Projections } from './Projections';
|
|
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 MbWorkArrangementSet {
|
|
23
|
+
location!: string;
|
|
24
|
+
workMode!: number;
|
|
25
|
+
}
|
|
26
|
+
eventType()(MbWorkArrangementSet);
|
|
27
|
+
|
|
28
|
+
class MbCandidateSubmitted {
|
|
29
|
+
name!: string;
|
|
30
|
+
location!: string;
|
|
31
|
+
}
|
|
32
|
+
eventType()(MbCandidateSubmitted);
|
|
33
|
+
|
|
34
|
+
class MbAssignmentSummary {
|
|
35
|
+
id!: string;
|
|
36
|
+
location!: string;
|
|
37
|
+
candidateName!: string;
|
|
38
|
+
}
|
|
39
|
+
setFrom(MbWorkArrangementSet, 'location')(MbAssignmentSummary.prototype, 'location');
|
|
40
|
+
noAutoMap(MbAssignmentSummary.prototype, 'location');
|
|
41
|
+
setFrom(MbCandidateSubmitted, 'name')(MbAssignmentSummary.prototype, 'candidateName');
|
|
42
|
+
fromEvent(MbWorkArrangementSet)(MbAssignmentSummary);
|
|
43
|
+
readModel()(MbAssignmentSummary);
|
|
44
|
+
|
|
45
|
+
class MbFullyExcluded {
|
|
46
|
+
id!: string;
|
|
47
|
+
location!: string;
|
|
48
|
+
}
|
|
49
|
+
setFrom(MbWorkArrangementSet, 'location')(MbFullyExcluded.prototype, 'location');
|
|
50
|
+
fromEvent(MbWorkArrangementSet)(MbFullyExcluded);
|
|
51
|
+
noAutoMap(MbFullyExcluded);
|
|
52
|
+
readModel()(MbFullyExcluded);
|
|
53
|
+
|
|
54
|
+
class MbProductRenamed {
|
|
55
|
+
name!: string;
|
|
56
|
+
version!: number;
|
|
57
|
+
}
|
|
58
|
+
eventType()(MbProductRenamed);
|
|
59
|
+
|
|
60
|
+
class MbProductPriceChanged {
|
|
61
|
+
price!: number;
|
|
62
|
+
version!: number;
|
|
63
|
+
}
|
|
64
|
+
eventType()(MbProductPriceChanged);
|
|
65
|
+
|
|
66
|
+
class MbProductVersion {
|
|
67
|
+
id!: string;
|
|
68
|
+
name!: string;
|
|
69
|
+
price!: number;
|
|
70
|
+
version!: number;
|
|
71
|
+
}
|
|
72
|
+
fromAll('version')(MbProductVersion.prototype, 'version');
|
|
73
|
+
fromEvent(MbProductRenamed)(MbProductVersion);
|
|
74
|
+
fromEvent(MbProductPriceChanged)(MbProductVersion);
|
|
75
|
+
readModel()(MbProductVersion);
|
|
76
|
+
|
|
77
|
+
class MbOrderPlaced {
|
|
78
|
+
amount!: number;
|
|
79
|
+
}
|
|
80
|
+
eventType()(MbOrderPlaced);
|
|
81
|
+
|
|
82
|
+
class MbOrderSummaryWithCustomSequence {
|
|
83
|
+
id!: string;
|
|
84
|
+
totalAmount!: number;
|
|
85
|
+
}
|
|
86
|
+
setFrom(MbOrderPlaced, 'amount')(MbOrderSummaryWithCustomSequence.prototype, 'totalAmount');
|
|
87
|
+
fromEvent(MbOrderPlaced)(MbOrderSummaryWithCustomSequence);
|
|
88
|
+
eventSequence('custom-sequence')(MbOrderSummaryWithCustomSequence);
|
|
89
|
+
readModel()(MbOrderSummaryWithCustomSequence);
|
|
90
|
+
|
|
91
|
+
class MbLocalEvent {
|
|
92
|
+
data!: string;
|
|
93
|
+
}
|
|
94
|
+
eventType()(MbLocalEvent);
|
|
95
|
+
|
|
96
|
+
class MbLocalSnapshot {
|
|
97
|
+
id!: string;
|
|
98
|
+
data!: string;
|
|
99
|
+
}
|
|
100
|
+
setFrom(MbLocalEvent, 'data')(MbLocalSnapshot.prototype, 'data');
|
|
101
|
+
fromEvent(MbLocalEvent)(MbLocalSnapshot);
|
|
102
|
+
eventLog(MbLocalSnapshot);
|
|
103
|
+
readModel()(MbLocalSnapshot);
|
|
104
|
+
|
|
105
|
+
class MbProjectNoted {
|
|
106
|
+
note!: string;
|
|
107
|
+
}
|
|
108
|
+
eventType()(MbProjectNoted);
|
|
109
|
+
|
|
110
|
+
class MbProjectNoteCleared {}
|
|
111
|
+
eventType()(MbProjectNoteCleared);
|
|
112
|
+
|
|
113
|
+
class MbProjectNotes {
|
|
114
|
+
id!: string;
|
|
115
|
+
note!: string | undefined;
|
|
116
|
+
}
|
|
117
|
+
setFrom(MbProjectNoted, 'note')(MbProjectNotes.prototype, 'note');
|
|
118
|
+
clearWith(MbProjectNoteCleared)(MbProjectNotes.prototype, 'note');
|
|
119
|
+
fromEvent(MbProjectNoted)(MbProjectNotes);
|
|
120
|
+
readModel()(MbProjectNotes);
|
|
121
|
+
|
|
122
|
+
interface BuiltFromEntry {
|
|
123
|
+
Key: { Id: string };
|
|
124
|
+
Value: { Properties: Record<string, string>; Key: string; ParentKey: string };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface BuiltDefinition {
|
|
128
|
+
EventSequenceId: string;
|
|
129
|
+
AutoMap: AutoMap;
|
|
130
|
+
NoAutoMapProperties: string[];
|
|
131
|
+
All: { Properties: Record<string, string> };
|
|
132
|
+
From: BuiltFromEntry[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createProjections(readModels: (new (...args: unknown[]) => unknown)[]) {
|
|
136
|
+
const registerMock = vi.fn().mockResolvedValue(undefined);
|
|
137
|
+
const registerManyMock = vi.fn().mockResolvedValue(undefined);
|
|
138
|
+
const connection = {
|
|
139
|
+
readModels: { registerMany: registerManyMock },
|
|
140
|
+
projections: { register: registerMock }
|
|
141
|
+
} as unknown as ChronicleConnection;
|
|
142
|
+
|
|
143
|
+
const clientArtifacts: IClientArtifactsProvider = {
|
|
144
|
+
eventTypes: [],
|
|
145
|
+
readModels: readModels as unknown as IClientArtifactsProvider['readModels'],
|
|
146
|
+
reactors: [],
|
|
147
|
+
reducers: [],
|
|
148
|
+
seeders: [],
|
|
149
|
+
constraints: [],
|
|
150
|
+
projections: [],
|
|
151
|
+
webhooks: [],
|
|
152
|
+
eventTypeMigrations: []
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const projections = new Projections('test-store', 'test-namespace', connection, clientArtifacts, 'test-sink');
|
|
156
|
+
return { projections, registerMock };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function registerAndGetDefinition(readModelType: new (...args: unknown[]) => unknown): Promise<BuiltDefinition> {
|
|
160
|
+
const { projections, registerMock } = createProjections([readModelType]);
|
|
161
|
+
await projections.register();
|
|
162
|
+
return registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function findFromEntry(definition: BuiltDefinition, eventTypeId: string): BuiltFromEntry {
|
|
166
|
+
const entry = definition.From.find(candidate => candidate.Key.Id === eventTypeId);
|
|
167
|
+
if (!entry) {
|
|
168
|
+
throw new Error(`No From entry found for event type '${eventTypeId}'.`);
|
|
169
|
+
}
|
|
170
|
+
return entry;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
describe('Projections model-bound completeness', () => {
|
|
174
|
+
describe('when a property is excluded from AutoMap with noAutoMap', () => {
|
|
175
|
+
it('should keep AutoMap enabled at the root and list only the excluded property', async () => {
|
|
176
|
+
const definition = await registerAndGetDefinition(MbAssignmentSummary);
|
|
177
|
+
|
|
178
|
+
expect(definition.AutoMap).toBe(AutoMap.Enabled);
|
|
179
|
+
expect(definition.NoAutoMapProperties).toEqual(['location']);
|
|
180
|
+
|
|
181
|
+
const workArrangementSet = findFromEntry(definition, 'MbWorkArrangementSet');
|
|
182
|
+
expect(workArrangementSet.Value.Properties.location).toBe('location');
|
|
183
|
+
|
|
184
|
+
const candidateSubmitted = findFromEntry(definition, 'MbCandidateSubmitted');
|
|
185
|
+
expect(candidateSubmitted.Value.Properties.candidateName).toBe('name');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('when a whole model-bound projection is excluded from AutoMap with class-level noAutoMap', () => {
|
|
190
|
+
it('should disable AutoMap for the whole definition', async () => {
|
|
191
|
+
const definition = await registerAndGetDefinition(MbFullyExcluded);
|
|
192
|
+
expect(definition.AutoMap).toBe(AutoMap.Disabled);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('when a property uses fromAll to read from every event type', () => {
|
|
197
|
+
it('should include it in the All properties map', async () => {
|
|
198
|
+
const definition = await registerAndGetDefinition(MbProductVersion);
|
|
199
|
+
expect(definition.All.Properties.version).toBe('version');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('when a model-bound projection declares a custom event sequence', () => {
|
|
204
|
+
it('should use the declared event sequence instead of the event log', async () => {
|
|
205
|
+
const definition = await registerAndGetDefinition(MbOrderSummaryWithCustomSequence);
|
|
206
|
+
expect(definition.EventSequenceId).toBe('custom-sequence');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('when a model-bound projection is explicitly pinned to the event log', () => {
|
|
211
|
+
it('should use the event log sequence', async () => {
|
|
212
|
+
const definition = await registerAndGetDefinition(MbLocalSnapshot);
|
|
213
|
+
expect(definition.EventSequenceId).toBe('event-log');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('when a root scalar property is cleared with an event', () => {
|
|
218
|
+
it('should add a $null mapping for that property on the clearing event', async () => {
|
|
219
|
+
const definition = await registerAndGetDefinition(MbProjectNotes);
|
|
220
|
+
const clearedBy = findFromEntry(definition, 'MbProjectNoteCleared');
|
|
221
|
+
expect(clearedBy.Value.Properties.note).toBe('$null');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -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
|
|