@cratis/chronicle 6.51.1 → 6.52.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 (94) hide show
  1. package/dist/observation/FastForwardOutcome.d.ts +14 -0
  2. package/dist/observation/FastForwardOutcome.d.ts.map +1 -0
  3. package/dist/observation/FastForwardOutcome.js +17 -0
  4. package/dist/observation/FastForwardOutcome.js.map +1 -0
  5. package/dist/observation/FastForwardResult.d.ts +14 -0
  6. package/dist/observation/FastForwardResult.d.ts.map +1 -0
  7. package/dist/observation/FastForwardResult.js +4 -0
  8. package/dist/observation/FastForwardResult.js.map +1 -0
  9. package/dist/observation/IObservers.d.ts +9 -0
  10. package/dist/observation/IObservers.d.ts.map +1 -1
  11. package/dist/observation/Observers.d.ts +3 -0
  12. package/dist/observation/Observers.d.ts.map +1 -1
  13. package/dist/observation/Observers.js +11 -0
  14. package/dist/observation/Observers.js.map +1 -1
  15. package/dist/observation/index.d.ts +3 -0
  16. package/dist/observation/index.d.ts.map +1 -1
  17. package/dist/observation/index.js +2 -0
  18. package/dist/observation/index.js.map +1 -1
  19. package/dist/observation/toFastForwardResult.d.ts +16 -0
  20. package/dist/observation/toFastForwardResult.d.ts.map +1 -0
  21. package/dist/observation/toFastForwardResult.js +37 -0
  22. package/dist/observation/toFastForwardResult.js.map +1 -0
  23. package/dist/reactors/Reactors.d.ts.map +1 -1
  24. package/dist/reactors/Reactors.js +3 -1
  25. package/dist/reactors/Reactors.js.map +1 -1
  26. package/dist/readModels/for_ReadModels/given/Book.d.ts +8 -0
  27. package/dist/readModels/for_ReadModels/given/Book.d.ts.map +1 -0
  28. package/dist/readModels/for_ReadModels/given/Book.js +25 -0
  29. package/dist/readModels/for_ReadModels/given/Book.js.map +1 -0
  30. package/dist/readModels/for_ReadModels/given/BookBorrowed.d.ts +3 -0
  31. package/dist/readModels/for_ReadModels/given/BookBorrowed.d.ts.map +1 -0
  32. package/dist/readModels/for_ReadModels/given/BookBorrowed.js +5 -0
  33. package/dist/readModels/for_ReadModels/given/BookBorrowed.js.map +1 -0
  34. package/dist/readModels/for_ReadModels/given/BookRegistered.d.ts +5 -0
  35. package/dist/readModels/for_ReadModels/given/BookRegistered.d.ts.map +1 -0
  36. package/dist/readModels/for_ReadModels/given/BookRegistered.js +7 -0
  37. package/dist/readModels/for_ReadModels/given/BookRegistered.js.map +1 -0
  38. package/dist/readModels/for_ReadModels/given/BookReturned.d.ts +3 -0
  39. package/dist/readModels/for_ReadModels/given/BookReturned.d.ts.map +1 -0
  40. package/dist/readModels/for_ReadModels/given/BookReturned.js +5 -0
  41. package/dist/readModels/for_ReadModels/given/BookReturned.js.map +1 -0
  42. package/dist/readModels/for_deserializeReadModel/given/Address.d.ts +6 -0
  43. package/dist/readModels/for_deserializeReadModel/given/Address.d.ts.map +1 -0
  44. package/dist/readModels/for_deserializeReadModel/given/Address.js +11 -0
  45. package/dist/readModels/for_deserializeReadModel/given/Address.js.map +1 -0
  46. package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.d.ts +9 -0
  47. package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.d.ts.map +1 -0
  48. package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.js +16 -0
  49. package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.js.map +1 -0
  50. package/dist/reducers/Reducers.d.ts.map +1 -1
  51. package/dist/reducers/Reducers.js +2 -1
  52. package/dist/reducers/Reducers.js.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/dist/webhooks/WebhookDefinitionBuilder.d.ts.map +1 -1
  55. package/dist/webhooks/WebhookDefinitionBuilder.js +3 -1
  56. package/dist/webhooks/WebhookDefinitionBuilder.js.map +1 -1
  57. package/dist/webhooks/Webhooks.d.ts.map +1 -1
  58. package/dist/webhooks/Webhooks.js +3 -1
  59. package/dist/webhooks/Webhooks.js.map +1 -1
  60. package/observation/FastForwardOutcome.ts +19 -0
  61. package/observation/FastForwardResult.ts +19 -0
  62. package/observation/IObservers.ts +10 -0
  63. package/observation/Observers.spec.ts +41 -4
  64. package/observation/Observers.ts +14 -0
  65. package/observation/index.ts +3 -0
  66. package/observation/toFastForwardResult.ts +40 -0
  67. package/package.json +4 -4
  68. package/projections/for_ProjectionDefinitionCompiler/against_a_kernel/when_subscribing_with_from_all_events.integration.spec.ts +3 -3
  69. package/reactors/Reactors.ts +3 -1
  70. package/readModels/for_ReadModels/given/Book.ts +26 -0
  71. package/readModels/for_ReadModels/given/BookBorrowed.ts +4 -0
  72. package/readModels/for_ReadModels/given/BookRegistered.ts +7 -0
  73. package/readModels/for_ReadModels/given/BookReturned.ts +4 -0
  74. package/readModels/for_ReadModels/when_reading_a_model_bound_projection/without_registration_fields.spec.ts +30 -0
  75. package/readModels/for_deserializeReadModel/given/Address.ts +12 -0
  76. package/readModels/for_deserializeReadModel/given/IdentityDefaults.fixture.ts +15 -0
  77. package/readModels/for_deserializeReadModel/given/SetterDefaults.fixture.ts +22 -0
  78. package/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.ts +18 -0
  79. package/readModels/for_deserializeReadModel/when_restoring/with_a_non_idempotent_setter_and_an_absent_sibling.spec.ts +20 -0
  80. package/readModels/for_deserializeReadModel/when_restoring/with_a_partial_nested_object.spec.ts +31 -0
  81. package/readModels/for_deserializeReadModel/when_restoring/with_absent_fields.spec.ts +22 -0
  82. package/readModels/for_deserializeReadModel/when_restoring/with_explicit_null.spec.ts +20 -0
  83. package/readModels/for_deserializeReadModel/when_restoring/with_identity_dependent_defaults.spec.ts +20 -0
  84. package/readModels/for_deserializeReadModel/when_restoring/with_partial_array_items.spec.ts +23 -0
  85. package/readModels/for_deserializeReadModel/when_restoring/with_present_fields.spec.ts +19 -0
  86. package/reducers/Reducers.ts +2 -1
  87. package/testing/EventScenario.constraint-values.spec.ts +2 -2
  88. package/testing/for_ReadModelScenario/when_evaluating_arithmetic/with_legacy_decorators.legacy.spec.ts +1 -1
  89. package/testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts +1 -1
  90. package/testing/for_ReadModelScenario/when_evaluating_joins/with_a_fluent_projection.spec.ts +2 -2
  91. package/testing/for_ReadModelScenario/when_evaluating_joins/with_legacy_decorators.legacy.spec.ts +2 -2
  92. package/testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts +2 -2
  93. package/webhooks/WebhookDefinitionBuilder.ts +3 -1
  94. package/webhooks/Webhooks.ts +3 -1
