@cratis/chronicle 6.6.0 → 6.7.1
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/README.md +3 -1
- package/artifacts/DefaultClientArtifactsProvider.ts +12 -1
- package/artifacts/validateArtifactSchemas.spec.ts +3 -3
- package/compliance/pii.ts +2 -2
- package/compliance/subject.ts +1 -1
- package/confidentiality/encrypted.ts +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js +14 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
- package/dist/compliance/pii.d.ts +2 -2
- package/dist/compliance/pii.js +2 -2
- package/dist/compliance/subject.d.ts +1 -1
- package/dist/compliance/subject.js +1 -1
- package/dist/confidentiality/encrypted.d.ts +1 -1
- package/dist/confidentiality/encrypted.js +1 -1
- package/dist/events/eventTypeDecorator.d.ts +6 -1
- package/dist/events/eventTypeDecorator.d.ts.map +1 -1
- package/dist/events/eventTypeDecorator.js.map +1 -1
- package/dist/projections/Projections.d.ts.map +1 -1
- package/dist/projections/Projections.js +32 -15
- package/dist/projections/Projections.js.map +1 -1
- package/dist/projections/modelBound/fromEvent.d.ts.map +1 -1
- package/dist/projections/modelBound/fromEvent.js +2 -0
- package/dist/projections/modelBound/fromEvent.js.map +1 -1
- package/dist/readModels/ReadModels.d.ts +1 -0
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +23 -17
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/assertUniqueReadModelIds.d.ts +4 -0
- package/dist/readModels/assertUniqueReadModelIds.d.ts.map +1 -0
- package/dist/readModels/assertUniqueReadModelIds.js +16 -0
- package/dist/readModels/assertUniqueReadModelIds.js.map +1 -0
- package/dist/readModels/readModel.d.ts +5 -0
- package/dist/readModels/readModel.d.ts.map +1 -1
- package/dist/readModels/readModel.js +14 -0
- package/dist/readModels/readModel.js.map +1 -1
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +12 -2
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TypeDiscoverer.d.ts +2 -0
- package/dist/types/TypeDiscoverer.d.ts.map +1 -1
- package/dist/types/TypeDiscoverer.js +39 -2
- package/dist/types/TypeDiscoverer.js.map +1 -1
- package/events/constructorRoundTrip.legacy.spec.ts +47 -0
- package/events/constructorRoundTrip.standard.spec.ts +47 -0
- package/events/eventTypeDecorator.ts +6 -1
- package/package.json +1 -1
- package/projections/Projections.childrenAndNested.spec.ts +0 -2
- package/projections/Projections.modelBoundCompleteness.spec.ts +0 -7
- package/projections/Projections.operationalSurface.spec.ts +0 -4
- package/projections/Projections.spec.ts +0 -2
- package/projections/Projections.ts +32 -17
- package/projections/Projections.variants.spec.ts +0 -5
- package/projections/modelBound/fromEvent.ts +3 -0
- package/readModels/MaterializedReadModels.spec.ts +0 -5
- package/readModels/ReadModels.spec.ts +0 -9
- package/readModels/ReadModels.ts +24 -18
- package/readModels/assertUniqueReadModelIds.ts +18 -0
- package/readModels/discovery.spec.ts +113 -0
- package/readModels/readModel.ts +14 -0
- package/readModels/standardFallbacks.standard.spec.ts +1 -1
- package/reducers/Reducers.spec.ts +18 -0
- package/reducers/Reducers.ts +11 -2
- package/schemas/JsonSchemaGenerator.confidentiality.spec.ts +5 -14
- package/schemas/JsonSchemaGenerator.spec.ts +3 -13
- package/schemas/decorators.legacy.spec.ts +2 -3
- package/schemas/decorators.standard.spec.ts +12 -10
- package/types/TypeDiscoverer.ts +38 -2
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { DecoratorType } from './DecoratorType.js';
|
|
5
|
+
import { TypeIntrospector } from './TypeIntrospector.js';
|
|
6
|
+
import { hasPropertyMetadata } from './propertyDecoratorMetadata.js';
|
|
7
|
+
const modelBoundPropertyKeys = [
|
|
8
|
+
'setFrom', 'setFromContext', 'setValue', 'addFrom', 'subtractFrom',
|
|
9
|
+
'increment', 'decrement', 'count', 'childrenFrom', 'join', 'fromEvery', 'fromAll'
|
|
10
|
+
].map(name => `chronicle:projection:${name}`);
|
|
11
|
+
/** Identifies models whose event mappings live on their properties rather than on @fromEvent. */
|
|
12
|
+
export function hasModelBoundProperties(type) {
|
|
13
|
+
return TypeIntrospector.getTrackedProperties(type).some(property => modelBoundPropertyKeys.some(key => hasPropertyMetadata(key, type.prototype, property)));
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Encapsulates discovery and registry operations for decorator-based artifacts.
|
|
6
17
|
*/
|
|
@@ -27,7 +38,16 @@ export class TypeDiscoverer {
|
|
|
27
38
|
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
28
39
|
const files = await this._glob(patterns);
|
|
29
40
|
for (const file of files) {
|
|
30
|
-
await this._importFile(path.resolve(file));
|
|
41
|
+
const module = await this._importFile(path.resolve(file));
|
|
42
|
+
if (module && typeof module === 'object') {
|
|
43
|
+
for (const type of Object.values(module)) {
|
|
44
|
+
if (typeof type !== 'function' || !type.prototype)
|
|
45
|
+
continue;
|
|
46
|
+
if (hasModelBoundProperties(type)) {
|
|
47
|
+
this.register(DecoratorType.ReadModel, type);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
31
51
|
}
|
|
32
52
|
}
|
|
33
53
|
/**
|
|
@@ -39,7 +59,24 @@ export class TypeDiscoverer {
|
|
|
39
59
|
register(decoratorType, type, name) {
|
|
40
60
|
const discoveredName = name ?? type.name;
|
|
41
61
|
const typesForDecorator = TypeDiscoverer._registeredTypes.get(decoratorType) ?? new Map();
|
|
42
|
-
|
|
62
|
+
if (decoratorType === DecoratorType.ReadModel) {
|
|
63
|
+
const previous = Array.from(typesForDecorator).find(([, registered]) => registered === type);
|
|
64
|
+
if (previous && previous[0] !== discoveredName) {
|
|
65
|
+
// An explicit legacy registration overrides the type-name alias registered by an observer.
|
|
66
|
+
if (previous[0] !== type.name)
|
|
67
|
+
return;
|
|
68
|
+
typesForDecorator.delete(previous[0]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let key = discoveredName;
|
|
72
|
+
if (decoratorType === DecoratorType.ReadModel && typesForDecorator.has(key) && typesForDecorator.get(key) !== type &&
|
|
73
|
+
!(type.prototype instanceof typesForDecorator.get(key))) {
|
|
74
|
+
let suffix = 2;
|
|
75
|
+
while (typesForDecorator.has(`${discoveredName}#${suffix}`))
|
|
76
|
+
suffix++;
|
|
77
|
+
key = `${discoveredName}#${suffix}`;
|
|
78
|
+
}
|
|
79
|
+
typesForDecorator.set(key, type);
|
|
43
80
|
TypeDiscoverer._registeredTypes.set(decoratorType, typesForDecorator);
|
|
44
81
|
}
|
|
45
82
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TypeDiscoverer.js","sourceRoot":"","sources":["../../types/TypeDiscoverer.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"TypeDiscoverer.js","sourceRoot":"","sources":["../../types/TypeDiscoverer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { ConceptAs, field, JsonSerializer } from '@cratis/fundamentals';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import type { ChronicleConnection } from '../connection/index.js';
|
|
7
|
+
import { EventSequence } from '../eventSequences/EventSequence.js';
|
|
8
|
+
import { EventSequenceId } from '../eventSequences/EventSequenceId.js';
|
|
9
|
+
import type { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
|
|
10
|
+
import { eventType, getEventTypeMetadata } from './eventTypeDecorator.js';
|
|
11
|
+
|
|
12
|
+
class TaskTitle extends ConceptAs<string> {
|
|
13
|
+
static readonly valueType = String;
|
|
14
|
+
}
|
|
15
|
+
class TaskDetails {
|
|
16
|
+
@field(String) description!: string;
|
|
17
|
+
constructor(description: string) { this.description = description; }
|
|
18
|
+
}
|
|
19
|
+
@eventType('constructor-legacy')
|
|
20
|
+
class TaskCreated {
|
|
21
|
+
@field(TaskTitle) title: TaskTitle;
|
|
22
|
+
@field(TaskDetails) details: TaskDetails;
|
|
23
|
+
constructor(title: TaskTitle, details: TaskDetails) {
|
|
24
|
+
this.title = title;
|
|
25
|
+
this.details = details;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('when appending a legacy-decorator event with required constructor arguments', () => {
|
|
30
|
+
it('should serialize, generate schema and hydrate the event with its concept and nested object', async () => {
|
|
31
|
+
const append = vi.fn().mockResolvedValue({ Response: { SequenceNumber: 0n, ConstraintViolations: [], Errors: [] } });
|
|
32
|
+
const sequence = new EventSequence(EventSequenceId.eventLog, 'store', 'Default',
|
|
33
|
+
{ eventSequences: { append } } as unknown as ChronicleConnection, {} as IUnitOfWorkManager);
|
|
34
|
+
const event = new TaskCreated(new TaskTitle('Write specs'), new TaskDetails('With constructors'));
|
|
35
|
+
await sequence.append('task-1', event);
|
|
36
|
+
const content = append.mock.calls[0][0].Content as string;
|
|
37
|
+
expect(JSON.parse(content)).toEqual({ title: 'Write specs', details: { description: 'With constructors' } });
|
|
38
|
+
expect(getEventTypeMetadata(TaskCreated)!.schema.properties?.title.type).toBe('string');
|
|
39
|
+
expect(getEventTypeMetadata(TaskCreated)!.schema.properties?.details.properties?.description.type).toBe('string');
|
|
40
|
+
const restored = JsonSerializer.deserialize(TaskCreated, content);
|
|
41
|
+
expect(restored).toBeInstanceOf(TaskCreated);
|
|
42
|
+
expect(restored.title).toBeInstanceOf(TaskTitle);
|
|
43
|
+
expect(restored.title.value).toBe('Write specs');
|
|
44
|
+
expect(restored.details).toBeInstanceOf(TaskDetails);
|
|
45
|
+
expect(restored.details.description).toBe('With constructors');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { ConceptAs, field, JsonSerializer } from '@cratis/fundamentals';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import type { ChronicleConnection } from '../connection/index.js';
|
|
7
|
+
import { EventSequence } from '../eventSequences/EventSequence.js';
|
|
8
|
+
import { EventSequenceId } from '../eventSequences/EventSequenceId.js';
|
|
9
|
+
import type { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
|
|
10
|
+
import { eventType, getEventTypeMetadata } from './eventTypeDecorator.js';
|
|
11
|
+
|
|
12
|
+
class TaskTitle extends ConceptAs<string> {
|
|
13
|
+
static readonly valueType = String;
|
|
14
|
+
}
|
|
15
|
+
class TaskDetails {
|
|
16
|
+
@field(String) description!: string;
|
|
17
|
+
constructor(description: string) { this.description = description; }
|
|
18
|
+
}
|
|
19
|
+
@eventType('constructor-standard')
|
|
20
|
+
class TaskCreated {
|
|
21
|
+
@field(TaskTitle) title: TaskTitle;
|
|
22
|
+
@field(TaskDetails) details: TaskDetails;
|
|
23
|
+
constructor(title: TaskTitle, details: TaskDetails) {
|
|
24
|
+
this.title = title;
|
|
25
|
+
this.details = details;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('when appending a standard-decorator event with required constructor arguments', () => {
|
|
30
|
+
it('should serialize, generate schema and hydrate the event with its concept and nested object', async () => {
|
|
31
|
+
const append = vi.fn().mockResolvedValue({ Response: { SequenceNumber: 0n, ConstraintViolations: [], Errors: [] } });
|
|
32
|
+
const sequence = new EventSequence(EventSequenceId.eventLog, 'store', 'Default',
|
|
33
|
+
{ eventSequences: { append } } as unknown as ChronicleConnection, {} as IUnitOfWorkManager);
|
|
34
|
+
const event = new TaskCreated(new TaskTitle('Write specs'), new TaskDetails('With constructors'));
|
|
35
|
+
await sequence.append('task-1', event);
|
|
36
|
+
const content = append.mock.calls[0][0].Content as string;
|
|
37
|
+
expect(JSON.parse(content)).toEqual({ title: 'Write specs', details: { description: 'With constructors' } });
|
|
38
|
+
expect(getEventTypeMetadata(TaskCreated)!.schema.properties?.title.type).toBe('string');
|
|
39
|
+
expect(getEventTypeMetadata(TaskCreated)!.schema.properties?.details.properties?.description.type).toBe('string');
|
|
40
|
+
const restored = JsonSerializer.deserialize(TaskCreated, content);
|
|
41
|
+
expect(restored).toBeInstanceOf(TaskCreated);
|
|
42
|
+
expect(restored.title).toBeInstanceOf(TaskTitle);
|
|
43
|
+
expect(restored.title.value).toBe('Write specs');
|
|
44
|
+
expect(restored.details).toBeInstanceOf(TaskDetails);
|
|
45
|
+
expect(restored.details.description).toBe('With constructors');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -49,7 +49,12 @@ export interface EventTypeMetadata {
|
|
|
49
49
|
* ```typescript
|
|
50
50
|
* @eventType()
|
|
51
51
|
* class EmployeeHired {
|
|
52
|
-
*
|
|
52
|
+
* @field(String) firstName: string;
|
|
53
|
+
* @field(String) lastName: string;
|
|
54
|
+
* constructor(firstName: string, lastName: string) {
|
|
55
|
+
* this.firstName = firstName;
|
|
56
|
+
* this.lastName = lastName;
|
|
57
|
+
* }
|
|
53
58
|
* }
|
|
54
59
|
* ```
|
|
55
60
|
*/
|
package/package.json
CHANGED
|
@@ -7,7 +7,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
7
7
|
import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
8
8
|
import { ChronicleConnection } from '../connection/index.js';
|
|
9
9
|
import { eventType } from '../events/eventTypeDecorator.js';
|
|
10
|
-
import { readModel } from '../readModels/readModel.js';
|
|
11
10
|
import { childrenFrom } from './modelBound/childrenFrom.js';
|
|
12
11
|
import { clearWith } from './modelBound/clearWith.js';
|
|
13
12
|
import { fromEvent } from './modelBound/fromEvent.js';
|
|
@@ -86,7 +85,6 @@ field(OrderNote)(Order.prototype, 'note');
|
|
|
86
85
|
clearWith(NoteCleared)(Order.prototype, 'note');
|
|
87
86
|
childrenFrom(TagAdded)(Order.prototype, 'tags');
|
|
88
87
|
fromEvent(OrderCreated)(Order);
|
|
89
|
-
readModel()(Order);
|
|
90
88
|
|
|
91
89
|
interface FromRecord {
|
|
92
90
|
Key: { Id: string };
|
|
@@ -7,7 +7,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
7
7
|
import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
8
8
|
import { ChronicleConnection } from '../connection/index.js';
|
|
9
9
|
import { eventType } from '../events/eventTypeDecorator.js';
|
|
10
|
-
import { readModel } from '../readModels/readModel.js';
|
|
11
10
|
import { clearWith } from './modelBound/clearWith.js';
|
|
12
11
|
import { eventLog, eventSequence } from './modelBound/eventSequence.js';
|
|
13
12
|
import { fromAll } from './modelBound/fromAll.js';
|
|
@@ -40,7 +39,6 @@ setFrom(MbWorkArrangementSet, 'location')(MbAssignmentSummary.prototype, 'locati
|
|
|
40
39
|
noAutoMap(MbAssignmentSummary.prototype, 'location');
|
|
41
40
|
setFrom(MbCandidateSubmitted, 'name')(MbAssignmentSummary.prototype, 'candidateName');
|
|
42
41
|
fromEvent(MbWorkArrangementSet)(MbAssignmentSummary);
|
|
43
|
-
readModel()(MbAssignmentSummary);
|
|
44
42
|
|
|
45
43
|
class MbFullyExcluded {
|
|
46
44
|
id!: string;
|
|
@@ -49,7 +47,6 @@ class MbFullyExcluded {
|
|
|
49
47
|
setFrom(MbWorkArrangementSet, 'location')(MbFullyExcluded.prototype, 'location');
|
|
50
48
|
fromEvent(MbWorkArrangementSet)(MbFullyExcluded);
|
|
51
49
|
noAutoMap(MbFullyExcluded);
|
|
52
|
-
readModel()(MbFullyExcluded);
|
|
53
50
|
|
|
54
51
|
class MbProductRenamed {
|
|
55
52
|
name!: string;
|
|
@@ -72,7 +69,6 @@ class MbProductVersion {
|
|
|
72
69
|
fromAll('version')(MbProductVersion.prototype, 'version');
|
|
73
70
|
fromEvent(MbProductRenamed)(MbProductVersion);
|
|
74
71
|
fromEvent(MbProductPriceChanged)(MbProductVersion);
|
|
75
|
-
readModel()(MbProductVersion);
|
|
76
72
|
|
|
77
73
|
class MbOrderPlaced {
|
|
78
74
|
amount!: number;
|
|
@@ -86,7 +82,6 @@ class MbOrderSummaryWithCustomSequence {
|
|
|
86
82
|
setFrom(MbOrderPlaced, 'amount')(MbOrderSummaryWithCustomSequence.prototype, 'totalAmount');
|
|
87
83
|
fromEvent(MbOrderPlaced)(MbOrderSummaryWithCustomSequence);
|
|
88
84
|
eventSequence('custom-sequence')(MbOrderSummaryWithCustomSequence);
|
|
89
|
-
readModel()(MbOrderSummaryWithCustomSequence);
|
|
90
85
|
|
|
91
86
|
class MbLocalEvent {
|
|
92
87
|
data!: string;
|
|
@@ -100,7 +95,6 @@ class MbLocalSnapshot {
|
|
|
100
95
|
setFrom(MbLocalEvent, 'data')(MbLocalSnapshot.prototype, 'data');
|
|
101
96
|
fromEvent(MbLocalEvent)(MbLocalSnapshot);
|
|
102
97
|
eventLog(MbLocalSnapshot);
|
|
103
|
-
readModel()(MbLocalSnapshot);
|
|
104
98
|
|
|
105
99
|
class MbProjectNoted {
|
|
106
100
|
note!: string;
|
|
@@ -117,7 +111,6 @@ class MbProjectNotes {
|
|
|
117
111
|
setFrom(MbProjectNoted, 'note')(MbProjectNotes.prototype, 'note');
|
|
118
112
|
clearWith(MbProjectNoteCleared)(MbProjectNotes.prototype, 'note');
|
|
119
113
|
fromEvent(MbProjectNoted)(MbProjectNotes);
|
|
120
|
-
readModel()(MbProjectNotes);
|
|
121
114
|
|
|
122
115
|
interface BuiltFromEntry {
|
|
123
116
|
Key: { Id: string };
|
|
@@ -7,7 +7,6 @@ import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
|
7
7
|
import { ChronicleConnection } from '../connection/index.js';
|
|
8
8
|
import { ObserverRunningState } from '../observation/ObserverRunningState.js';
|
|
9
9
|
import { eventType } from '../events/eventTypeDecorator.js';
|
|
10
|
-
import { readModel } from '../readModels/readModel.js';
|
|
11
10
|
import { eventSequence } from './modelBound/eventSequence.js';
|
|
12
11
|
import { fromEvent } from './modelBound/fromEvent.js';
|
|
13
12
|
import { projection } from './declarative/projection.js';
|
|
@@ -30,17 +29,14 @@ class OpSummary {
|
|
|
30
29
|
}
|
|
31
30
|
fromEvent(OpStateChanged)(OpSummary);
|
|
32
31
|
eventSequence('custom-op-sequence')(OpSummary);
|
|
33
|
-
readModel()(OpSummary);
|
|
34
32
|
|
|
35
33
|
class UndiscoveredSummary {
|
|
36
34
|
id!: string;
|
|
37
35
|
}
|
|
38
|
-
readModel()(UndiscoveredSummary);
|
|
39
36
|
|
|
40
37
|
class DeclarativeReadModel {
|
|
41
38
|
id!: string;
|
|
42
39
|
}
|
|
43
|
-
readModel()(DeclarativeReadModel);
|
|
44
40
|
|
|
45
41
|
class DeclarativeSummaryProjection implements IProjectionFor<DeclarativeReadModel> {
|
|
46
42
|
// discover() never calls define() - only register() does - so a no-op body is sufficient here.
|
|
@@ -6,7 +6,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
6
6
|
import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
7
7
|
import { ChronicleConnection } from '../connection/index.js';
|
|
8
8
|
import { eventType } from '../events/eventTypeDecorator.js';
|
|
9
|
-
import { readModel } from '../readModels/readModel.js';
|
|
10
9
|
import { addFrom } from './modelBound/addFrom.js';
|
|
11
10
|
import { count } from './modelBound/count.js';
|
|
12
11
|
import { decrement } from './modelBound/decrement.js';
|
|
@@ -59,7 +58,6 @@ decrement(CounterDecremented)(Inventory.prototype, 'decrementedCount');
|
|
|
59
58
|
count(ThingHappened)(Inventory.prototype, 'thingsHappenedCount');
|
|
60
59
|
count(KeyedThingHappened, 'all-things')(Inventory.prototype, 'constantKeyedCount');
|
|
61
60
|
fromEvent(ItemAdded)(Inventory);
|
|
62
|
-
readModel()(Inventory);
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
63
|
* Builds a {@link Projections} instance wired to capture the registration payload sent
|
|
@@ -18,7 +18,11 @@ import { FailedPartition } from '../observation/FailedPartition.js';
|
|
|
18
18
|
import { FailedPartitions } from '../observation/FailedPartitions.js';
|
|
19
19
|
import { toObserverRunningState } from '../observation/toObserverRunningState.js';
|
|
20
20
|
import { getReadModelMetadata } from '../readModels/index.js';
|
|
21
|
+
import { getReadModelId } from '../readModels/readModel.js';
|
|
22
|
+
import { assertUniqueReadModelIds } from '../readModels/assertUniqueReadModelIds.js';
|
|
23
|
+
import { JsonSchemaGenerator } from '../schemas/index.js';
|
|
21
24
|
import { TypeIntrospector } from '../types/index.js';
|
|
25
|
+
import { hasModelBoundProperties } from '../types/TypeDiscoverer.js';
|
|
22
26
|
import { IProjections } from './IProjections.js';
|
|
23
27
|
import { getProjectionMetadata } from './declarative/projection.js';
|
|
24
28
|
import { ProjectionBuilderFor } from './declarative/ProjectionBuilderFor.js';
|
|
@@ -109,7 +113,7 @@ export class Projections implements IProjections {
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
for (const type of readModelTypes) {
|
|
112
|
-
if (!hasFromEventMetadata(type)) {
|
|
116
|
+
if (!hasFromEventMetadata(type) && !hasModelBoundProperties(type)) {
|
|
113
117
|
continue;
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -136,6 +140,10 @@ export class Projections implements IProjections {
|
|
|
136
140
|
|
|
137
141
|
this._logger.info('Registering projections', { declarativeCount: this._declarative.size, modelBoundCount: this._modelBound.size });
|
|
138
142
|
|
|
143
|
+
const inferred = this._clientArtifacts.projections
|
|
144
|
+
.map(type => getProjectionMetadata(type)?.readModelType)
|
|
145
|
+
.filter((type): type is Constructor => type !== undefined);
|
|
146
|
+
assertUniqueReadModelIds([...this._clientArtifacts.readModels, ...inferred]);
|
|
139
147
|
const builtProjections: BuiltProjection[] = [
|
|
140
148
|
...Array.from(this._declarative.values()).map(type => this.buildDeclarativeDefinition(type)),
|
|
141
149
|
...Array.from(this._modelBound.values()).map(type => this.buildModelBoundDefinition(type))
|
|
@@ -349,7 +357,14 @@ export class Projections implements IProjections {
|
|
|
349
357
|
|
|
350
358
|
for (const projection of projections) {
|
|
351
359
|
const readModelIdentifier = projection.ReadModel;
|
|
352
|
-
if (!readModelIdentifier
|
|
360
|
+
if (!readModelIdentifier) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const existing = byReadModel.get(readModelIdentifier);
|
|
364
|
+
if (existing) {
|
|
365
|
+
if (existing.ObserverIdentifier !== projection.Identifier) {
|
|
366
|
+
throw new Error(`Read model id '${readModelIdentifier}' has multiple projections.`);
|
|
367
|
+
}
|
|
353
368
|
continue;
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -380,14 +395,16 @@ export class Projections implements IProjections {
|
|
|
380
395
|
}
|
|
381
396
|
|
|
382
397
|
private getReadModelSchema(readModelIdentifier: string): string {
|
|
383
|
-
|
|
398
|
+
const types = [
|
|
399
|
+
...this._clientArtifacts.readModels,
|
|
400
|
+
...this._clientArtifacts.projections
|
|
401
|
+
.map(projectionType => getProjectionMetadata(projectionType)?.readModelType)
|
|
402
|
+
.filter((type): type is Constructor => type !== undefined)
|
|
403
|
+
];
|
|
404
|
+
for (const type of types) {
|
|
384
405
|
const metadata = getReadModelMetadata(type);
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (metadata.id.value === readModelIdentifier || type.name === readModelIdentifier) {
|
|
390
|
-
return JSON.stringify(metadata.schema);
|
|
406
|
+
if (getReadModelId(type) === readModelIdentifier) {
|
|
407
|
+
return JSON.stringify(metadata?.schema ?? JsonSchemaGenerator.generate(type));
|
|
391
408
|
}
|
|
392
409
|
}
|
|
393
410
|
|
|
@@ -409,8 +426,7 @@ export class Projections implements IProjections {
|
|
|
409
426
|
if (explicitReadModelIdentifier === type.name) {
|
|
410
427
|
// Use explicit readModelType from decorator if provided
|
|
411
428
|
if (metadata.readModelType) {
|
|
412
|
-
|
|
413
|
-
definition.ReadModel = rm?.id.value ?? (metadata.readModelType as Function).name;
|
|
429
|
+
definition.ReadModel = getReadModelId(metadata.readModelType);
|
|
414
430
|
} else {
|
|
415
431
|
const inferredReadModelIdentifier = this.inferReadModelIdentifier(builder.getMappedReadModelProperties());
|
|
416
432
|
if (inferredReadModelIdentifier) {
|
|
@@ -585,7 +601,7 @@ export class Projections implements IProjections {
|
|
|
585
601
|
}
|
|
586
602
|
}
|
|
587
603
|
|
|
588
|
-
const memberNames = new Set(getReadModelMetadata(type)?.members.keys() ??
|
|
604
|
+
const memberNames = new Set(getReadModelMetadata(type)?.members.keys() ?? TypeIntrospector.getMembers(type).keys());
|
|
589
605
|
const merged = mergeGlobalHandlers(type.name, memberNames, from, globalHandlers);
|
|
590
606
|
const reclassified = reclassify(type.name, merged, join, enteringEventTypes, variantMetadata.key);
|
|
591
607
|
from = reclassified.from;
|
|
@@ -651,13 +667,12 @@ export class Projections implements IProjections {
|
|
|
651
667
|
}
|
|
652
668
|
|
|
653
669
|
private resolveModelBoundMetadata(type: Constructor): ResolvedModelBoundMetadata | undefined {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (readModelMetadata && hasFromEventMetadata(type)) {
|
|
670
|
+
if (hasFromEventMetadata(type) || hasModelBoundProperties(type)) {
|
|
671
|
+
const identifier = getReadModelId(type);
|
|
657
672
|
return {
|
|
658
|
-
id: new ProjectionId(
|
|
673
|
+
id: new ProjectionId(identifier),
|
|
659
674
|
eventSequenceId: getEventSequenceMetadata(type),
|
|
660
|
-
readModelIdentifier:
|
|
675
|
+
readModelIdentifier: identifier
|
|
661
676
|
};
|
|
662
677
|
}
|
|
663
678
|
|
|
@@ -6,7 +6,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
6
6
|
import { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
7
7
|
import { ChronicleConnection } from '../connection/index.js';
|
|
8
8
|
import { eventType } from '../events/eventTypeDecorator.js';
|
|
9
|
-
import { readModel } from '../readModels/readModel.js';
|
|
10
9
|
import { entersOn } from './modelBound/entersOn.js';
|
|
11
10
|
import { fromEvent } from './modelBound/fromEvent.js';
|
|
12
11
|
import { globalFor } from './modelBound/globalFor.js';
|
|
@@ -50,7 +49,6 @@ setFrom(IssueCreated, 'title')(BacklogItem.prototype, 'title');
|
|
|
50
49
|
variantOf(WorkItem, 'id')(BacklogItem);
|
|
51
50
|
entersOn(IssueCreated)(BacklogItem);
|
|
52
51
|
fromEvent(IssueCreated)(BacklogItem);
|
|
53
|
-
readModel()(BacklogItem);
|
|
54
52
|
|
|
55
53
|
class PullRequestItem {
|
|
56
54
|
id!: string;
|
|
@@ -64,7 +62,6 @@ variantOf(WorkItem, 'id')(PullRequestItem);
|
|
|
64
62
|
entersOn(PullRequestCreated)(PullRequestItem);
|
|
65
63
|
fromEvent(PullRequestCreated)(PullRequestItem);
|
|
66
64
|
fromEvent(BuildCompleted)(PullRequestItem);
|
|
67
|
-
readModel()(PullRequestItem);
|
|
68
65
|
|
|
69
66
|
/** A shared handler with no entering event of its own - never registered as its own projection. */
|
|
70
67
|
class TitleHandler {
|
|
@@ -81,7 +78,6 @@ class UndeclaredVariant {
|
|
|
81
78
|
setFrom(IssueCreated, 'title')(UndeclaredVariant.prototype, 'title');
|
|
82
79
|
variantOf(WorkItem, 'id')(UndeclaredVariant);
|
|
83
80
|
fromEvent(IssueCreated)(UndeclaredVariant);
|
|
84
|
-
readModel()(UndeclaredVariant);
|
|
85
81
|
|
|
86
82
|
/** A separate identity, used to prove sibling cross-wiring stays scoped to one identity. */
|
|
87
83
|
class OtherIdentity {}
|
|
@@ -92,7 +88,6 @@ class OtherVariant {
|
|
|
92
88
|
variantOf(OtherIdentity, 'id')(OtherVariant);
|
|
93
89
|
entersOn(IssueCreated)(OtherVariant);
|
|
94
90
|
fromEvent(IssueCreated)(OtherVariant);
|
|
95
|
-
readModel()(OtherVariant);
|
|
96
91
|
|
|
97
92
|
interface CapturedDefinition {
|
|
98
93
|
Identifier: string;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { FromEventMetadata } from './FromEventMetadata.js';
|
|
6
6
|
import { FromEventOptions } from './FromEventOptions.js';
|
|
7
|
+
import { DecoratorType, TypeDiscoverer } from '../../types/index.js';
|
|
8
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
7
9
|
|
|
8
10
|
const METADATA_KEY = 'chronicle:projection:fromEvent';
|
|
9
11
|
|
|
@@ -18,6 +20,7 @@ export function fromEvent(eventType: Function, options?: FromEventOptions): Clas
|
|
|
18
20
|
const existing: FromEventMetadata[] = Reflect.getMetadata(METADATA_KEY, target) ?? [];
|
|
19
21
|
const metadata: FromEventMetadata = { eventType, ...options };
|
|
20
22
|
Reflect.defineMetadata(METADATA_KEY, [...existing, metadata], target);
|
|
23
|
+
TypeDiscoverer.default.register(DecoratorType.ReadModel, target as Constructor);
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -7,7 +7,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
7
7
|
import type { ChronicleConnection } from '../connection/index.js';
|
|
8
8
|
import { pii } from '../compliance/pii.js';
|
|
9
9
|
import { subject } from '../compliance/subject.js';
|
|
10
|
-
import { readModel } from './readModel.js';
|
|
11
10
|
import { MaterializedReadModels } from './MaterializedReadModels.js';
|
|
12
11
|
|
|
13
12
|
// Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
|
|
@@ -37,7 +36,6 @@ describe('MaterializedReadModels', () => {
|
|
|
37
36
|
field(String)(Employee.prototype, 'ssn');
|
|
38
37
|
pii()(Employee.prototype, 'ssn');
|
|
39
38
|
subject()(Employee.prototype, 'personId');
|
|
40
|
-
readModel('EmployeeWithSubjectMaterialized')(Employee);
|
|
41
39
|
|
|
42
40
|
it('should release using the decorated property as the subject', async () => {
|
|
43
41
|
const json = JSON.stringify({ id: 'employee-1', personId: 'person-42', ssn: '123-45-6789' });
|
|
@@ -57,7 +55,6 @@ describe('MaterializedReadModels', () => {
|
|
|
57
55
|
field(String)(Customer.prototype, 'id');
|
|
58
56
|
field(String)(Customer.prototype, 'ssn');
|
|
59
57
|
pii()(Customer.prototype, 'ssn');
|
|
60
|
-
readModel('CustomerWithIdOnlyMaterialized')(Customer);
|
|
61
58
|
|
|
62
59
|
it('should fall back to the id property as the subject, unchanged from today', async () => {
|
|
63
60
|
const json = JSON.stringify({ id: 'customer-7', ssn: '987-65-4321' });
|
|
@@ -75,7 +72,6 @@ describe('MaterializedReadModels', () => {
|
|
|
75
72
|
}
|
|
76
73
|
field(String)(Anonymous.prototype, 'ssn');
|
|
77
74
|
pii()(Anonymous.prototype, 'ssn');
|
|
78
|
-
readModel('AnonymousMaterialized')(Anonymous);
|
|
79
75
|
|
|
80
76
|
it('should fail closed without a subject', async () => {
|
|
81
77
|
const json = JSON.stringify({ ssn: '000-00-0000' });
|
|
@@ -98,7 +94,6 @@ describe('MaterializedReadModels', () => {
|
|
|
98
94
|
field(String)(PrivateCustomer.prototype, 'id');
|
|
99
95
|
field(String)(PrivateCustomer.prototype, 'ssn');
|
|
100
96
|
pii()(PrivateCustomer.prototype, 'ssn');
|
|
101
|
-
readModel('PrivateCustomer')(PrivateCustomer);
|
|
102
97
|
|
|
103
98
|
it('should reject instead of returning encrypted content', async () => {
|
|
104
99
|
const { readModels } = createMaterializedReadModels(
|
|
@@ -10,7 +10,6 @@ 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 { readModel } from './readModel.js';
|
|
14
13
|
import { ReadModels } from './ReadModels.js';
|
|
15
14
|
|
|
16
15
|
// Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
|
|
@@ -49,7 +48,6 @@ describe('ReadModels', () => {
|
|
|
49
48
|
describe('when no read model instance exists for a key', () => {
|
|
50
49
|
class MissingModel { id = ''; }
|
|
51
50
|
fromEvent(SomeEvent)(MissingModel);
|
|
52
|
-
readModel('MissingModel')(MissingModel);
|
|
53
51
|
|
|
54
52
|
it('should retain the legacy prototype-only instance for getInstanceById', async () => {
|
|
55
53
|
const { readModels, release } = createReadModels(MissingModel);
|
|
@@ -80,7 +78,6 @@ describe('ReadModels', () => {
|
|
|
80
78
|
class ExistingModel { id = ''; }
|
|
81
79
|
field(String)(ExistingModel.prototype, 'id');
|
|
82
80
|
fromEvent(SomeEvent)(ExistingModel);
|
|
83
|
-
readModel('ExistingModel')(ExistingModel);
|
|
84
81
|
|
|
85
82
|
it('should deserialize the model', async () => {
|
|
86
83
|
const { readModels, getInstanceByKey } = createReadModels(ExistingModel);
|
|
@@ -99,7 +96,6 @@ describe('ReadModels', () => {
|
|
|
99
96
|
field(String)(PrivateModel.prototype, 'id');
|
|
100
97
|
field(String)(PrivateModel.prototype, 'ssn');
|
|
101
98
|
pii()(PrivateModel.prototype, 'ssn');
|
|
102
|
-
readModel('PrivateInstanceModel')(PrivateModel);
|
|
103
99
|
class PrivateReducer {}
|
|
104
100
|
reducer('PrivateInstanceReducer', undefined, PrivateModel)(PrivateReducer);
|
|
105
101
|
|
|
@@ -124,7 +120,6 @@ describe('ReadModels', () => {
|
|
|
124
120
|
field(String)(PrivateModel.prototype, 'id');
|
|
125
121
|
field(String)(PrivateModel.prototype, 'ssn');
|
|
126
122
|
pii()(PrivateModel.prototype, 'ssn');
|
|
127
|
-
readModel('PrivateReducerModel')(PrivateModel);
|
|
128
123
|
class PrivateReducer {}
|
|
129
124
|
reducer('PrivateReducer', undefined, PrivateModel)(PrivateReducer);
|
|
130
125
|
|
|
@@ -150,7 +145,6 @@ describe('ReadModels', () => {
|
|
|
150
145
|
}
|
|
151
146
|
subject()(Employee.prototype, 'personId');
|
|
152
147
|
fromEvent(SomeEvent)(Employee);
|
|
153
|
-
readModel('EmployeeWithSubject')(Employee);
|
|
154
148
|
|
|
155
149
|
it('should release using the decorated property as the subject', async () => {
|
|
156
150
|
const { readModels, release } = createReadModels(Employee);
|
|
@@ -169,7 +163,6 @@ describe('ReadModels', () => {
|
|
|
169
163
|
id = '';
|
|
170
164
|
}
|
|
171
165
|
fromEvent(SomeEvent)(Customer);
|
|
172
|
-
readModel('CustomerWithIdOnly')(Customer);
|
|
173
166
|
|
|
174
167
|
it('should fall back to the id property as the subject, unchanged from today', async () => {
|
|
175
168
|
const { readModels, release } = createReadModels(Customer);
|
|
@@ -187,7 +180,6 @@ describe('ReadModels', () => {
|
|
|
187
180
|
name = '';
|
|
188
181
|
}
|
|
189
182
|
fromEvent(SomeEvent)(Anonymous);
|
|
190
|
-
readModel('AnonymousReadModel')(Anonymous);
|
|
191
183
|
|
|
192
184
|
it('should throw, same as today', async () => {
|
|
193
185
|
const { readModels } = createReadModels(Anonymous);
|
|
@@ -202,7 +194,6 @@ describe('ReadModels', () => {
|
|
|
202
194
|
id = '';
|
|
203
195
|
}
|
|
204
196
|
fromEvent(SomeEvent)(Customer);
|
|
205
|
-
readModel('CustomerForReleaseMany')(Customer);
|
|
206
197
|
|
|
207
198
|
it('should release each instance using its own resolved subject', async () => {
|
|
208
199
|
const { readModels, release } = createReadModels(Customer);
|