@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.
- package/dist/observation/FastForwardOutcome.d.ts +14 -0
- package/dist/observation/FastForwardOutcome.d.ts.map +1 -0
- package/dist/observation/FastForwardOutcome.js +17 -0
- package/dist/observation/FastForwardOutcome.js.map +1 -0
- package/dist/observation/FastForwardResult.d.ts +14 -0
- package/dist/observation/FastForwardResult.d.ts.map +1 -0
- package/dist/observation/FastForwardResult.js +4 -0
- package/dist/observation/FastForwardResult.js.map +1 -0
- package/dist/observation/IObservers.d.ts +9 -0
- package/dist/observation/IObservers.d.ts.map +1 -1
- package/dist/observation/Observers.d.ts +3 -0
- package/dist/observation/Observers.d.ts.map +1 -1
- package/dist/observation/Observers.js +11 -0
- package/dist/observation/Observers.js.map +1 -1
- package/dist/observation/index.d.ts +3 -0
- package/dist/observation/index.d.ts.map +1 -1
- package/dist/observation/index.js +2 -0
- package/dist/observation/index.js.map +1 -1
- package/dist/observation/toFastForwardResult.d.ts +16 -0
- package/dist/observation/toFastForwardResult.d.ts.map +1 -0
- package/dist/observation/toFastForwardResult.js +37 -0
- package/dist/observation/toFastForwardResult.js.map +1 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +3 -1
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/readModels/for_ReadModels/given/Book.d.ts +8 -0
- package/dist/readModels/for_ReadModels/given/Book.d.ts.map +1 -0
- package/dist/readModels/for_ReadModels/given/Book.js +25 -0
- package/dist/readModels/for_ReadModels/given/Book.js.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookBorrowed.d.ts +3 -0
- package/dist/readModels/for_ReadModels/given/BookBorrowed.d.ts.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookBorrowed.js +5 -0
- package/dist/readModels/for_ReadModels/given/BookBorrowed.js.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookRegistered.d.ts +5 -0
- package/dist/readModels/for_ReadModels/given/BookRegistered.d.ts.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookRegistered.js +7 -0
- package/dist/readModels/for_ReadModels/given/BookRegistered.js.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookReturned.d.ts +3 -0
- package/dist/readModels/for_ReadModels/given/BookReturned.d.ts.map +1 -0
- package/dist/readModels/for_ReadModels/given/BookReturned.js +5 -0
- package/dist/readModels/for_ReadModels/given/BookReturned.js.map +1 -0
- package/dist/readModels/for_deserializeReadModel/given/Address.d.ts +6 -0
- package/dist/readModels/for_deserializeReadModel/given/Address.d.ts.map +1 -0
- package/dist/readModels/for_deserializeReadModel/given/Address.js +11 -0
- package/dist/readModels/for_deserializeReadModel/given/Address.js.map +1 -0
- package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.d.ts +9 -0
- package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.d.ts.map +1 -0
- package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.js +16 -0
- package/dist/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.js.map +1 -0
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +2 -1
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webhooks/WebhookDefinitionBuilder.d.ts.map +1 -1
- package/dist/webhooks/WebhookDefinitionBuilder.js +3 -1
- package/dist/webhooks/WebhookDefinitionBuilder.js.map +1 -1
- package/dist/webhooks/Webhooks.d.ts.map +1 -1
- package/dist/webhooks/Webhooks.js +3 -1
- package/dist/webhooks/Webhooks.js.map +1 -1
- package/observation/FastForwardOutcome.ts +19 -0
- package/observation/FastForwardResult.ts +19 -0
- package/observation/IObservers.ts +10 -0
- package/observation/Observers.spec.ts +41 -4
- package/observation/Observers.ts +14 -0
- package/observation/index.ts +3 -0
- package/observation/toFastForwardResult.ts +40 -0
- package/package.json +4 -4
- package/projections/for_ProjectionDefinitionCompiler/against_a_kernel/when_subscribing_with_from_all_events.integration.spec.ts +3 -3
- package/reactors/Reactors.ts +3 -1
- package/readModels/for_ReadModels/given/Book.ts +26 -0
- package/readModels/for_ReadModels/given/BookBorrowed.ts +4 -0
- package/readModels/for_ReadModels/given/BookRegistered.ts +7 -0
- package/readModels/for_ReadModels/given/BookReturned.ts +4 -0
- package/readModels/for_ReadModels/when_reading_a_model_bound_projection/without_registration_fields.spec.ts +30 -0
- package/readModels/for_deserializeReadModel/given/Address.ts +12 -0
- package/readModels/for_deserializeReadModel/given/IdentityDefaults.fixture.ts +15 -0
- package/readModels/for_deserializeReadModel/given/SetterDefaults.fixture.ts +22 -0
- package/readModels/for_deserializeReadModel/given/a_read_model_with_defaults.ts +18 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_a_non_idempotent_setter_and_an_absent_sibling.spec.ts +20 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_a_partial_nested_object.spec.ts +31 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_absent_fields.spec.ts +22 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_explicit_null.spec.ts +20 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_identity_dependent_defaults.spec.ts +20 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_partial_array_items.spec.ts +23 -0
- package/readModels/for_deserializeReadModel/when_restoring/with_present_fields.spec.ts +19 -0
- package/reducers/Reducers.ts +2 -1
- package/testing/EventScenario.constraint-values.spec.ts +2 -2
- package/testing/for_ReadModelScenario/when_evaluating_arithmetic/with_legacy_decorators.legacy.spec.ts +1 -1
- package/testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts +1 -1
- package/testing/for_ReadModelScenario/when_evaluating_joins/with_a_fluent_projection.spec.ts +2 -2
- package/testing/for_ReadModelScenario/when_evaluating_joins/with_legacy_decorators.legacy.spec.ts +2 -2
- package/testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts +2 -2
- package/webhooks/WebhookDefinitionBuilder.ts +3 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
childOnlyAfterChild!.children[0].lastValue.should.equal('');
|
|
199
199
|
childOnlyAfterUnrelated!.rootValue.should.equal('child-value');
|
|
200
200
|
(unrelatedChildOnly === null).should.be.true;
|
|
201
201
|
});
|
package/reactors/Reactors.ts
CHANGED
|
@@ -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,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
|
+
});
|
package/readModels/for_deserializeReadModel/when_restoring/with_a_partial_nested_object.spec.ts
ADDED
|
@@ -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
|
+
});
|
package/readModels/for_deserializeReadModel/when_restoring/with_identity_dependent_defaults.spec.ts
ADDED
|
@@ -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
|
+
});
|
package/reducers/Reducers.ts
CHANGED
|
@@ -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
|
-
//
|
|
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.
|
|
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
|
-
(
|
|
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
|
|
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']);
|
package/testing/for_ReadModelScenario/when_evaluating_joins/with_a_fluent_projection.spec.ts
CHANGED
|
@@ -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
|
-
(
|
|
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
|
-
(
|
|
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 () => {
|
package/testing/for_ReadModelScenario/when_evaluating_joins/with_legacy_decorators.legacy.spec.ts
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
}
|
package/webhooks/Webhooks.ts
CHANGED
|
@@ -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
|
|