@@ -0,0 +1,40 @@
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 { FastForwardOutcome as ContractFastForwardOutcome, FastForwardResponse } from '@cratis/chronicle.contracts';
5
+ import { EventSequenceNumber } from '../eventSequences/EventSequenceNumber.js';
6
+ import { FastForwardOutcome } from './FastForwardOutcome.js';
7
+ import { FastForwardResult } from './FastForwardResult.js';
8
+
9
+ /**
10
+ * Converts a wire {@link ContractFastForwardOutcome} into the client {@link FastForwardOutcome}.
11
+ * @param outcome - The wire outcome to convert.
12
+ * @returns The converted client outcome.
13
+ */
14
+ export function toFastForwardOutcome(outcome: ContractFastForwardOutcome): FastForwardOutcome {
15
+ switch (outcome) {
16
+ case ContractFastForwardOutcome.FastForwarded:
17
+ return FastForwardOutcome.FastForwarded;
18
+ case ContractFastForwardOutcome.AlreadyAtTail:
19
+ return FastForwardOutcome.AlreadyAtTail;
20
+ case ContractFastForwardOutcome.ObserverReplaying:
21
+ return FastForwardOutcome.Replaying;
22
+ case ContractFastForwardOutcome.NotAvailable:
23
+ return FastForwardOutcome.NotAvailable;
24
+ default:
25
+ throw new Error(`Unknown fast forward outcome: ${outcome}`);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Converts a wire {@link FastForwardResponse} into the client {@link FastForwardResult}.
31
+ * @param response - The wire response to convert.
32
+ * @returns The converted client result.
33
+ */
34
+ export function toFastForwardResult(response: FastForwardResponse): FastForwardResult {
35
+ return {
36
+ outcome: toFastForwardOutcome(response.Outcome),
37
+ previousNextEventSequenceNumber: new EventSequenceNumber(response.PreviousNextEventSequenceNumber),
38
+ nextEventSequenceNumber: new EventSequenceNumber(response.NextEventSequenceNumber)
39
+ };
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "6.51.1",
3
+ "version": "6.52.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",
@@ -178,7 +178,7 @@
178
178
  },
179
179
  "dependencies": {
180
180
  "@bufbuild/protobuf": "^2.16.0",
181
- "@cratis/chronicle.contracts": "19.31.10",
181
+ "@cratis/chronicle.contracts": "19.56.1",
182
182
  "@grpc/grpc-js": "^1.14.5",
183
183
  "@opentelemetry/api": "^1.9.1",
184
184
  "glob": "^13.0.6",
@@ -188,7 +188,7 @@
188
188
  "undici": "^8.11.2"
189
189
  },
190
190
  "devDependencies": {
191
- "@cratis/fundamentals": "7.22.4",
191
+ "@cratis/fundamentals": "7.24.1",
192
192
  "@opentelemetry/context-async-hooks": "2.11.0",
193
193
  "@opentelemetry/core": "2.11.0",
194
194
  "@opentelemetry/instrumentation-grpc": "0.222.0",
@@ -203,6 +203,6 @@
203
203
  "vitest": "^5.0.3"
204
204
  },
205
205
  "peerDependencies": {
206
- "@cratis/fundamentals": "^7.22.4"
206
+ "@cratis/fundamentals": "^7.24.1"
207
207
  }
208
208
  }
@@ -149,7 +149,7 @@ describe.skipIf(!connectionString && !process.env.CI)('when opting into fromAllE
149
149
 
150
150
  await append(knownSource, Object.assign(new Unrelated(), { marker: 'no-value' }));
151
151
  allAfterUnrelated = await eventually(() => store.readModels.findInstanceById(AllWithChildren, knownSource),
152
- model => model !== null && (model.lastValue === null || model.lastValue === undefined));
152
+ model => model !== null && model.lastValue === '');
153
153
 
154
154
  await append(unrelatedSource, Object.assign(new Unrelated(), { marker: 'new-source' }));
155
155
  allOnly = await eventually(() => store.readModels.findInstanceById(AllOnly, unrelatedSource), model => model !== null);
@@ -183,7 +183,7 @@ describe.skipIf(!connectionString && !process.env.CI)('when opting into fromAllE
183
183
  (restrictedAfterChild!.children[0].lastValue !== 'child-value').should.be.true;
184
184
  });
