@cratis/chronicle 6.43.0 → 6.44.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 (37) hide show
  1. package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
  2. package/dist/projections/ProjectionDefinitionCompiler.js +30 -21
  3. package/dist/projections/ProjectionDefinitionCompiler.js.map +1 -1
  4. package/dist/projections/modelBound/FromAllEventsMetadata.d.ts +8 -0
  5. package/dist/projections/modelBound/FromAllEventsMetadata.d.ts.map +1 -0
  6. package/dist/projections/modelBound/FromAllEventsMetadata.js +4 -0
  7. package/dist/projections/modelBound/FromAllEventsMetadata.js.map +1 -0
  8. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +4 -2
  9. package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
  10. package/dist/projections/modelBound/childrenAndNestedBuilder.js +15 -8
  11. package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
  12. package/dist/projections/modelBound/fromAll.d.ts +5 -2
  13. package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
  14. package/dist/projections/modelBound/fromAll.js +8 -3
  15. package/dist/projections/modelBound/fromAll.js.map +1 -1
  16. package/dist/projections/modelBound/fromAllEvents.d.ts +32 -0
  17. package/dist/projections/modelBound/fromAllEvents.d.ts.map +1 -0
  18. package/dist/projections/modelBound/fromAllEvents.js +44 -0
  19. package/dist/projections/modelBound/fromAllEvents.js.map +1 -0
  20. package/dist/projections/modelBound/index.d.ts +2 -0
  21. package/dist/projections/modelBound/index.d.ts.map +1 -1
  22. package/dist/projections/modelBound/index.js +1 -0
  23. package/dist/projections/modelBound/index.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/dist/types/modelBoundPropertyMetadata.js +1 -1
  26. package/dist/types/modelBoundPropertyMetadata.js.map +1 -1
  27. package/package.json +2 -2
  28. package/projections/ProjectionDefinitionCompiler.ts +28 -21
  29. package/projections/for_ProjectionDefinitionCompiler/against_a_kernel/when_subscribing_with_from_all_events.integration.spec.ts +202 -0
  30. package/projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.spec.ts +235 -0
  31. package/projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts +107 -0
  32. package/projections/modelBound/FromAllEventsMetadata.ts +10 -0
  33. package/projections/modelBound/childrenAndNestedBuilder.ts +27 -8
  34. package/projections/modelBound/fromAll.ts +8 -3
  35. package/projections/modelBound/fromAllEvents.ts +48 -0
  36. package/projections/modelBound/index.ts +2 -0
  37. package/types/modelBoundPropertyMetadata.ts +1 -1
