@cratis/chronicle 6.11.0 → 6.12.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.
Files changed (33) hide show
  1. package/dist/projections/Projections.d.ts +1 -1
  2. package/dist/projections/Projections.d.ts.map +1 -1
  3. package/dist/projections/Projections.js +19 -27
  4. package/dist/projections/Projections.js.map +1 -1
  5. package/dist/readModels/ReadModels.d.ts.map +1 -1
  6. package/dist/readModels/ReadModels.js +12 -22
  7. package/dist/readModels/ReadModels.js.map +1 -1
  8. package/dist/readModels/buildReadModelDefinition.d.ts +30 -0
  9. package/dist/readModels/buildReadModelDefinition.d.ts.map +1 -0
  10. package/dist/readModels/buildReadModelDefinition.js +27 -0
  11. package/dist/readModels/buildReadModelDefinition.js.map +1 -0
  12. package/dist/readModels/index.d.ts +1 -0
  13. package/dist/readModels/index.d.ts.map +1 -1
  14. package/dist/readModels/index.js +1 -0
  15. package/dist/readModels/index.js.map +1 -1
  16. package/dist/readModels/indexDecorator.d.ts +16 -0
  17. package/dist/readModels/indexDecorator.d.ts.map +1 -0
  18. package/dist/readModels/indexDecorator.js +55 -0
  19. package/dist/readModels/indexDecorator.js.map +1 -0
  20. package/dist/reducers/Reducers.d.ts.map +1 -1
  21. package/dist/reducers/Reducers.js +9 -20
  22. package/dist/reducers/Reducers.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/projections/Projections.ts +20 -27
  26. package/readModels/ReadModels.ts +12 -22
  27. package/readModels/buildReadModelDefinition.ts +38 -0
  28. package/readModels/index.ts +1 -0
  29. package/readModels/indexDecorator.legacy.spec.ts +83 -0
  30. package/readModels/indexDecorator.ts +62 -0
  31. package/readModels/indexRegistration.spec.ts +99 -0
  32. package/readModels/standardFallbacks.standard.spec.ts +31 -0
  33. package/reducers/Reducers.ts +10 -20