185
185
  it('should clear the mapped root property on an unrelated event without that value', () => {
186
- (allAfterUnrelated!.lastValue === null || allAfterUnrelated!.lastValue === undefined).should.be.true;
186
+ allAfterUnrelated!.lastValue.should.equal('');
187
187
  });
188
188
  it('should leave the restricted alias unchanged on an unrelated event', () => restrictedAfterUnrelated!.lastValue.should.equal(restrictedAfterChild!.lastValue));
189
189
  it('should apply a child-declared all-event mapping through the shared root All block', () => {
@@ -195,7 +195,7 @@ describe.skipIf(!connectionString && !process.env.CI)('when opting into fromAllE
195
195
  childOnlyAfterChild!.rootValue.should.equal('child-value');
196
196
  childOnlyAfterChild!.children[0].id.should.equal('child-one');
197
197
  // The explicitly subscribed root From entry maps the root, not the child's shared values.
198
- (childOnlyAfterChild!.children[0].lastValue === undefined).should.be.true;
198
+ childOnlyAfterChild!.children[0].lastValue.should.equal('');
199
199
  childOnlyAfterUnrelated!.rootValue.should.equal('child-value');
200
200
  (unrelatedChildOnly === null).should.be.true;
201
201
  });
@@ -5,7 +5,7 @@ import 'reflect-metadata';
5
5
  import { createLogger } from '../logging/createLogger.js';
6
6
  import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
7
7
  import { Constructor } from '@cratis/fundamentals';
8
- import { ObservationState, ReactorMessage, ReplayState } from '@cratis/chronicle.contracts';
8
+ import { EventGenerationDelivery, ObservationState, ReactorMessage, ReplayState } from '@cratis/chronicle.contracts';
9
9
  import { IClientArtifactsProvider } from '../artifacts/index.js';
10
10
  import { ChronicleConnection } from '../connection/index.js';
11
11
  import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
@@ -276,6 +276,8 @@ export class Reactors implements IReactors {
276
276
  Key: EVENT_SOURCE_ID_KEY
277
277
  })),