@@ -0,0 +1,235 @@
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 { beforeEach, chai, describe, it } from 'vitest';
7
+ import { fromAllEvents, getFromAllEventsMetadata } from '../../index.js';
8
+ import { eventType } from '../../events/eventTypeDecorator.js';
9
+ import { childrenFrom } from '../modelBound/childrenFrom.js';
10
+ import { fromAll, getFromAllMetadata } from '../modelBound/fromAll.js';
11
+ import { fromEvery, getFromEveryMetadata } from '../modelBound/fromEvery.js';
12
+ import { fromEvent } from '../modelBound/fromEvent.js';
13
+ import { isModelBoundProjection } from '../modelBound/isModelBoundProjection.js';
14
+ import { nested } from '../modelBound/nested.js';
15
+ import { TypeIntrospector } from '../../types/index.js';
16
+ import { ReadModelScenario } from '../../testing/ReadModelScenario.js';
17
+ import { UnsupportedProjectionOperation } from '../../testing/projections/UnsupportedProjectionOperation.js';
18
+ import { compileModelBound } from './given/compile_model_bound.js';
19
+
20
+ chai.should();
21
+
22
+ class Model {
23
+ id = '';
24
+ title = '';
25
+ source = '';
26
+ sequence = '';
27
+ }
28
+ for (const property of ['id', 'title', 'source', 'sequence']) field(String)(Model.prototype, property);
29
+
30
+ function discoveredByExistingReaders(type: Function): boolean {
31
+ return TypeIntrospector.getTrackedProperties(type).some(property =>
32
+ !!getFromEveryMetadata(type.prototype, property) || !!getFromAllMetadata(type.prototype, property));
33
+ }
34
+
35
+ describe('when compiling legacy fromAllEvents without explicit event subscriptions', () => {
36
+ let result: ReturnType<typeof compileModelBound>;
37
+ beforeEach(() => {
38
+ class Candidate extends Model {}
39
+ fromAllEvents()(Candidate.prototype, 'title');
40
+ fromAllEvents(undefined, 'sequenceNumber')(Candidate.prototype, 'sequence');
41
+ fromEvery(undefined, 'eventSourceId')(Candidate.prototype, 'source');
42
+ result = compileModelBound(Candidate);
43
+ });
44
+ it('should subscribe to every event without expanding a known event list', () => {
45
+ result.definition.SubscribesToAllEvents.should.be.true;
46
+ result.definition.From.should.deep.equal([]);
47
+ result.definition.FromEvery.should.deep.equal([]);
48
+ });
49
+ it('should merge content and context mappings into All including children', () => {
50
+ result.definition.All!.should.deep.equal({ Properties: {
51
+ title: 'title', sequence: '$eventContext(SequenceNumber)', source: '$eventContext(EventSourceId)'
52
+ }, IncludeChildren: true, AutoMap: 0 });
53
+ });
54
+ it('should preserve the distinct decorator in subscription and mapping diagnostics', () => {
55
+ result.compiled.provenance.get(result.definition)!.should.include.deep.members([
56
+ { contractPath: 'SubscribesToAllEvents', declaration: '@fromAllEvents' },
57
+ { contractPath: 'All', declaration: '@fromAllEvents' },
58
+ { contractPath: 'All.Properties.title', declaration: '@fromAllEvents' },
59
+ { contractPath: 'All.Properties.source', declaration: '@fromEvery' }
60
+ ]);
61
+ });
62
+ });
63
+
64
+ describe('when using legacy fromAllEvents in a read-model scenario', () => {
65
+ it('should reject the subscription before events can be seeded', () => {
66
+ class Candidate extends Model {}
67
+ fromAllEvents('title')(Candidate.prototype, 'title');
68
+ (() => new ReadModelScenario(Candidate, { readModels: [Candidate], eventTypes: [], projections: [], reducers: [] }))
69
+ .should.throw(UnsupportedProjectionOperation, 'SubscribesToAllEvents (@fromAllEvents)');
70
+ });
71
+ });
72
+
73
+ describe('when discovering and inheriting legacy fromAllEvents metadata', () => {
74
+ class AllModel extends Model {}
75
+ fromAllEvents('name')(AllModel.prototype, 'title');
76
+ class Derived extends AllModel {}
77
+ class Sibling extends Model {}
78
+ fromAll('restrictedName')(Sibling.prototype, 'title');
79
+
80
+ it('should discover a model using only fromAllEvents with Arc style existing readers', () => {
81
+ discoveredByExistingReaders(AllModel).should.be.true;
82
+ isModelBoundProjection(AllModel).should.be.true;
83
+ getFromAllMetadata(AllModel.prototype, 'title')!.should.deep.equal({ property: 'name', contextProperty: undefined });
84
+ (getFromEveryMetadata(AllModel.prototype, 'title') === undefined).should.be.true;
85
+ });
86
+ it('should inherit discovery and the subscription without affecting a sibling', () => {
87
+ discoveredByExistingReaders(Derived).should.be.true;
88
+ compileModelBound(Derived).definition.SubscribesToAllEvents.should.be.true;
89
+ compileModelBound(Derived).definition.All!.should.deep.equal(compileModelBound(AllModel).definition.All);
90
+ (compileModelBound(Sibling).definition.SubscribesToAllEvents === undefined).should.be.true;
91
+ (getFromAllEventsMetadata(Sibling.prototype, 'title') === undefined).should.be.true;
92
+ });
93
+ it('should keep derived annotations independent from the base', () => {
94
+ class Override extends AllModel {}
95
+ fromAllEvents('derivedName')(Override.prototype, 'title');
96
+ compileModelBound(Override).definition.All!.Properties.title.should.equal('derivedName');
97
+ compileModelBound(AllModel).definition.All!.Properties.title.should.equal('name');
98
+ });
99
+ });
100
+
101
+ describe('when compiling fromAllEvents on a child model', () => {
102
+ class Child { id = ''; title = ''; }
103
+ field(String)(Child.prototype, 'id');
104
+ field(String)(Child.prototype, 'title');
105
+ fromAllEvents('name')(Child.prototype, 'title');
106
+ class Added { id = ''; name = ''; }
107
+ eventType('from-all-events-child-added')(Added);
108
+ field(String)(Added.prototype, 'id');
109
+ field(String)(Added.prototype, 'name');
110
+ class Parent extends Model { items: Child[] = []; }
111
+ field(Array, { enumerable: true, genericArguments: [Child] })(Parent.prototype, 'items');
112
+ childrenFrom(Added, 'id')(Parent.prototype, 'items');
113
+
114
+ it('should not widen the root subscription from child metadata', () => {
115
+ const { definition } = compileModelBound(Parent);
116
+ (definition.SubscribesToAllEvents === undefined).should.be.true;
117
+ ('SubscribesToAllEvents' in definition.Children.items).should.be.false;
118
+ });
119
+ it('should collect the child mapping by bare name in the root All without subscribing to all events', () => {
120
+ const { compiled, definition } = compileModelBound(Parent);
121
+ definition.All!.should.deep.equal({ Properties: { title: 'name' }, IncludeChildren: true, AutoMap: 0 });
122
+ definition.Children.items.All!.Properties.should.deep.equal({});
123
+ compiled.provenance.get(definition)!.should.include.deep.members([
124
+ { contractPath: 'All', declaration: '@fromAllEvents' },
125
+ { contractPath: 'All.Properties.title', declaration: '@fromAllEvents' }
126
+ ]);
127
+ });
128
+ it('should emit the subscription only at the root when the parent opts in', () => {
129
+ class AllParent extends Parent {}
130
+ fromAllEvents(undefined, 'eventSourceId')(AllParent.prototype, 'source');
131
+ const { definition } = compileModelBound(AllParent);
132
+ definition.SubscribesToAllEvents.should.be.true;
133
+ definition.All!.should.deep.equal({
134
+ Properties: { source: '$eventContext(EventSourceId)', title: 'name' }, IncludeChildren: true, AutoMap: 0
135
+ });
136
+ ('SubscribesToAllEvents' in definition.Children.items).should.be.false;
137
+ });
138
+ it('should share one bare-name mapping when a later child member shadows a root member', () => {
139
+ class AllParent extends Model { items: Child[] = []; }
140
+ fromAllEvents('rootName')(AllParent.prototype, 'title');
141
+ field(Array, { genericArguments: [Child] })(AllParent.prototype, 'items');
142
+ childrenFrom(Added, 'id')(AllParent.prototype, 'items');
143
+ compileModelBound(AllParent).definition.All!.Properties.should.deep.equal({ title: 'name' });
144
+ });
145
+ });
146
+
147
+ describe('when compiling fromAllEvents on nested and inherited child models', () => {
148
+ class Detail { label = ''; sequence = ''; }
149
+ field(String)(Detail.prototype, 'label');
150
+ field(String)(Detail.prototype, 'sequence');
151
+ fromAllEvents()(Detail.prototype, 'label');
152
+ fromAllEvents(undefined, 'sequenceNumber')(Detail.prototype, 'sequence');
153
+ class Added { id = ''; }
154
+ eventType('from-all-events-nested-child-added')(Added);
155
+ field(String)(Added.prototype, 'id');
156
+ class Child { id = ''; title = ''; ignored = ''; detail = new Detail(); }
157
+ field(String)(Child.prototype, 'id');
158
+ field(String)(Child.prototype, 'title');
159
+ field(String)(Child.prototype, 'ignored');
160
+ field(Detail)(Child.prototype, 'detail');
161
+ fromAllEvents('name')(Child.prototype, 'title');
162
+ fromEvery('ignoredEvery')(Child.prototype, 'ignored');
163
+ fromAll('ignoredAlias')(Child.prototype, 'ignored');
164
+ nested(Child.prototype, 'detail');
165
+ class InheritedChild extends Child {}
166
+ class Group { children: InheritedChild[] = []; }
167
+ field(Array, { genericArguments: [InheritedChild] })(Group.prototype, 'children');
168
+ childrenFrom(Added, 'id')(Group.prototype, 'children');
169
+ class Parent { groups: Group[] = []; detail = new Detail(); }
170
+ field(Array, { genericArguments: [Group] })(Parent.prototype, 'groups');
171
+ childrenFrom(Added, 'id')(Parent.prototype, 'groups');
172
+ field(Detail)(Parent.prototype, 'detail');
173
+ nested(Parent.prototype, 'detail');
174
+
175
+ it('should collect grandchild and nested mappings into the root without path prefixes or legacy child mappings', () => {
176
+ const { definition } = compileModelBound(Parent);
177
+ definition.All!.should.deep.equal({
178
+ Properties: { title: 'name', label: 'label', sequence: '$eventContext(SequenceNumber)' },
179
+ IncludeChildren: true, AutoMap: 0
180
+ });
181
+ (definition.SubscribesToAllEvents === undefined).should.be.true;
182
+ definition.Children.groups.Children.children.All!.Properties.should.deep.equal({});
183
+ definition.Children.groups.Children.children.Nested.detail.All!.Properties.should.deep.equal({});
184
+ definition.Nested.detail.All!.Properties.should.deep.equal({});
185
+ });
186
+ it('should collect a nested-only mapping without widening the root subscription', () => {
187
+ class NestedOnly { detail = new Detail(); }
188
+ fromEvent(Added)(NestedOnly);
189
+ field(Detail)(NestedOnly.prototype, 'detail');
190
+ nested(NestedOnly.prototype, 'detail');
191
+ const { definition } = compileModelBound(NestedOnly);
192
+ definition.All!.should.deep.equal({
193
+ Properties: { label: 'label', sequence: '$eventContext(SequenceNumber)' }, IncludeChildren: true, AutoMap: 0
194
+ });
195
+ (definition.SubscribesToAllEvents === undefined).should.be.true;
196
+ ('SubscribesToAllEvents' in definition.Nested.detail).should.be.false;
197
+ });
198
+ });
199
+
200
+ describe('when opting into fromAllEvents alongside inherited restricted mappings', () => {
201
+ it('should promote inherited fromEvery and fromAll mappings into the same shared All block', () => {
202
+ class Restricted extends Model {}
203
+ fromEvery(undefined, 'eventSourceId')(Restricted.prototype, 'source');
204
+ fromAll(undefined, 'sequenceNumber')(Restricted.prototype, 'sequence');
205
+ class AllModel extends Restricted {}
206
+ fromAllEvents('name')(AllModel.prototype, 'title');
207
+ const { definition } = compileModelBound(AllModel);
208
+ definition.SubscribesToAllEvents.should.be.true;
209
+ definition.All!.should.deep.equal({
210
+ Properties: { title: 'name', source: '$eventContext(EventSourceId)', sequence: '$eventContext(SequenceNumber)' },
211
+ IncludeChildren: true, AutoMap: 0
212
+ });
213
+ compileModelBound(Restricted).definition.All!.IncludeChildren.should.be.false;
214
+ });
215
+ });
216
+
217
+ for (const allEventsFirst of [true, false]) {
218
+ describe(`when all shared mapping decorators target one legacy property with fromAllEvents ${allEventsFirst ? 'first' : 'last'}`, () => {
219
+ it('should prefer the explicit subscription without overwriting compatibility metadata', () => {
220
+ class Candidate extends Model {}
221
+ if (allEventsFirst) fromAllEvents('newName')(Candidate.prototype, 'title');
222
+ fromEvery('everyName')(Candidate.prototype, 'title');
223
+ fromAll('aliasName')(Candidate.prototype, 'title');
224
+ if (!allEventsFirst) fromAllEvents('newName')(Candidate.prototype, 'title');
225
+ const { compiled, definition } = compileModelBound(Candidate);
226
+ definition.SubscribesToAllEvents.should.be.true;
227
+ definition.All!.Properties.title.should.equal('newName');
228
+ getFromAllMetadata(Candidate.prototype, 'title')!.property!.should.equal('aliasName');
229
+ getFromEveryMetadata(Candidate.prototype, 'title')!.property!.should.equal('everyName');
230
+ compiled.provenance.get(definition)!.should.include.deep.members([
231
+ { contractPath: 'All.Properties.title', declaration: '@fromAllEvents' }
232
+ ]);
233
+ });
234
+ });
235
+ }
@@ -0,0 +1,107 @@
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 { field } from '@cratis/fundamentals';
5
+ import { chai, describe, it, type Assertion } from 'vitest';
6
+ import { fromAllEvents, getFromAllEventsMetadata, fromAll, getFromAllMetadata, fromEvery, getFromEveryMetadata, childrenFrom, nested } from '../index.js';
7
+ import { eventType } from '../../events/eventTypeDecorator.js';
8
+ import { TypeIntrospector } from '../../types/index.js';
9
+ import { ReadModelScenario } from '../../testing/ReadModelScenario.js';
10
+ import { UnsupportedProjectionOperation } from '../../testing/projections/UnsupportedProjectionOperation.js';
11
+ import { compileModelBound } from './given/compile_model_bound.js';
12
+
13
+ chai.should();
14
+ function should(value: unknown): Assertion { return (value as { should: Assertion }).should; }
15
+
16
+ class AllModel {
17
+ @field(String) id = '';
18
+ @field(String) @fromAllEvents('name') title = '';
19
+ @field(String) @fromAllEvents(undefined, 'sequenceNumber') sequence = '';
20
+ @field(String) @fromEvery(undefined, 'eventSourceId') source = '';
21
+ constructor() { throw new Error('Model discovery must not construct the model'); }
22
+ }
23
+
24
+ describe('when compiling standard fromAllEvents without explicit event subscriptions', () => {
25
+ it('should emit the subscription and shared mappings without constructing the model', () => {
26
+ const { definition } = compileModelBound(AllModel);
27
+ should(definition.SubscribesToAllEvents).be.true;
28
+ should(definition.From).deep.equal([]);
29
+ should(definition.FromEvery).deep.equal([]);
30
+ should(definition.All).deep.equal({ Properties: {
31
+ title: 'name', sequence: '$eventContext(SequenceNumber)', source: '$eventContext(EventSourceId)'
32
+ }, IncludeChildren: true, AutoMap: 0 });
33
+ });
34
+
35
+ it('should discover a model using only fromAllEvents through existing Arc style readers', () => {
36
+ class OnlyAll {
37
+ @field(String) @fromAllEvents() title = '';
38
+ constructor() { throw new Error('Discovery must not construct the model'); }
39
+ }
40
+ class Derived extends OnlyAll {}
41
+ for (const type of [OnlyAll, Derived]) {
42
+ should(TypeIntrospector.getTrackedProperties(type).some(property =>
43
+ !!getFromEveryMetadata(type.prototype, property) || !!getFromAllMetadata(type.prototype, property))).be.true;
44
+ should(getFromAllMetadata(type.prototype, 'title')).deep.equal({ property: undefined, contextProperty: undefined });
45
+ should(getFromEveryMetadata(type.prototype, 'title') === undefined).be.true;
46
+ should(compileModelBound(type).definition.SubscribesToAllEvents).be.true;
47
+ }
48
+ });
49
+
50
+ it('should inherit and override mappings without leaking the subscription to a sibling', () => {
51
+ class Base { @field(String) id = ''; }
52
+ class All extends Base { @field(String) @fromAllEvents('name') title = ''; }
53
+ class Derived extends All {}
54
+ class Override extends All { @field(String) @fromAllEvents('derivedName') override title = ''; }
55
+ class Alias extends Base { @field(String) @fromAll('name') title = ''; }
56
+ should(compileModelBound(Derived).definition.SubscribesToAllEvents).be.true;
57
+ should(compileModelBound(Derived).definition.All).deep.equal(compileModelBound(All).definition.All);
58
+ should(compileModelBound(Override).definition.All!.Properties.title).equal('derivedName');
59
+ should(compileModelBound(All).definition.All!.Properties.title).equal('name');
60
+ should(compileModelBound(Alias).definition.SubscribesToAllEvents === undefined).be.true;
61
+ should(getFromAllEventsMetadata(Alias.prototype, 'title') === undefined).be.true;
62
+ });
63
+
64
+ it('should collect standard child and nested mappings in the root All without widening the subscription', () => {
65
+ @eventType('standard-from-all-events-child-added')
66
+ class Added { @field(String) id = ''; }
67
+ class Detail { @field(String) @fromAllEvents(undefined, 'sequenceNumber') sequence = ''; }
68
+ class Child {
69
+ @field(String) id = '';
70
+ @field(String) @fromAllEvents('name') title = '';
71
+ @field(Detail) @nested detail = new Detail();
72
+ }
73
+ class Derived extends Child {}
74
+ class Parent {
75
+ @field(Array, { genericArguments: [Derived] }) @childrenFrom(Added, 'id') items: Derived[] = [];
76
+ }
77
+ const { definition } = compileModelBound(Parent);
78
+ should(definition.All).deep.equal({
79
+ Properties: { title: 'name', sequence: '$eventContext(SequenceNumber)' }, IncludeChildren: true, AutoMap: 0
80
+ });
81
+ should(definition.SubscribesToAllEvents === undefined).be.true;
82
+ should(definition.Children.items.All!.Properties).deep.equal({});
83
+ should(definition.Children.items.Nested.detail.All!.Properties).deep.equal({});
84
+ });
85
+
86
+ it('should reject all-event scenarios before replay with the standard decorator declaration', () => {
87
+ should(() => new ReadModelScenario(AllModel, { readModels: [AllModel], eventTypes: [], projections: [], reducers: [] }))
88
+ .throw(UnsupportedProjectionOperation, 'SubscribesToAllEvents (@fromAllEvents)');
89
+ });
90
+
91
+ it('should prefer fromAllEvents in either decorator order without changing the alias metadata', () => {
92
+ class Both {
93
+ @field(String) id = '';
94
+ @field(String) @fromEvery('everyName') @fromAll('aliasName') @fromAllEvents('newName') title = '';
95
+ @field(String) @fromAllEvents('otherName') @fromAll('aliasName') @fromEvery('everyName') other = '';
96
+ }
97
+ const { compiled, definition } = compileModelBound(Both);
98
+ should(definition.SubscribesToAllEvents).be.true;
99
+ should(definition.All!.Properties).deep.equal({ title: 'newName', other: 'otherName' });
100
+ should(getFromAllMetadata(Both.prototype, 'title')!.property).equal('aliasName');
101
+ should(getFromEveryMetadata(Both.prototype, 'title')!.property).equal('everyName');
102
+ should(compiled.provenance.get(definition)).include.deep.members([
103
+ { contractPath: 'SubscribesToAllEvents', declaration: '@fromAllEvents' },
104
+ { contractPath: 'All.Properties.title', declaration: '@fromAllEvents' }
105
+ ]);
106
+ });
107
+ });
@@ -0,0 +1,10 @@
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
+ /** Metadata stored by the fromAllEvents property decorator. */
5
+ export interface FromAllEventsMetadata {
6
+ /** The event property name to read the value from. */
7
+ readonly property?: string;
8
+ /** The event context property name to read the value from. */
9
+ readonly contextProperty?: string;
10
+ }
@@ -234,8 +234,13 @@ function discoverIdentifiedBy(childType: Function | undefined, eventKey: string
234
234
  * `@childrenFrom`/`@nested` properties the child/nested type itself declares.
235
235
  * @param definition - The children definition being populated.
236
236
  * @param childType - The resolved child/nested type, when available.
237
+ * @param collectSharedMapping - Collects member mappings into the root's shared All definition.
237
238
  */
238
- function populateFromType(definition: ChildrenDefinitionLike, childType: Function | undefined): void {
239
+ function populateFromType(
240
+ definition: ChildrenDefinitionLike,
241
+ childType: Function | undefined,
242
+ collectSharedMapping?: (prototype: object, property: string) => void
243
+ ): void {
239
244
  if (!childType) {
240
245
  return;
241
246
  }
@@ -275,7 +280,10 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
275
280
  }
276
281
 
277
282
  const prototype = childType.prototype;
278
- for (const property of TypeIntrospector.getTrackedProperties(childType)) {
283
+ const properties = TypeIntrospector.getTrackedProperties(childType);
284
+ // Like .NET, collect this type's members before descending into its children.
285
+ for (const property of properties) collectSharedMapping?.(prototype, property);
286
+ for (const property of properties) {
279
287
  applyPropertyMappings(prototype, property, fromByEventType);
280
288
 
281
289
  // A clear on the member carrying @nested removes that nested object; a clear on
@@ -299,11 +307,11 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
299
307
 
300
308
  const childrenFromList = getChildrenFromMetadata(prototype, property);
301
309
  if (childrenFromList.length > 0) {
302
- definition.Children[property] = buildChildrenEntry(childType, property, childrenFromList);
310
+ definition.Children[property] = buildChildrenEntry(childType, property, childrenFromList, collectSharedMapping);
303
311
  }
304
312
 
305
313
  if (isNested(prototype, property)) {
306
- definition.Nested[property] = buildNestedEntry(childType, property);
314
+ definition.Nested[property] = buildNestedEntry(childType, property, collectSharedMapping);
307
315
  }
308
316
  }
309
317
 
@@ -317,9 +325,15 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
317
325
  * @param type - The class declaring the property.
318
326
  * @param property - The property name.
319
327
  * @param metadataList - The `@childrenFrom` metadata entries declared on the property (one per creating event type).
328
+ * @param collectSharedMapping - Collects member mappings into the root's shared All definition.
320
329
  * @returns The wire-shaped children definition.
321
330
  */
322
- export function buildChildrenEntry(type: Function, property: string, metadataList: ChildrenFromMetadata[]): ChildrenDefinitionLike {
331
+ export function buildChildrenEntry(
332
+ type: Function,
333
+ property: string,
334
+ metadataList: ChildrenFromMetadata[],
335
+ collectSharedMapping?: (prototype: object, property: string) => void
336
+ ): ChildrenDefinitionLike {
323
337
  const childType = resolveChildElementType(type, property);
324
338
  const definition = createEmptyChildrenDefinition(type, childType);
325
339
 
@@ -338,7 +352,7 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
338
352
  });
339
353
  }
340
354
 
341
- populateFromType(definition, childType);
355
+ populateFromType(definition, childType, collectSharedMapping);
342
356
 
343
357
  // For non-aggregate-only creating events, populate an unmapped child identifier from
344
358
  // the creating event's key. IdentifiedBy selects the child property; it is not itself
@@ -381,9 +395,14 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
381
395
  * Builds the `ChildrenDefinition` for a `@nested` single-object property.
382
396
  * @param type - The class declaring the property.
383
397
  * @param property - The property name.
398
+ * @param collectSharedMapping - Collects member mappings into the root's shared All definition.
384
399
  * @returns The wire-shaped nested definition.
385
400
  */
386
- export function buildNestedEntry(type: Function, property: string): ChildrenDefinitionLike {
401
+ export function buildNestedEntry(
402
+ type: Function,
403
+ property: string,
404
+ collectSharedMapping?: (prototype: object, property: string) => void
405
+ ): ChildrenDefinitionLike {
387
406
  const nestedType = resolveNestedType(type, property);
388
407
  const definition = createEmptyChildrenDefinition(type, nestedType);
389
408
  definition.IdentifiedBy = notSetPropertyPath;
@@ -395,6 +414,6 @@ export function buildNestedEntry(type: Function, property: string): ChildrenDefi
395
414
  definition.RemovedWith.push({ Key: eventType, Value: { Key: '$eventSourceId', ParentKey: '' } });
396
415
  }
397
416
 
398
- populateFromType(definition, nestedType);
417
+ populateFromType(definition, nestedType, collectSharedMapping);
399
418
  return definition;
400
419
  }
@@ -5,6 +5,7 @@ import 'reflect-metadata';
5
5
  import { TypeIntrospector } from '../../types/index.js';
6
6
  import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
7
  import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
8
+ import { getFromAllEventsMetadata } from './fromAllEvents.js';
8
9
 
9
10
  /** Metadata stored by the fromAll property decorator. */
10
11
  export interface FromAllMetadata {
@@ -21,7 +22,9 @@ const METADATA_KEY = 'chronicle:projection:fromAll';
21
22
  * event type the projection is built from - the convention-based counterpart to declaring the same
22
23
  * mapping on every individual `fromEvent`. Equivalent to {@link fromEvery} for model-bound
23
24
  * projections.
24
- * @deprecated In the next major, @fromAll subscribes to every event type like fluent fromAll() and .NET [FromAll]; use @fromEvery to keep the current behavior.
25
+ * @deprecated @fromAll remains a supported, deprecated alias for restricted mappings; prefer @fromEvery.
26
+ * Contrary to the earlier 'next major' announcement, its behavior will not change;
27
+ * use @fromAllEvents to opt into all-event subscriptions.
25
28
  * @param property - Optional event property name. If not specified, uses the model property name.
26
29
  * @param contextProperty - Optional event context property name.
27
30
  * @returns A property decorator.
@@ -35,11 +38,13 @@ export function fromAll(property?: string, contextProperty?: string): ChronicleP
35
38
  }
36
39
 
37
40
  /**
38
- * Retrieves fromAll metadata stored on the given property.
41
+ * Retrieves fromAll metadata stored on the given property, falling back to fromAllEvents metadata
42
+ * for compatibility with existing model discovery. This reader does not imply an all-event subscription.
39
43
  * @param target - The class prototype.
40
44
  * @param propertyKey - The property name.
41
45
  * @returns The fromAll metadata, or undefined if not decorated.
42
46
  */
43
47
  export function getFromAllMetadata(target: object, propertyKey: string): FromAllMetadata | undefined {
44
- return getPropertyMetadata<FromAllMetadata>(METADATA_KEY, target, propertyKey);
48
+ return getPropertyMetadata<FromAllMetadata>(METADATA_KEY, target, propertyKey)
49
+ ?? getFromAllEventsMetadata(target, propertyKey);
45
50
  }
@@ -0,0 +1,48 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import 'reflect-metadata';
5
+ import { TypeIntrospector } from '../../types/index.js';
6
+ import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
7
+ import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
8
+ import type { FromAllEventsMetadata } from './FromAllEventsMetadata.js';
9
+
10
+ const METADATA_KEY = 'chronicle:projection:fromAllEvents';
11
+
12
+ /**
13
+ * On a root property, subscribes the projection to every event type, including events not declared
14
+ * with `fromEvent`. The model has one shared All block: opting in makes every root
15
+ * `@fromEvery`/`@fromAll` mapping (including inherited ones) an all-event mapping that also applies
16
+ * to children. Restricted and all-event shared mappings cannot be mixed in one model.
17
+ * `@fromEvery`/`@fromAll` mappings on child or nested types are not collected, unlike .NET,
18
+ * which also collects child [FromEvery] mappings.
19
+ * On a child or nested member, contributes its bare property name to the root All block and sets
20
+ * IncludeChildren for the shared block, including the root's restricted shared mappings, without
21
+ * widening the root subscription. With kernel 19.26.2, when the child's creating event is explicitly
22
+ * subscribed at the root, it updates the root's shared mappings and creates the child without
23
+ * populating its shared mapped values. Opt into `@fromAllEvents` at the root to populate child values
24
+ * in this case. Takes precedence over `fromEvery` and the deprecated `fromAll` alias on the same property.
25
+ * Opting in changes the definition and may trigger replay; unrelated events can create rows or clear
26
+ * mapped properties. Use `fromEvery` without `fromAllEvents` on the model or its children to keep
27
+ * shared mappings restricted to already-subscribed events and the root.
28
+ * @param property - Optional event property name. If not specified, uses the model property name.
29
+ * @param contextProperty - Optional event context property name.
30
+ * @returns A property decorator.
31
+ */
32
+ export function fromAllEvents(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
33
+ return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
34
+ TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
35
+ const metadata: FromAllEventsMetadata = { property, contextProperty };
36
+ Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Retrieves the explicit all-event subscription metadata stored on the given property.
42
+ * @param target - The class prototype.
43
+ * @param propertyKey - The property name.
44
+ * @returns The fromAllEvents metadata, or undefined if not decorated.
45
+ */
46
+ export function getFromAllEventsMetadata(target: object, propertyKey: string): FromAllEventsMetadata | undefined {
47
+ return getPropertyMetadata<FromAllEventsMetadata>(METADATA_KEY, target, propertyKey);
48
+ }
@@ -37,6 +37,8 @@ export { fromEvery, getFromEveryMetadata } from './fromEvery.js';
37
37
  export type { FromEveryMetadata } from './fromEvery.js';
38
38
  export { fromAll, getFromAllMetadata } from './fromAll.js';
39
39
  export type { FromAllMetadata } from './fromAll.js';
40
+ export { fromAllEvents, getFromAllEventsMetadata } from './fromAllEvents.js';
41
+ export type { FromAllEventsMetadata } from './FromAllEventsMetadata.js';
40
42
  export { noAutoMap, isNoAutoMap, isPropertyNoAutoMap } from './noAutoMap.js';
41
43
  export { eventSequence, eventLog, getEventSequenceMetadata } from './eventSequence.js';
42
44
  export { variantOf, getVariantOfMetadata } from './variantOf.js';
@@ -12,7 +12,7 @@ const finalizedMetadata = new FinalizationRegistry<WeakRef<object>>(reference =>
12
12
 
13
13
  export const modelBoundPropertyKeys = [
14
14
  'setFrom', 'setFromContext', 'setValue', 'addFrom', 'subtractFrom',
15
- 'increment', 'decrement', 'count', 'childrenFrom', 'join', 'fromEvery', 'fromAll'
15
+ 'increment', 'decrement', 'count', 'childrenFrom', 'join', 'fromEvery', 'fromAll', 'fromAllEvents'
16
16
  ].map(name => `chronicle:projection:${name}`);
17
17
 
18
18
  /** Records a standard-decorated class with an event mapping on one of its fields. */