@@ -0,0 +1,99 @@
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 { field } from '@cratis/fundamentals';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import type { IClientArtifactsProvider } from '../artifacts/index.js';
8
+ import type { ChronicleConnection } from '../connection/index.js';
9
+ import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
10
+ import { eventType } from '../events/eventTypeDecorator.js';
11
+ import { Projections } from '../projections/Projections.js';
12
+ import { projection } from '../projections/declarative/projection.js';
13
+ import { fromEvent } from '../projections/modelBound/fromEvent.js';
14
+ import { entersOn } from '../projections/modelBound/entersOn.js';
15
+ import { variantOf } from '../projections/modelBound/variantOf.js';
16
+ import { reducer } from '../reducers/reducer.js';
17
+ import { Reducers } from '../reducers/Reducers.js';
18
+ import { index } from './indexDecorator.js';
19
+
20
+ class IndexedEvent {}
21
+ eventType('indexed-event')(IndexedEvent);
22
+
23
+ class DeclarativeModel {
24
+ @field(String) @index() reference!: string;
25
+ }
26
+ @projection('indexed-declarative', DeclarativeModel)
27
+ class DeclarativeProjection {
28
+ define(): void {}
29
+ }
30
+
31
+ @fromEvent(IndexedEvent)
32
+ class ModelBoundModel {
33
+ @field(String) @index() reference!: string;
34
+ }
35
+
36
+ class VariantIdentity {}
37
+ @variantOf(VariantIdentity, 'id')
38
+ @entersOn(IndexedEvent)
39
+ @fromEvent(IndexedEvent)
40
+ class IndexedVariant {
41
+ @field(String) id!: string;
42
+ @field(String) @index() reference!: string;
43
+ }
44
+
45
+ class ReducedModel {
46
+ @field(String) @index() reference!: string;
47
+ }
48
+ @reducer('indexed-reducer', undefined, ReducedModel)
49
+ class IndexedReducer {}
50
+
51
+ function artifacts(readModels: IClientArtifactsProvider['readModels'] = [], projections: IClientArtifactsProvider['projections'] = [], reducers: IClientArtifactsProvider['reducers'] = []): IClientArtifactsProvider {
52
+ return {
53
+ eventTypes: [], readModels, projections, reducers, reactors: [], seeders: [], constraints: [],
54
+ webhooks: [], eventTypeMigrations: [], globalForHandlers: []
55
+ };
56
+ }
57
+
58
+ function connection() {
59
+ const registerMany = vi.fn().mockResolvedValue({});
60
+ const value = {
61
+ readModels: { registerMany },
62
+ projections: { register: vi.fn().mockResolvedValue({}) },
63
+ reducers: { observe: vi.fn().mockImplementation(async function* () {}) }
64
+ } as unknown as ChronicleConnection;
65
+ return { value, registerMany };
66
+ }
67
+
68
+ function indexesFor(registerMany: ReturnType<typeof vi.fn>, identifier: string) {
69
+ const definitions = registerMany.mock.calls[0][0].ReadModels as Array<{ Type: { Identifier: string }; Indexes: unknown[] }>;
70
+ return definitions.find(definition => definition.Type.Identifier === identifier)?.Indexes;
71
+ }
72
+
73
+ describe('when registering indexed read models through the startup paths', () => {
74
+ it('should send indexes for a declarative projection', async () => {
75
+ const { value, registerMany } = connection();
76
+ await new Projections('store', 'Default', value, artifacts([], [DeclarativeProjection]), 'sink').register();
77
+ expect(indexesFor(registerMany, 'DeclarativeModel')).toEqual([{ PropertyPath: 'reference' }]);
78
+ });
79
+
80
+ it('should send indexes for a model-bound projection', async () => {
81
+ const { value, registerMany } = connection();
82
+ await new Projections('store', 'Default', value, artifacts([ModelBoundModel]), 'sink').register();
83
+ expect(indexesFor(registerMany, 'ModelBoundModel')).toEqual([{ PropertyPath: 'reference' }]);
84
+ });
85
+
86
+ it('should send indexes for a model-bound variant', async () => {
87
+ const { value, registerMany } = connection();
88
+ await new Projections('store', 'Default', value, artifacts([IndexedVariant]), 'sink').register();
89
+ expect(indexesFor(registerMany, 'IndexedVariant')).toEqual([{ PropertyPath: 'reference' }]);
90
+ });
91
+
92
+ it('should send indexes for a reducer read model', async () => {
93
+ const { value, registerMany } = connection();
94
+ const reducers = new Reducers(artifacts([], [], [IndexedReducer]), value, 'store', 'Default', new ConnectionLifecycle(), 'sink');
95
+ await reducers.register();
96
+ reducers.dispose();
97
+ expect(indexesFor(registerMany, 'ReducedModel')).toEqual([{ PropertyPath: 'reference' }]);
98
+ });
99
+ });
@@ -2,6 +2,8 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { field } from '@cratis/fundamentals';
5
+ import { fromEvent } from '../projections/modelBound/fromEvent.js';
6
+ import { index } from './indexDecorator.js';
5
7
  import { describe, expect, it, vi } from 'vitest';
6
8
  import { IClientArtifactsProvider } from '../artifacts/IClientArtifactsProvider.js';
7
9
  import { ChronicleConnection } from '../connection/ChronicleConnection.js';