278
278
  IsReplayable: !isOnceOnly(reactorType),
279
+ IsNotReplayable: isOnceOnly(reactorType),
280
+ GenerationDelivery: EventGenerationDelivery.Compatibility,
279
281
  Tags: getTagsFor(reactorType).map(t => t.value),
280
282
  Filters: {
281
283
  FilterTags: getFilterTagsFor(reactorType).map(t => t.value),
@@ -0,0 +1,26 @@
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 { fromEvent } from '../../../projections/modelBound/fromEvent.js';
7
+ import { setValue } from '../../../projections/modelBound/setValue.js';
8
+ import { BookRegistered } from './BookRegistered.js';
9
+ import { BookBorrowed } from './BookBorrowed.js';
10
+ import { BookReturned } from './BookReturned.js';
11
+
12
+ export class Book {
13
+ id = '';
14
+ title: string | null = null;
15
+ isbn: string | null = null;
16
+ borrowed = false;
17
+ }
18
+ field(String)(Book.prototype, 'id');
19
+ field(String)(Book.prototype, 'title');
20
+ field(String)(Book.prototype, 'isbn');
21
+ field(Boolean)(Book.prototype, 'borrowed');
22
+ fromEvent(BookRegistered)(Book);
23
+ fromEvent(BookBorrowed)(Book);
24
+ fromEvent(BookReturned)(Book);
25
+ setValue(BookBorrowed, true)(Book.prototype, 'borrowed');
26
+ setValue(BookReturned, false)(Book.prototype, 'borrowed');
@@ -0,0 +1,4 @@
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
+ export class BookBorrowed {}
@@ -0,0 +1,7 @@
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
+ export class BookRegistered {
5
+ title = '';
6
+ isbn = '';
7
+ }
@@ -0,0 +1,4 @@
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
+ export class BookReturned {}
@@ -0,0 +1,30 @@
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 { beforeEach, chai, describe, it, vi } from 'vitest';
5
+ import type { IClientArtifactsProvider } from '../../../artifacts/index.js';
6
+ import type { ChronicleConnection } from '../../../connection/index.js';
7
+ import { ReadModels } from '../../ReadModels.js';
8
+ import { Book } from '../given/Book.js';
9
+
10
+ const should = chai.should();
11
+
12
+ for (const borrowed of [true, false]) {
13
+ describe(`when reading a model-bound projection after a book is ${borrowed ? 'borrowed' : 'returned'} without registration fields`, () => {
14
+ let result: Book;
15
+
16
+ beforeEach(async () => {
17
+ const connection = {
18
+ readModels: { getInstanceByKey: vi.fn().mockResolvedValue({ ReadModel: JSON.stringify({ id: 'book', borrowed }) }) }
19
+ } as unknown as ChronicleConnection;
20
+ const artifacts = { readModels: [Book], projections: [], reducers: [] } as unknown as IClientArtifactsProvider;
21
+ const readModels = new ReadModels('store', 'tenant', connection, artifacts, 'sink');
22
+ result = await readModels.getInstanceById(Book, 'book');
23
+ });
24
+
25
+ it('should restore the model type', () => result.should.be.instanceOf(Book));
26
+ it('should keep the title null', () => should.equal(result.title, null));
27
+ it('should keep the isbn null', () => should.equal(result.isbn, null));
28
+ it('should restore the borrowed state', () => result.borrowed.should.equal(borrowed));
29
+ });
30
+ }
@@ -0,0 +1,12 @@
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
+
7
+ export class Address {
8
+ street = 'Default street';
9
+ city: string | null = null;
10
+ }
11
+ field(String)(Address.prototype, 'street');
12
+ field(String)(Address.prototype, 'city');
@@ -0,0 +1,15 @@
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
+
7
+ export class IdentityDefaults {
8
+ original = {};
9
+ mirror = this.original;
10
+ owner = this;
11
+ name = 'default';
12
+ }
13
+ field(Object)(IdentityDefaults.prototype, 'mirror');
14
+ field(Object)(IdentityDefaults.prototype, 'owner');
15
+ field(String)(IdentityDefaults.prototype, 'name');
@@ -0,0 +1,22 @@
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
+
7
+ export class SetterDefaults {
8
+ sibling = 'default';
9
+ assignments = 0;
10
+ private _name = 'default';
11
+
12
+ get name(): string {
13
+ return this._name;
14
+ }
15
+
16
+ set name(value: string) {
17
+ this.assignments++;
18
+ this._name = `prefix:${value}`;
19
+ }
20
+ }
21
+ field(String)(SetterDefaults.prototype, 'name');
22
+ field(String)(SetterDefaults.prototype, 'sibling');
@@ -0,0 +1,18 @@
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
+
7
+ import { Address } from './Address.js';
8
+
9
+ export class Model {
10
+ title: string | null = null;
11
+ status = 'available';
12
+ address = new Address();
13
+ children = [new Address()];
14
+ }
15
+ field(String)(Model.prototype, 'title');
16
+ field(String)(Model.prototype, 'status');
17
+ field(Address)(Model.prototype, 'address');
18
+ field(Array, { genericArguments: [Address] })(Model.prototype, 'children');
@@ -0,0 +1,20 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { SetterDefaults } from '../given/SetterDefaults.fixture.js';
7
+
8
+ chai.should();
9
+
10
+ describe('when restoring with a non-idempotent setter and an absent sibling', () => {
11
+ let result: SetterDefaults;
12
+
13
+ beforeEach(() => {
14
+ result = deserializeReadModel(SetterDefaults, '{"name":"stored"}');
15
+ });
16
+
17
+ it('should apply the setter transformation once', () => result.name.should.equal('prefix:stored'));
18
+ it('should invoke the setter once', () => result.assignments.should.equal(1));
19
+ it('should keep the absent sibling default', () => result.sibling.should.equal('default'));
20
+ });
@@ -0,0 +1,31 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { Model } from '../given/a_read_model_with_defaults.js';
7
+ import { Address } from '../given/Address.js';
8
+
9
+ const should = chai.should();
10
+
11
+ describe('when restoring with a partial nested object', () => {
12
+ let result: Model;
13
+
14
+ beforeEach(() => {
15
+ result = deserializeReadModel(Model, '{"address":{"city":"Oslo"}}');
16
+ });
17
+
18
+ it('should restore the nested type', () => result.address.should.be.instanceOf(Address));
19
+ it('should keep the absent nested field default', () => result.address.street.should.equal('Default street'));
20
+ it('should replace the present nested field default', () => result.address.city!.should.equal('Oslo'));
21
+ });
22
+
23
+ describe('when restoring with explicit null in a nested object', () => {
24
+ let result: Model;
25
+
26
+ beforeEach(() => {
27
+ result = deserializeReadModel(Model, '{"address":{"street":null}}');
28
+ });
29
+
30
+ it('should replace the non-null nested field default', () => should.equal(result.address.street, null));
31
+ });
@@ -0,0 +1,22 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { Model } from '../given/a_read_model_with_defaults.js';
7
+ import { Address } from '../given/Address.js';
8
+
9
+ const should = chai.should();
10
+
11
+ describe('when restoring with absent fields', () => {
12
+ let result: Model;
13
+
14
+ beforeEach(() => {
15
+ result = deserializeReadModel(Model, '{}');
16
+ });
17
+
18
+ it('should keep the null default', () => should.equal(result.title, null));
19
+ it('should keep the non-null default', () => result.status.should.equal('available'));
20
+ it('should keep the nested object default', () => result.address.should.deep.equal(new Address()));
21
+ it('should deserialize an absent array to an empty array', () => result.children.should.deep.equal([]));
22
+ });
@@ -0,0 +1,20 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { Model } from '../given/a_read_model_with_defaults.js';
7
+
8
+ const should = chai.should();
9
+
10
+ describe('when restoring with explicit null', () => {
11
+ let result: Model;
12
+
13
+ beforeEach(() => {
14
+ result = deserializeReadModel(Model, '{"status":null,"address":null,"children":null}');
15
+ });
16
+
17
+ it('should replace the non-null scalar default', () => should.equal(result.status, null));
18
+ it('should replace the nested object default', () => should.equal(result.address, null));
19
+ it('should replace the array default', () => should.equal(result.children, null));
20
+ });
@@ -0,0 +1,20 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { IdentityDefaults } from '../given/IdentityDefaults.fixture.js';
7
+
8
+ chai.should();
9
+
10
+ describe('when restoring with identity-dependent defaults', () => {
11
+ let result: IdentityDefaults;
12
+
13
+ beforeEach(() => {
14
+ result = deserializeReadModel(IdentityDefaults, '{"name":"stored"}');
15
+ });
16
+
17
+ it('should keep the default referencing another member identical to that member', () => result.mirror.should.equal(result.original));
18
+ it('should keep the default referencing its owner identical to the returned instance', () => result.owner.should.equal(result));
19
+ it('should still replace a present decorated field', () => result.name.should.equal('stored'));
20
+ });
@@ -0,0 +1,23 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { Model } from '../given/a_read_model_with_defaults.js';
7
+ import { Address } from '../given/Address.js';
8
+
9
+ const should = chai.should();
10
+
11
+ describe('when restoring with partial array items', () => {
12
+ let result: Model;
13
+
14
+ beforeEach(() => {
15
+ result = deserializeReadModel(Model, '{"children":[{}, {"street":"Stored street","city":null}]}');
16
+ });
17
+
18
+ it('should replace the array default', () => result.children.should.have.lengthOf(2));
19
+ it('should restore the child type', () => result.children[0].should.be.instanceOf(Address));
20
+ it('should keep the absent child field default', () => result.children[0].street.should.equal('Default street'));
21
+ it('should replace the present child field default', () => result.children[1].street.should.equal('Stored street'));
22
+ it('should preserve the null child field', () => should.equal(result.children[1].city, null));
23
+ });
@@ -0,0 +1,19 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { deserializeReadModel } from '../../deserializeReadModel.js';
6
+ import { Model } from '../given/a_read_model_with_defaults.js';
7
+
8
+ chai.should();
9
+
10
+ describe('when restoring with present fields', () => {
11
+ let result: Model;
12
+
13
+ beforeEach(() => {
14
+ result = deserializeReadModel(Model, '{"title":"Stored title","status":"borrowed"}');
15
+ });
16
+
17
+ it('should replace the null default', () => result.title!.should.equal('Stored title'));
18
+ it('should replace the non-null default', () => result.status.should.equal('borrowed'));
19
+ });
@@ -5,7 +5,7 @@ import 'reflect-metadata';
5
5
  import { createLogger } from '../logging/createLogger.js';
6
6
  import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
7
7
  import { Constructor } from '@cratis/fundamentals';
8
- import { AppendedEvent, ReadModelObserverType, ReduceOperationMessage, ReducerMessage, ReplayState } from '@cratis/chronicle.contracts';
8
+ import { AppendedEvent, EventGenerationDelivery, ReadModelObserverType, ReduceOperationMessage, ReducerMessage, ReplayState } from '@cratis/chronicle.contracts';
9
9
  import { IClientArtifactsProvider } from '../artifacts/index.js';
10
10
  import { ChronicleConnection } from '../connection/index.js';
11
11
  import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
@@ -370,6 +370,7 @@ export class Reducers implements IReducers {
370
370
  ReadModel: readModelName,
371
371
  Hash: '',
372
372
  IsActive: getReducerMetadata(reducerType)?.isActive ?? true,
373
+ GenerationDelivery: EventGenerationDelivery.Compatibility,
373
374
  Tags: getTagsFor(reducerType).map(t => t.value),
374
375
  Filters: {
375
376
  FilterTags: getFilterTagsFor(reducerType).map(t => t.value),
@@ -168,11 +168,11 @@ describe('fixture-backed unique scalar values', () => {
168
168
  const results = scenario.results;
169
169
  const unprovenKeys = ['!', '#', '%', '&', "'", '(', ')', '*', '+', ',', '/', ';', '<', '=', '>', '?', '[', ']', '^', '`', '~'];
170
170
  const invalidContent: unknown[] = [1, 0, 1.5, undefined, [], {}, new Date(), 'É', 'e\u0301', '"', '\\', '\n'];
171
- // The production serializer throws on null before checkedContent can run.
171
+ // Fundamentals preserves null in JSON, so checkedContent rejects it as unsupported string content.
172
172
  for (const [value, operation] of [
173
173
  ...unprovenKeys.map(value => [value, 'artifacts.constraints']),
174
174
  ...invalidContent.map(value => [value, 'append.content']),
175
- [null, 'append.serialization']
175
+ [null, 'append.content']
176
176
  ] as Array<[unknown, string]>) {
177
177
  const bad = new OracleDomainText('valid');
178
178
  Object.assign(bad, { key: value });
@@ -83,7 +83,7 @@ describe('when evaluating arithmetic with legacy model-bound decorators', () =>
83
83
  it('should suppress aggregate-only AutoMap and restore it for mixed mappings', async () => {
84
84
  const scenario = new ReadModelScenario(Totals, artifacts);
85
85
  scenario.given.forEventSource('A').events(new Added(2));
86
- ((await scenario.instance)!.title === undefined).should.be.true;
86
+ (await scenario.instance)!.title.should.equal('');
87
87
  scenario.given.forEventSource('A').events(new Renamed());
88
88
  const result = (await scenario.instance)!;
89
89
  [result.total, result.title, result.label].should.deep.equal([3, 'automapped', 'explicit']);
@@ -84,7 +84,7 @@ describe('when evaluating arithmetic with standard model-bound decorators', () =
84
84
  it('should suppress aggregate-only AutoMap and restore it for mixed mappings', async () => {
85
85
  const scenario = new ReadModelScenario(Totals, artifacts);
86
86
  scenario.given.forEventSource('A').events(new Added(2));
87
- should((await scenario.instance)!.title === undefined).be.true;
87
+ should((await scenario.instance)!.title).equal('');
88
88
  scenario.given.forEventSource('A').events(new Renamed());
89
89
  const result = (await scenario.instance)!;
90
90
  should([result.total, result.title, result.label]).deep.equal([3, 'automapped', 'explicit']);
@@ -61,7 +61,7 @@ describe('when evaluating a fluent root join', () => {
61
61
  scenario.given.forEventSource('customer').events(new CustomerNamed('name'));
62
62
  (await scenario.instanceForEventSourceId('A'))!.customerName.should.equal('name');
63
63
  (await scenario.instanceForEventSourceId('B'))!.customerName.should.equal('name');
64
- ((await scenario.instanceForEventSourceId('C'))!.customerName === undefined).should.be.true;
64
+ (await scenario.instanceForEventSourceId('C'))!.customerName.should.equal('');
65
65
  });
66
66
 
67
67
  it('should retain joined history across removal without resurrecting the removed root', async () => {
@@ -95,7 +95,7 @@ describe('when evaluating a fluent root join', () => {
95
95
  (await first.instance)!.customerName.should.equal('second');
96
96
  const second = new ReadModelScenario(Order, artifacts);
97
97
  second.given.forEventSource('A').events(new OrderPlaced('customer'));
98
- ((await second.instance)!.customerName === undefined).should.be.true;
98
+ (await second.instance)!.customerName.should.equal('');
99
99
  });
100
100
 
101
101
  it('should update the root keys when observing a committed event sequence', async () => {
@@ -47,8 +47,8 @@ describe('when evaluating joins with legacy model-bound decorators', () => {
47
47
  const result = (await scenario.instance)!;
48
48
  result.customerName.should.equal('before');
49
49
  result.city.should.equal('Oslo');
50
- (result.name === undefined).should.be.true;
51
- (result.ignored === undefined).should.be.true;
50
+ result.name.should.equal('');
51
+ result.ignored.should.equal('');
52
52
  });
53
53
 
54
54
  it('should update existing roots and recreate a removed root from the latest joined history', async () => {
@@ -44,8 +44,8 @@ describe('when evaluating joins with standard model-bound decorators', () => {
44
44
  const result = (await scenario.instance)!;
45
45
  result.customerName.should.equal('before');
46
46
  result.city.should.equal('Oslo');
47
- (result.name === undefined).should.be.true;
48
- (result.ignored === undefined).should.be.true;
47
+ result.name.should.equal('');
48
+ result.ignored.should.equal('');
49
49
  });
50
50
 
51
51
  it('should fan out subsequent join events to all matching roots', async () => {
@@ -138,7 +138,9 @@ export class WebhookDefinitionBuilder implements IWebhookDefinitionBuilder {
138
138
  Headers: Object.fromEntries(this._headers)
139
139
  },
140
140
  IsReplayable: this._isReplayable,
141
- IsActive: this._isActive
141
+ IsActive: this._isActive,
142
+ IsNotReplayable: !this._isReplayable,
143
+ IsInactive: !this._isActive
142
144
  };
143
145
  }
144
146
  }
@@ -102,7 +102,9 @@ export class Webhooks implements IWebhooks {
102
102
  Headers: webhook.Headers
103
103
  },
104
104
  IsReplayable: webhook.IsReplayable,
105
- IsActive: webhook.IsActive
105
+ IsActive: webhook.IsActive,
106
+ IsNotReplayable: !webhook.IsReplayable,
107
+ IsInactive: !webhook.IsActive
106
108
  }));
107
109
  }
108
110