@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.
Files changed (69) hide show
  1. package/README.md +3 -1
  2. package/artifacts/DefaultClientArtifactsProvider.ts +12 -1
  3. package/artifacts/validateArtifactSchemas.spec.ts +3 -3
  4. package/compliance/pii.ts +2 -2
  5. package/compliance/subject.ts +1 -1
  6. package/confidentiality/encrypted.ts +1 -1
  7. package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
  8. package/dist/artifacts/DefaultClientArtifactsProvider.js +14 -1
  9. package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
  10. package/dist/compliance/pii.d.ts +2 -2
  11. package/dist/compliance/pii.js +2 -2
  12. package/dist/compliance/subject.d.ts +1 -1
  13. package/dist/compliance/subject.js +1 -1
  14. package/dist/confidentiality/encrypted.d.ts +1 -1
  15. package/dist/confidentiality/encrypted.js +1 -1
  16. package/dist/events/eventTypeDecorator.d.ts +6 -1
  17. package/dist/events/eventTypeDecorator.d.ts.map +1 -1
  18. package/dist/events/eventTypeDecorator.js.map +1 -1
  19. package/dist/projections/Projections.d.ts.map +1 -1
  20. package/dist/projections/Projections.js +32 -15
  21. package/dist/projections/Projections.js.map +1 -1
  22. package/dist/projections/modelBound/fromEvent.d.ts.map +1 -1
  23. package/dist/projections/modelBound/fromEvent.js +2 -0
  24. package/dist/projections/modelBound/fromEvent.js.map +1 -1
  25. package/dist/readModels/ReadModels.d.ts +1 -0
  26. package/dist/readModels/ReadModels.d.ts.map +1 -1
  27. package/dist/readModels/ReadModels.js +23 -17
  28. package/dist/readModels/ReadModels.js.map +1 -1
  29. package/dist/readModels/assertUniqueReadModelIds.d.ts +4 -0
  30. package/dist/readModels/assertUniqueReadModelIds.d.ts.map +1 -0
  31. package/dist/readModels/assertUniqueReadModelIds.js +16 -0
  32. package/dist/readModels/assertUniqueReadModelIds.js.map +1 -0
  33. package/dist/readModels/readModel.d.ts +5 -0
  34. package/dist/readModels/readModel.d.ts.map +1 -1
  35. package/dist/readModels/readModel.js +14 -0
  36. package/dist/readModels/readModel.js.map +1 -1
  37. package/dist/reducers/Reducers.d.ts.map +1 -1
  38. package/dist/reducers/Reducers.js +12 -2
  39. package/dist/reducers/Reducers.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/dist/types/TypeDiscoverer.d.ts +2 -0
  42. package/dist/types/TypeDiscoverer.d.ts.map +1 -1
  43. package/dist/types/TypeDiscoverer.js +39 -2
  44. package/dist/types/TypeDiscoverer.js.map +1 -1
  45. package/events/constructorRoundTrip.legacy.spec.ts +47 -0
  46. package/events/constructorRoundTrip.standard.spec.ts +47 -0
  47. package/events/eventTypeDecorator.ts +6 -1
  48. package/package.json +1 -1
  49. package/projections/Projections.childrenAndNested.spec.ts +0 -2
  50. package/projections/Projections.modelBoundCompleteness.spec.ts +0 -7
  51. package/projections/Projections.operationalSurface.spec.ts +0 -4
  52. package/projections/Projections.spec.ts +0 -2
  53. package/projections/Projections.ts +32 -17
  54. package/projections/Projections.variants.spec.ts +0 -5
  55. package/projections/modelBound/fromEvent.ts +3 -0
  56. package/readModels/MaterializedReadModels.spec.ts +0 -5
  57. package/readModels/ReadModels.spec.ts +0 -9
  58. package/readModels/ReadModels.ts +24 -18
  59. package/readModels/assertUniqueReadModelIds.ts +18 -0
  60. package/readModels/discovery.spec.ts +113 -0
  61. package/readModels/readModel.ts +14 -0
  62. package/readModels/standardFallbacks.standard.spec.ts +1 -1
  63. package/reducers/Reducers.spec.ts +18 -0
  64. package/reducers/Reducers.ts +11 -2
  65. package/schemas/JsonSchemaGenerator.confidentiality.spec.ts +5 -14
  66. package/schemas/JsonSchemaGenerator.spec.ts +3 -13
  67. package/schemas/decorators.legacy.spec.ts +2 -3
  68. package/schemas/decorators.standard.spec.ts +12 -10
  69. 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
- typesForDecorator.set(discoveredName, type);
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;AAOxB;;GAEG;AACH,MAAM,OAAO,cAAc;IACvB,0CAA0C;IAC1C,MAAM,CAAU,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;IAEvC,MAAM,CAAU,gBAAgB,GAAiD,IAAI,GAAG,EAAE,CAAC;IAElF,KAAK,CAAe;IACpB,WAAW,CAAe;IAE3C;;;;OAIG;IACH,YAAY,IAAmB,EAAE,UAAyB;QACtD,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,cAAc,CAAC,sBAAsB,CAAC;QAC3D,IAAI,CAAC,WAAW,GAAG,UAAU,IAAI,cAAc,CAAC,oBAAoB,CAAC;IACzE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAQ,CAAC,OAA0B;QACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,aAA4B,EAAE,IAAiB,EAAE,IAAa;QACnE,MAAM,cAAc,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;QACzC,MAAM,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,GAAG,EAAuB,CAAC;QAC/G,iBAAiB,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QAC5C,cAAc,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC1E,CAAC;IAED;;;;OAIG;IACH,uBAAuB,CAAC,aAA4B;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAClG,CAAC;IAED;;;;;OAKG;IACH,6BAA6B,CAAC,aAA4B,EAAE,IAAY;QACpE,OAAO,cAAc,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAED;;;OAGG;IACH,KAAK;QACD,cAAc,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAC5C,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,OAA0B;QAClE,IAAI,YAAqB,CAAC;QAC1B,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAuB,CAAC;YAC9D,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;QACjG,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;YACxE,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC7F,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QACtD,OAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACJ"}
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
- * constructor(readonly firstName: string, readonly lastName: string) {}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "6.6.0",
3
+ "version": "6.7.1",
4
4
  "description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -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 || byReadModel.has(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
- for (const type of this._clientArtifacts.readModels) {
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 (!metadata) {
386
- continue;
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
- const rm = getReadModelMetadata(metadata.readModelType);
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() ?? properties);
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
- const readModelMetadata = getReadModelMetadata(type);
655
-
656
- if (readModelMetadata && hasFromEventMetadata(type)) {
670
+ if (hasFromEventMetadata(type) || hasModelBoundProperties(type)) {
671
+ const identifier = getReadModelId(type);
657
672
  return {
658
- id: new ProjectionId(readModelMetadata.id.value),
673
+ id: new ProjectionId(identifier),
659
674
  eventSequenceId: getEventSequenceMetadata(type),
660
- readModelIdentifier: readModelMetadata.id.value
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);