@@ -16,12 +18,41 @@ class UndecoratedModel {
16
18
  @reducer('undecorated-model', undefined, UndecoratedModel)
17
19
  class ModelReducer {}
18
20
 
21
+ class IndexedEvent { value = ''; }
22
+
23
+ class IndexedLine {
24
+ @field(String) @index() productId!: string;
25
+ @field(Number) quantity!: number;
26
+ }
27
+
28
+ @fromEvent(IndexedEvent)
29
+ class IndexedOrder {
30
+ @field(String) id!: string;
31
+ @field(String) @index() customerId!: string;
32
+ @field(Array, { genericArguments: [IndexedLine] }) lines!: IndexedLine[];
33
+ }
34
+
19
35
  const artifacts = {
20
36
  eventTypes: [], readModels: [], reactors: [], reducers: [ModelReducer], seeders: [],
21
37
  constraints: [], projections: [], webhooks: [], eventTypeMigrations: [], globalForHandlers: []
22
38
  } as IClientArtifactsProvider;
23
39
 
24
40
  describe('inferred read models with standard decorators', () => {
41
+ it('registers model-bound indexes on fields and typed collection elements as .NET property paths', async () => {
42
+ const registerMany = vi.fn().mockResolvedValue({});
43
+ const connection = { readModels: { registerMany } } as unknown as ChronicleConnection;
44
+ const provider = { ...artifacts, readModels: [IndexedOrder] };
45
+ await new ReadModels('test', 'Default', connection, provider, 'sink').register(IndexedOrder);
46
+ expect(registerMany).toHaveBeenCalledWith(expect.objectContaining({
47
+ ReadModels: [expect.objectContaining({
48
+ Indexes: [
49
+ { PropertyPath: 'customerId' },
50
+ { PropertyPath: 'lines.productId' }
51
+ ]
52
+ })]
53
+ }));
54
+ });
55
+
25
56
  it('rejects an untyped array before registering a reducer read model', async () => {
26
57
  const registerMany = vi.fn();
27
58
  const connection = { readModels: { registerMany } } as unknown as ChronicleConnection;
@@ -3,11 +3,10 @@
3
3
 
4
4
  import 'reflect-metadata';
5
5
  import { diag } from '@opentelemetry/api';
6
- import { Constructor, Guid } from '@cratis/fundamentals';
6
+ import { Constructor } from '@cratis/fundamentals';
7
7
  import { ObservationState, ReadModelObserverType, ReducerMessage } from '@cratis/chronicle.contracts';
8
8
  import { IClientArtifactsProvider } from '../artifacts/index.js';
9
9
  import { ChronicleConnection } from '../connection/index.js';
10
- import { toContractsGuid } from '../connection/Guid.js';
11
10
  import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
12
11
  import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
13
12
  import { toClientEventContext } from '../events/toClientEventContext.js';
@@ -19,6 +18,7 @@ import { IReducers } from './IReducers.js';
19
18
  import { getReducerMetadata } from './reducer.js';
20
19
  import { getReadModelMetadata } from '../readModels/index.js';
21
20
  import { getReadModelId } from '../readModels/readModel.js';
21
+ import { buildReadModelDefinition } from '../readModels/buildReadModelDefinition.js';
22
22
  import { assertUniqueReadModelIds } from '../readModels/assertUniqueReadModelIds.js';
23
23
  import { JsonSchemaGenerator } from '../schemas/index.js';
24
24
 
@@ -181,24 +181,14 @@ export class Reducers implements IReducers {
181
181
 
182
182
  const readModels = Array.from(this._reducers.entries()).map(([id, reducerType]) => {
183
183
  const readModelName = this.getReducerReadModelIdentifier(reducerType);
184
- return {
185
- Type: {
186
- Identifier: readModelName,
187
- Generation: 1
188
- },
189
- ContainerName: readModelName,
190
- DisplayName: readModelName,
191
- Sink: {
192
- ConfigurationId: toContractsGuid(Guid.empty),
193
- TypeId: this._defaultSinkTypeId
194
- },
195
- Schema: this.getReducerSchema(reducerType, readModelName),
196
- Indexes: [],
197
- ObserverType: ReadModelObserverType.Reducer,
198
- ObserverIdentifier: id,
199
- Owner: 1,
200
- Source: 1
201
- };
184
+ return buildReadModelDefinition({
185
+ identifier: readModelName,
186
+ type: getReducerMetadata(reducerType)?.readModel,
187
+ schema: this.getReducerSchema(reducerType, readModelName),
188
+ sinkTypeId: this._defaultSinkTypeId,
189
+ observerType: ReadModelObserverType.Reducer,
190
+ observerIdentifier: id
191
+ });
202
192
  });
203
193
 
204
194
  const identifiers = new Set<string>();