@cratis/chronicle 6.47.0 → 6.49.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.
- package/ChronicleClient.ts +3 -0
- package/EventStore.ts +10 -3
- package/IEventStore.ts +8 -0
- package/artifacts/DefaultClientArtifactsProvider.ts +5 -0
- package/artifacts/IClientArtifactsProvider.ts +3 -0
- package/connection/ChronicleConnection.ts +6 -0
- package/connection/ChronicleServices.ts +2 -0
- package/connection/ConnectionLifecycle.ts +22 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +3 -0
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/EventStore.d.ts +2 -0
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +9 -3
- package/dist/EventStore.js.map +1 -1
- package/dist/IEventStore.d.ts +7 -0
- package/dist/IEventStore.d.ts.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.d.ts +2 -0
- package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js +4 -0
- package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
- package/dist/artifacts/IClientArtifactsProvider.d.ts +2 -0
- package/dist/artifacts/IClientArtifactsProvider.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +1 -0
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +5 -1
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/ChronicleServices.d.ts +2 -1
- package/dist/connection/ChronicleServices.d.ts.map +1 -1
- package/dist/connection/ConnectionLifecycle.d.ts +8 -0
- package/dist/connection/ConnectionLifecycle.d.ts.map +1 -1
- package/dist/connection/ConnectionLifecycle.js +21 -0
- package/dist/connection/ConnectionLifecycle.js.map +1 -1
- package/dist/eventSequences/AppendOptions.d.ts +12 -0
- package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
- package/dist/eventSequences/EventForEventSourceId.d.ts +11 -0
- package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
- package/dist/eventSequences/EventLog.d.ts +2 -1
- package/dist/eventSequences/EventLog.d.ts.map +1 -1
- package/dist/eventSequences/EventLog.js +2 -2
- package/dist/eventSequences/EventLog.js.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts +4 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +32 -6
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventSequences/ITransactionalEventSequence.d.ts +5 -2
- package/dist/eventSequences/ITransactionalEventSequence.d.ts.map +1 -1
- package/dist/eventSequences/TransactionalEventSequence.d.ts +3 -2
- package/dist/eventSequences/TransactionalEventSequence.d.ts.map +1 -1
- package/dist/eventSequences/TransactionalEventSequence.js +4 -4
- package/dist/eventSequences/TransactionalEventSequence.js.map +1 -1
- package/dist/eventSequences/resolveEventSourceRouting.d.ts +39 -0
- package/dist/eventSequences/resolveEventSourceRouting.d.ts.map +1 -0
- package/dist/eventSequences/resolveEventSourceRouting.js +92 -0
- package/dist/eventSequences/resolveEventSourceRouting.js.map +1 -0
- package/dist/eventSources/ConcurrencyDimensions.d.ts +19 -0
- package/dist/eventSources/ConcurrencyDimensions.d.ts.map +1 -0
- package/dist/eventSources/ConcurrencyDimensions.js +19 -0
- package/dist/eventSources/ConcurrencyDimensions.js.map +1 -0
- package/dist/eventSources/DuplicateEventSourceName.d.ts +6 -0
- package/dist/eventSources/DuplicateEventSourceName.d.ts.map +1 -0
- package/dist/eventSources/DuplicateEventSourceName.js +12 -0
- package/dist/eventSources/DuplicateEventSourceName.js.map +1 -0
- package/dist/eventSources/DuplicateEventStreamName.d.ts +7 -0
- package/dist/eventSources/DuplicateEventStreamName.d.ts.map +1 -0
- package/dist/eventSources/DuplicateEventStreamName.js +14 -0
- package/dist/eventSources/DuplicateEventStreamName.js.map +1 -0
- package/dist/eventSources/EventRoutingContradictsEventSource.d.ts +9 -0
- package/dist/eventSources/EventRoutingContradictsEventSource.d.ts.map +1 -0
- package/dist/eventSources/EventRoutingContradictsEventSource.js +18 -0
- package/dist/eventSources/EventRoutingContradictsEventSource.js.map +1 -0
- package/dist/eventSources/EventSourceDefinition.d.ts +20 -0
- package/dist/eventSources/EventSourceDefinition.d.ts.map +1 -0
- package/dist/eventSources/EventSourceDefinition.js +4 -0
- package/dist/eventSources/EventSourceDefinition.js.map +1 -0
- package/dist/eventSources/EventSources.d.ts +31 -0
- package/dist/eventSources/EventSources.d.ts.map +1 -0
- package/dist/eventSources/EventSources.js +90 -0
- package/dist/eventSources/EventSources.js.map +1 -0
- package/dist/eventSources/EventStreamDefinition.d.ts +13 -0
- package/dist/eventSources/EventStreamDefinition.d.ts.map +1 -0
- package/dist/eventSources/EventStreamDefinition.js +4 -0
- package/dist/eventSources/EventStreamDefinition.js.map +1 -0
- package/dist/eventSources/EventStreamDoesNotBelongToEventSource.d.ts +7 -0
- package/dist/eventSources/EventStreamDoesNotBelongToEventSource.d.ts.map +1 -0
- package/dist/eventSources/EventStreamDoesNotBelongToEventSource.js +14 -0
- package/dist/eventSources/EventStreamDoesNotBelongToEventSource.js.map +1 -0
- package/dist/eventSources/EventStreamRequiresEventSource.d.ts +6 -0
- package/dist/eventSources/EventStreamRequiresEventSource.d.ts.map +1 -0
- package/dist/eventSources/EventStreamRequiresEventSource.js +12 -0
- package/dist/eventSources/EventStreamRequiresEventSource.js.map +1 -0
- package/dist/eventSources/IEventSources.d.ts +27 -0
- package/dist/eventSources/IEventSources.d.ts.map +1 -0
- package/dist/eventSources/IEventSources.js +4 -0
- package/dist/eventSources/IEventSources.js.map +1 -0
- package/dist/eventSources/ResolvedEventRouting.d.ts +37 -0
- package/dist/eventSources/ResolvedEventRouting.d.ts.map +1 -0
- package/dist/eventSources/ResolvedEventRouting.js +60 -0
- package/dist/eventSources/ResolvedEventRouting.js.map +1 -0
- package/dist/eventSources/UnknownEventSource.d.ts +6 -0
- package/dist/eventSources/UnknownEventSource.d.ts.map +1 -0
- package/dist/eventSources/UnknownEventSource.js +12 -0
- package/dist/eventSources/UnknownEventSource.js.map +1 -0
- package/dist/eventSources/eventSource.d.ts +60 -0
- package/dist/eventSources/eventSource.d.ts.map +1 -0
- package/dist/eventSources/eventSource.js +72 -0
- package/dist/eventSources/eventSource.js.map +1 -0
- package/dist/eventSources/index.d.ts +17 -0
- package/dist/eventSources/index.d.ts.map +1 -0
- package/dist/eventSources/index.js +13 -0
- package/dist/eventSources/index.js.map +1 -0
- package/dist/events/EventContext.d.ts +5 -0
- package/dist/events/EventContext.d.ts.map +1 -1
- package/dist/events/WireEventContext.d.ts +2 -0
- package/dist/events/WireEventContext.d.ts.map +1 -1
- package/dist/events/toClientEventContext.d.ts.map +1 -1
- package/dist/events/toClientEventContext.js +1 -0
- package/dist/events/toClientEventContext.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/projections/eventContextPropertyExpression.d.ts.map +1 -1
- package/dist/projections/eventContextPropertyExpression.js +1 -0
- package/dist/projections/eventContextPropertyExpression.js.map +1 -1
- package/dist/readModels/IReadModelWatcher.d.ts +30 -0
- package/dist/readModels/IReadModelWatcher.d.ts.map +1 -0
- package/dist/readModels/IReadModelWatcher.js +4 -0
- package/dist/readModels/IReadModelWatcher.js.map +1 -0
- package/dist/readModels/IReadModels.d.ts +16 -2
- package/dist/readModels/IReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModelChangeContext.d.ts +9 -0
- package/dist/readModels/ReadModelChangeContext.d.ts.map +1 -0
- package/dist/readModels/ReadModelChangeContext.js +4 -0
- package/dist/readModels/ReadModelChangeContext.js.map +1 -0
- package/dist/readModels/ReadModelChangeType.d.ts +7 -0
- package/dist/readModels/ReadModelChangeType.d.ts.map +1 -0
- package/dist/readModels/ReadModelChangeType.js +10 -0
- package/dist/readModels/ReadModelChangeType.js.map +1 -0
- package/dist/readModels/ReadModelChangeset.d.ts +6 -0
- package/dist/readModels/ReadModelChangeset.d.ts.map +1 -1
- package/dist/readModels/ReadModelWatchOptions.d.ts +19 -0
- package/dist/readModels/ReadModelWatchOptions.d.ts.map +1 -0
- package/dist/readModels/ReadModelWatchOptions.js +4 -0
- package/dist/readModels/ReadModelWatchOptions.js.map +1 -0
- package/dist/readModels/ReadModelWatcher.d.ts +52 -0
- package/dist/readModels/ReadModelWatcher.d.ts.map +1 -0
- package/dist/readModels/ReadModelWatcher.js +276 -0
- package/dist/readModels/ReadModelWatcher.js.map +1 -0
- package/dist/readModels/ReadModels.d.ts +11 -1
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +47 -11
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/for_ReadModelWatcher/given/a_kernel_stream.d.ts +15 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_kernel_stream.d.ts.map +1 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_kernel_stream.js +45 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_kernel_stream.js.map +1 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_watched_read_model.d.ts +21 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_watched_read_model.d.ts.map +1 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_watched_read_model.js +35 -0
- package/dist/readModels/for_ReadModelWatcher/given/a_watched_read_model.js.map +1 -0
- package/dist/readModels/index.d.ts +4 -0
- package/dist/readModels/index.d.ts.map +1 -1
- package/dist/readModels/index.js +1 -0
- package/dist/readModels/index.js.map +1 -1
- package/dist/testing/ReactorScenario.d.ts.map +1 -1
- package/dist/testing/ReactorScenario.js +2 -0
- package/dist/testing/ReactorScenario.js.map +1 -1
- package/dist/testing/projections/ProjectionReadModelProcessor.d.ts.map +1 -1
- package/dist/testing/projections/ProjectionReadModelProcessor.js +2 -1
- package/dist/testing/projections/ProjectionReadModelProcessor.js.map +1 -1
- package/dist/transactions/IUnitOfWork.d.ts +3 -1
- package/dist/transactions/IUnitOfWork.d.ts.map +1 -1
- package/dist/transactions/TransactionalEventRouting.d.ts +12 -0
- package/dist/transactions/TransactionalEventRouting.d.ts.map +1 -0
- package/dist/transactions/TransactionalEventRouting.js +4 -0
- package/dist/transactions/TransactionalEventRouting.js.map +1 -0
- package/dist/transactions/UnitOfWork.d.ts +2 -1
- package/dist/transactions/UnitOfWork.d.ts.map +1 -1
- package/dist/transactions/UnitOfWork.js +11 -2
- package/dist/transactions/UnitOfWork.js.map +1 -1
- package/dist/transactions/index.d.ts +1 -0
- package/dist/transactions/index.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/DecoratorType.d.ts +3 -1
- package/dist/types/DecoratorType.d.ts.map +1 -1
- package/dist/types/DecoratorType.js +2 -0
- package/dist/types/DecoratorType.js.map +1 -1
- package/eventSequences/AppendOptions.ts +14 -0
- package/eventSequences/EventForEventSourceId.ts +13 -0
- package/eventSequences/EventLog.ts +4 -2
- package/eventSequences/EventSequence.ts +33 -6
- package/eventSequences/ITransactionalEventSequence.ts +5 -2
- package/eventSequences/TransactionalEventSequence.ts +5 -4
- package/eventSequences/resolveEventSourceRouting.ts +125 -0
- package/eventSources/ConcurrencyDimensions.ts +26 -0
- package/eventSources/DuplicateEventSourceName.ts +10 -0
- package/eventSources/DuplicateEventStreamName.ts +10 -0
- package/eventSources/EventRoutingContradictsEventSource.ts +10 -0
- package/eventSources/EventSourceDefinition.ts +27 -0
- package/eventSources/EventSources.ts +93 -0
- package/eventSources/EventStreamDefinition.ts +18 -0
- package/eventSources/EventStreamDoesNotBelongToEventSource.ts +10 -0
- package/eventSources/EventStreamRequiresEventSource.ts +10 -0
- package/eventSources/IEventSources.ts +33 -0
- package/eventSources/ResolvedEventRouting.ts +78 -0
- package/eventSources/UnknownEventSource.ts +10 -0
- package/eventSources/eventSource.ts +116 -0
- package/eventSources/for_EventSources/against_a_kernel/when_appending_through_registered_sources.integration.spec.ts +106 -0
- package/eventSources/for_EventSources/when_appending_through_a_definition.spec.ts +190 -0
- package/eventSources/for_EventSources/when_batch_requires_differing_guards.spec.ts +139 -0
- package/eventSources/for_EventSources/when_discovering_and_registering.spec.ts +94 -0
- package/eventSources/index.ts +19 -0
- package/events/EventContext.ts +6 -0
- package/events/WireEventContext.ts +2 -0
- package/events/for_EventContext/with_event_source.spec.ts +22 -0
- package/events/toClientEventContext.ts +1 -0
- package/for_ChronicleClient/when_reconnecting/with_waiting_read_model_watchers.spec.ts +117 -0
- package/for_EventStore/when_implementing_a_legacy_event_store.spec.ts +19 -0
- package/index.ts +3 -0
- package/jobs/for_Jobs/against_a_kernel/when_managing_a_replay_job.integration.spec.ts +24 -4
- package/package.json +3 -3
- package/projections/eventContextPropertyExpression.ts +2 -1
- package/reactors/for_Reactors/against_a_kernel/when_replaying_reactors_with_once_only_handlers.integration.spec.ts +27 -8
- package/readModels/IReadModelWatcher.ts +35 -0
- package/readModels/IReadModels.ts +17 -2
- package/readModels/ReadModelChangeContext.ts +11 -0
- package/readModels/ReadModelChangeType.ts +9 -0
- package/readModels/ReadModelChangeset.ts +9 -0
- package/readModels/ReadModelWatchOptions.ts +23 -0
- package/readModels/ReadModelWatcher.ts +271 -0
- package/readModels/ReadModels.ts +60 -11
- package/readModels/for_ReadModelWatcher/against_a_kernel/when_watching_changes.integration.spec.ts +104 -0
- package/readModels/for_ReadModelWatcher/given/a_kernel_stream.ts +50 -0
- package/readModels/for_ReadModelWatcher/given/a_watched_read_model.ts +37 -0
- package/readModels/for_ReadModelWatcher/watchCompatibility.standard.spec.ts +29 -0
- package/readModels/for_ReadModelWatcher/when_a_stream_cannot_recover.spec.ts +86 -0
- package/readModels/for_ReadModelWatcher/when_applying_backpressure.spec.ts +80 -0
- package/readModels/for_ReadModelWatcher/when_failing_with_buffered_changes.spec.ts +39 -0
- package/readModels/for_ReadModelWatcher/when_not_opting_into_resume.spec.ts +62 -0
- package/readModels/for_ReadModelWatcher/when_notifying_resubscription.spec.ts +128 -0
- package/readModels/for_ReadModelWatcher/when_opting_into_overflow.spec.ts +39 -0
- package/readModels/for_ReadModelWatcher/when_receiving_changes.spec.ts +69 -0
- package/readModels/for_ReadModelWatcher/when_reconnecting.spec.ts +99 -0
- package/readModels/for_ReadModelWatcher/when_reconnecting_during_conversion.spec.ts +84 -0
- package/readModels/for_ReadModelWatcher/when_recovering_a_stream.spec.ts +106 -0
- package/readModels/for_ReadModelWatcher/when_stopping.spec.ts +85 -0
- package/readModels/for_ReadModelWatcher/when_subscribing.spec.ts +62 -0
- package/readModels/for_ReadModels/when_watching.spec.ts +85 -0
- package/readModels/index.ts +4 -0
- package/testing/EventScenario.event-sources.spec.ts +34 -0
- package/testing/ReactorScenario.spec.ts +18 -1
- package/testing/ReactorScenario.ts +1 -0
- package/testing/projections/ProjectionReadModelProcessor.ts +2 -1
- package/transactions/IUnitOfWork.ts +3 -1
- package/transactions/TransactionalEventRouting.ts +16 -0
- package/transactions/UnitOfWork.ts +13 -2
- package/transactions/for_UnitOfWork/when_committing_events_with_event_source_routing.spec.ts +41 -0
- package/transactions/index.ts +1 -0
- package/types/DecoratorType.ts +4 -1
|
@@ -0,0 +1,117 @@
|
|
|
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 { afterEach, beforeEach, chai, describe, it, vi } from 'vitest';
|
|
5
|
+
import { ChronicleClient } from '../../ChronicleClient.js';
|
|
6
|
+
import { ChronicleOptions } from '../../ChronicleOptions.js';
|
|
7
|
+
import { EventStore } from '../../EventStore.js';
|
|
8
|
+
import type { IEventStore } from '../../IEventStore.js';
|
|
9
|
+
import { IncompatibleChronicleServer } from '../../connection/IncompatibleChronicleServer.js';
|
|
10
|
+
import { RejectedChronicleCredentials } from '../../connection/RejectedChronicleCredentials.js';
|
|
11
|
+
import { projection } from '../../projections/declarative/projection.js';
|
|
12
|
+
import type { IReadModelWatcher } from '../../readModels/IReadModelWatcher.js';
|
|
13
|
+
import { a_kernel_stream } from '../../readModels/for_ReadModelWatcher/given/a_kernel_stream.js';
|
|
14
|
+
|
|
15
|
+
chai.should();
|
|
16
|
+
|
|
17
|
+
const transport = vi.hoisted(() => ({
|
|
18
|
+
resetChannel: vi.fn(), connect: vi.fn(), disconnect: vi.fn(), getVersionInfo: vi.fn(),
|
|
19
|
+
ensureEventStore: vi.fn(), watch: vi.fn(),
|
|
20
|
+
lost: undefined as ((reason: string, error: unknown) => void) | undefined
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('../../connection/ChronicleConnection', () => ({
|
|
23
|
+
ChronicleConnection: class {
|
|
24
|
+
resetChannel = transport.resetChannel;
|
|
25
|
+
connect = transport.connect;
|
|
26
|
+
disconnect = transport.disconnect;
|
|
27
|
+
server = { getVersionInfo: transport.getVersionInfo };
|
|
28
|
+
eventStores = { ensureEventStore: transport.ensureEventStore };
|
|
29
|
+
readModels = { watch: transport.watch };
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
vi.mock('../../connection/KernelKeepAlive', () => ({
|
|
33
|
+
KernelKeepAlive: class {
|
|
34
|
+
constructor(_connections: unknown, onLost: (reason: string, error: unknown) => void) { transport.lost = onLost; }
|
|
35
|
+
async start() {}
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
class Model { id = ''; }
|
|
40
|
+
class ModelProjection { define() {} }
|
|
41
|
+
projection('model', Model)(ModelProjection);
|
|
42
|
+
|
|
43
|
+
describe('when reconnecting with waiting read model watchers', () => {
|
|
44
|
+
let client: ChronicleClient;
|
|
45
|
+
let store: IEventStore;
|
|
46
|
+
let watcher: IReadModelWatcher<Model>;
|
|
47
|
+
let stream: a_kernel_stream;
|
|
48
|
+
let rejectReconnect: (error: Error) => void;
|
|
49
|
+
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
vi.clearAllMocks();
|
|
53
|
+
// Registration is outside this connection-lifetime specification; retain the real store and watchers.
|
|
54
|
+
vi.spyOn(EventStore.prototype, 'registerArtifacts').mockResolvedValue(undefined);
|
|
55
|
+
transport.resetChannel.mockResolvedValue(undefined);
|
|
56
|
+
transport.connect.mockResolvedValue(undefined);
|
|
57
|
+
transport.getVersionInfo.mockResolvedValue({});
|
|
58
|
+
transport.ensureEventStore.mockResolvedValue({ IsSuccess: true, IsAuthorized: true });
|
|
59
|
+
stream = new a_kernel_stream();
|
|
60
|
+
transport.watch.mockImplementation((_request, options: { signal: AbortSignal }) => {
|
|
61
|
+
stream.signal = options.signal;
|
|
62
|
+
options.signal.addEventListener('abort', () => stream.end(), { once: true });
|
|
63
|
+
return stream;
|
|
64
|
+
});
|
|
65
|
+
client = new ChronicleClient(ChronicleOptions.fromConnectionString('chronicle://localhost:35000', {
|
|
66
|
+
discoveryPatterns: [],
|
|
67
|
+
clientArtifactsProvider: {
|
|
68
|
+
readModels: [Model], projections: [ModelProjection], eventTypes: [], reactors: [], reducers: [],
|
|
69
|
+
seeders: [], constraints: [], webhooks: [], eventTypeMigrations: [], globalForHandlers: []
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
store = await client.getEventStore('store');
|
|
73
|
+
watcher = store.readModels.createWatcher(Model, { resume: true });
|
|
74
|
+
stream.send({ Subscribed: true });
|
|
75
|
+
await watcher.subscribed;
|
|
76
|
+
transport.connect.mockImplementation(() => new Promise<void>((_resolve, reject) => { rejectReconnect = reject; }));
|
|
77
|
+
transport.lost!('keep-alive-ended', { code: 14 });
|
|
78
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
79
|
+
stream.signal.aborted.should.be.true;
|
|
80
|
+
});
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
client.dispose();
|
|
83
|
+
vi.restoreAllMocks();
|
|
84
|
+
vi.useRealTimers();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it.each([
|
|
88
|
+
['incompatible server', IncompatibleChronicleServer],
|
|
89
|
+
['rejected credentials', RejectedChronicleCredentials]
|
|
90
|
+
] as const)('should reject readiness and iteration on terminal %s', async (_reason, Failure) => {
|
|
91
|
+
const failure = new Failure('Replacement connection rejected');
|
|
92
|
+
const readiness = watcher.subscribed.catch(error => error);
|
|
93
|
+
const next = watcher[Symbol.asyncIterator]().next().catch(error => error);
|
|
94
|
+
rejectReconnect(failure);
|
|
95
|
+
(await readiness).should.equal(failure);
|
|
96
|
+
(await next).should.equal(failure);
|
|
97
|
+
await vi.advanceTimersByTimeAsync(15000);
|
|
98
|
+
transport.watch.mock.calls.should.have.lengthOf(1);
|
|
99
|
+
transport.connect.mock.calls.should.have.lengthOf(2);
|
|
100
|
+
// A cached store cannot create a watcher that waits forever after the terminal verdict.
|
|
101
|
+
const later = store.readModels.createWatcher(Model);
|
|
102
|
+
(await later.subscribed.catch(error => error)).should.equal(failure);
|
|
103
|
+
(await later[Symbol.asyncIterator]().next().catch(error => error)).should.equal(failure);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should reject pending readiness but complete iteration cleanly if disposed during recovery', async () => {
|
|
107
|
+
const readiness = watcher.subscribed.catch(error => error);
|
|
108
|
+
const next = watcher[Symbol.asyncIterator]().next();
|
|
109
|
+
client.dispose();
|
|
110
|
+
(await readiness).name.should.equal('AbortError');
|
|
111
|
+
(await next).done!.should.be.true;
|
|
112
|
+
rejectReconnect(new IncompatibleChronicleServer('Late reconnect failure'));
|
|
113
|
+
await vi.advanceTimersByTimeAsync(15000);
|
|
114
|
+
(await watcher[Symbol.asyncIterator]().next()).done!.should.be.true;
|
|
115
|
+
transport.watch.mock.calls.should.have.lengthOf(1);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -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 { chai, describe, it } from 'vitest';
|
|
5
|
+
import type { IEventStore } from '../IEventStore.js';
|
|
6
|
+
|
|
7
|
+
const should = chai.should();
|
|
8
|
+
|
|
9
|
+
/** An implementation written before event sources existed: it has no `eventSources` member. */
|
|
10
|
+
type LegacyEventStore = Omit<IEventStore, 'eventSources'>;
|
|
11
|
+
|
|
12
|
+
describe('when implementing an event store written before event sources', () => {
|
|
13
|
+
it('should still satisfy the IEventStore contract', () => {
|
|
14
|
+
// Compile-time regression: assigning would fail type-checking if `eventSources` became required.
|
|
15
|
+
const legacy = {} as LegacyEventStore;
|
|
16
|
+
const store: IEventStore = legacy;
|
|
17
|
+
should.not.exist(store.eventSources);
|
|
18
|
+
});
|
|
19
|
+
});
|
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import * as events from './events/index.js';
|
|
5
5
|
import * as eventSequences from './eventSequences/index.js';
|
|
6
|
+
import * as eventSources from './eventSources/index.js';
|
|
6
7
|
import * as reactors from './reactors/index.js';
|
|
7
8
|
import * as reducers from './reducers/index.js';
|
|
8
9
|
import * as seeding from './seeding/index.js';
|
|
@@ -48,6 +49,7 @@ export { DiagChronicleLogger } from './logging/DiagChronicleLogger.js';
|
|
|
48
49
|
|
|
49
50
|
export * from './events/index.js';
|
|
50
51
|
export * from './eventSequences/index.js';
|
|
52
|
+
export * from './eventSources/index.js';
|
|
51
53
|
export * from './reactors/index.js';
|
|
52
54
|
export * from './reducers/index.js';
|
|
53
55
|
export * from './seeding/index.js';
|
|
@@ -74,6 +76,7 @@ export * from './confidentiality/index.js';
|
|
|
74
76
|
export {
|
|
75
77
|
events,
|
|
76
78
|
eventSequences,
|
|
79
|
+
eventSources,
|
|
77
80
|
reactors,
|
|
78
81
|
reducers,
|
|
79
82
|
seeding,
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { afterAll, beforeAll, chai, describe, it } from 'vitest';
|
|
6
|
-
import { JobStatus, JobStepStatus, type JobStepSummaryResponse, type JobSummaryResponse } from '@cratis/chronicle.contracts';
|
|
6
|
+
import { JobStatus, JobStepStatus, ObserverRunningState, type JobStepSummaryResponse, type JobSummaryResponse } from '@cratis/chronicle.contracts';
|
|
7
7
|
import { field, Guid } from '@cratis/fundamentals';
|
|
8
8
|
import { ChronicleClient } from '../../../ChronicleClient.js';
|
|
9
9
|
import { ChronicleOptions } from '../../../ChronicleOptions.js';
|
|
10
10
|
import { fromContractsGuid } from '../../../connection/Guid.js';
|
|
11
11
|
import type { IClientArtifactsProvider } from '../../../artifacts/index.js';
|
|
12
|
+
import type { ChronicleConnection } from '../../../connection/index.js';
|
|
12
13
|
import { eventType } from '../../../events/index.js';
|
|
13
14
|
import type { IEventStore } from '../../../IEventStore.js';
|
|
14
15
|
import { fromEvent } from '../../../projections/index.js';
|
|
@@ -55,8 +56,10 @@ async function eventually<T>(read: () => Promise<T>, accept: (value: T) => boole
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
describe.skipIf(!connectionString && !process.env.CI)('when managing a replay job against a kernel', () => {
|
|
59
|
+
const storeName = `Jobs${randomUUID().replaceAll('-', '').slice(0, 12)}`;
|
|
58
60
|
let client: ChronicleClient;
|
|
59
61
|
let store: IEventStore;
|
|
62
|
+
let connection: ChronicleConnection;
|
|
60
63
|
let jobId: JobId;
|
|
61
64
|
let listed: JobSummaryResponse[];
|
|
62
65
|
let stopped: JobSummaryResponse | undefined;
|
|
@@ -65,19 +68,36 @@ describe.skipIf(!connectionString && !process.env.CI)('when managing a replay jo
|
|
|
65
68
|
let stopUnknownJob: unknown;
|
|
66
69
|
let deleteUnknownJob: unknown;
|
|
67
70
|
|
|
71
|
+
const waitForActiveObserver = (lastHandledSequenceNumber?: bigint) => eventually(
|
|
72
|
+
() => connection.observers.getObserverInformation({
|
|
73
|
+
EventStore: storeName,
|
|
74
|
+
Namespace: 'Default',
|
|
75
|
+
ObserverId: 'Item',
|
|
76
|
+
EventSequenceId: 'event-log'
|
|
77
|
+
}),
|
|
78
|
+
observer => observer.IsSubscribed && observer.RunningState === ObserverRunningState.Active &&
|
|
79
|
+
(lastHandledSequenceNumber === undefined || observer.LastHandledEventSequenceNumber === lastHandledSequenceNumber),
|
|
80
|
+
15_000);
|
|
81
|
+
|
|
82
|
+
// Readiness (30s) and the existing job-state waits (90s) leave 30s for RPC overhead.
|
|
68
83
|
beforeAll(async () => {
|
|
69
84
|
client = new ChronicleClient(ChronicleOptions.fromConnectionString(connectionString!, {
|
|
70
85
|
discoveryPatterns: [],
|
|
71
86
|
clientArtifactsProvider: artifacts
|
|
72
87
|
}));
|
|
73
|
-
store = await client.getEventStore(
|
|
88
|
+
store = await client.getEventStore(storeName);
|
|
89
|
+
connection = (store as unknown as { _connection: ChronicleConnection })._connection;
|
|
74
90
|
|
|
91
|
+
// Append only after initial catch-up completes, so it cannot race the explicit replay.
|
|
92
|
+
await waitForActiveObserver();
|
|
75
93
|
// Enough events that the replay is still running when it is stopped; a stopped job stays until deleted,
|
|
76
94
|
// where a completed one is removed and could not be listed reliably.
|
|
77
|
-
await store.eventLog.appendMany(Array.from({ length: 500 }, (_, index) => ({
|
|
95
|
+
const appended = await store.eventLog.appendMany(Array.from({ length: 500 }, (_, index) => ({
|
|
78
96
|
eventSourceId: randomUUID(),
|
|
79
97
|
event: Object.assign(new ItemAdded(), { name: `Item ${index}` })
|
|
80
98
|
})));
|
|
99
|
+
// ReplayForModel uses Observer.Replay too; wait for acknowledged progress before requesting it.
|
|
100
|
+
await waitForActiveObserver(appended.at(-1)!.sequenceNumber.value);
|
|
81
101
|
|
|
82
102
|
jobId = await store.projections.replayForModel(Item);
|
|
83
103
|
|
|
@@ -98,7 +118,7 @@ describe.skipIf(!connectionString && !process.env.CI)('when managing a replay jo
|
|
|
98
118
|
|
|
99
119
|
stopUnknownJob = await store.jobs.stop(randomUUID()).catch((error: unknown) => error);
|
|
100
120
|
deleteUnknownJob = await store.jobs.delete(randomUUID()).catch((error: unknown) => error);
|
|
101
|
-
});
|
|
121
|
+
}, 150_000);
|
|
102
122
|
|
|
103
123
|
afterAll(() => client?.dispose());
|
|
104
124
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/chronicle",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.49.0",
|
|
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",
|
|
@@ -173,12 +173,12 @@
|
|
|
173
173
|
"clean": "rm -rf dist",
|
|
174
174
|
"test": "vitest run",
|
|
175
175
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
176
|
-
"test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/declarative/for_CompositeKeyBuilder/when_typing_constant_parts.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts events/for_handles/when_decorating_a_method.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_field_types/with_standard_decorators.standard.spec.ts eventSequences/for_EventSequence/when_appending/with_subject_annotations.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_tags/with_standard_decorators.standard.spec.ts telemetry/for_ChronicleTelemetry/when_exposing_names/with_convention_names.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts events/for_observerFilters/when_registering/with_standard_decorators.standard.spec.ts reactors/for_Reactors/against_a_kernel/when_filtering_source_and_stream_types.integration.spec.ts testing/for_observerScenarios/when_selecting_filters/with_source_and_stream_types.spec.ts",
|
|
176
|
+
"test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/declarative/for_CompositeKeyBuilder/when_typing_constant_parts.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts events/for_handles/when_decorating_a_method.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_field_types/with_standard_decorators.standard.spec.ts eventSequences/for_EventSequence/when_appending/with_subject_annotations.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_tags/with_standard_decorators.standard.spec.ts telemetry/for_ChronicleTelemetry/when_exposing_names/with_convention_names.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts events/for_observerFilters/when_registering/with_standard_decorators.standard.spec.ts reactors/for_Reactors/against_a_kernel/when_filtering_source_and_stream_types.integration.spec.ts testing/for_observerScenarios/when_selecting_filters/with_source_and_stream_types.spec.ts readModels/for_ReadModelWatcher/watchCompatibility.standard.spec.ts for_EventStore/when_implementing_a_legacy_event_store.spec.ts",
|
|
177
177
|
"test:native": "node ./scripts/check-native-esm.mjs"
|
|
178
178
|
},
|
|
179
179
|
"dependencies": {
|
|
180
180
|
"@bufbuild/protobuf": "^2.16.0",
|
|
181
|
-
"@cratis/chronicle.contracts": "19.
|
|
181
|
+
"@cratis/chronicle.contracts": "19.30.0",
|
|
182
182
|
"@grpc/grpc-js": "^1.14.5",
|
|
183
183
|
"@opentelemetry/api": "^1.9.1",
|
|
184
184
|
"glob": "^13.0.6",
|
|
@@ -23,7 +23,8 @@ const contextProperties = {
|
|
|
23
23
|
correlationId: true,
|
|
24
24
|
causation: true,
|
|
25
25
|
tags: true
|
|
26
|
-
|
|
26
|
+
// eventSource is deliberately not projectable: its kernel resolution is not part of the supported expression set.
|
|
27
|
+
} satisfies Record<Exclude<keyof EventContext, 'eventSource'>, true>;
|
|
27
28
|
|
|
28
29
|
// Mirror the kernel's DerivedPropertyFunctions registry: Week() applies to dates only.
|
|
29
30
|
const derivedFunctions: Record<string, string> = { week: 'Week' };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { afterAll, beforeAll, chai, describe, it } from 'vitest';
|
|
6
|
+
import { ObserverRunningState } from '@cratis/chronicle.contracts';
|
|
6
7
|
import { field } from '@cratis/fundamentals';
|
|
7
8
|
import { ChronicleClient } from '../../../ChronicleClient.js';
|
|
8
9
|
import { ChronicleOptions } from '../../../ChronicleOptions.js';
|
|
@@ -65,12 +66,11 @@ const artifacts: IClientArtifactsProvider = {
|
|
|
65
66
|
globalForHandlers: []
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
async function eventually(accept: () => boolean, timeoutMs = 45_000): Promise<void> {
|
|
69
|
+
// Keep the existing 45s replay polling budget for concurrent kernel load in CI.
|
|
70
|
+
// Initial observation is gated separately by the bounded readiness waits below.
|
|
71
|
+
async function eventually(accept: () => boolean | Promise<boolean>, timeoutMs = 45_000): Promise<void> {
|
|
72
72
|
const deadline = Date.now() + timeoutMs;
|
|
73
|
-
while (!accept()) {
|
|
73
|
+
while (!await accept()) {
|
|
74
74
|
if (Date.now() > deadline) throw new Error('Timed out waiting for the kernel');
|
|
75
75
|
await new Promise(resolve => setTimeout(resolve, 250));
|
|
76
76
|
}
|
|
@@ -86,6 +86,17 @@ describe.skipIf(!connectionString && !process.env.CI)('when replaying a reactor
|
|
|
86
86
|
let jobId: string | undefined;
|
|
87
87
|
let invocationsBeforeReplay: string[];
|
|
88
88
|
|
|
89
|
+
const waitForActiveObserver = (lastHandledSequenceNumber?: bigint, timeoutMs = 15_000) => eventually(async () => {
|
|
90
|
+
const observer = await connection.observers.getObserverInformation({
|
|
91
|
+
EventStore: storeName,
|
|
92
|
+
Namespace: 'Default',
|
|
93
|
+
ObserverId: 'MixedOnceOnlyReactor',
|
|
94
|
+
EventSequenceId: 'event-log'
|
|
95
|
+
});
|
|
96
|
+
return observer.IsSubscribed && observer.RunningState === ObserverRunningState.Active &&
|
|
97
|
+
(lastHandledSequenceNumber === undefined || observer.LastHandledEventSequenceNumber === lastHandledSequenceNumber);
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
|
|
89
100
|
beforeAll(async () => {
|
|
90
101
|
client = new ChronicleClient(ChronicleOptions.fromConnectionString(connectionString!, {
|
|
91
102
|
discoveryPatterns: [],
|
|
@@ -94,10 +105,18 @@ describe.skipIf(!connectionString && !process.env.CI)('when replaying a reactor
|
|
|
94
105
|
store = await client.getEventStore(storeName);
|
|
95
106
|
connection = (store as unknown as { _connection: ChronicleConnection })._connection;
|
|
96
107
|
|
|
108
|
+
// Registration starts observation in the background. Append only after the subscription
|
|
109
|
+
// is active, so initial catch-up cannot race the explicit replay's state transition.
|
|
110
|
+
// Initial registration and subscription took over 30s under concurrent CI kernel load before.
|
|
111
|
+
await waitForActiveObserver(undefined, 30_000);
|
|
97
112
|
(await store.eventLog.append(randomUUID(), Object.assign(new MethodOnceOnlyHappened(), { name: 'a' }))).isSuccess.should.be.true;
|
|
98
113
|
(await store.eventLog.append(randomUUID(), Object.assign(new MethodReplayableHappened(), { name: 'b' }))).isSuccess.should.be.true;
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
const appended = await store.eventLog.append(randomUUID(), Object.assign(new MethodReplayOnceOnlyHappened(), { name: 'c' }));
|
|
115
|
+
appended.isSuccess.should.be.true;
|
|
116
|
+
// Worst case 30s + 30s + 15s + 45s + 3s stays inside the explicit 150s hook timeout below.
|
|
117
|
+
await eventually(() => invocations.length === 3, 30_000);
|
|
118
|
+
// The handlers update their sink before the kernel acknowledges delivery and records progress.
|
|
119
|
+
await waitForActiveObserver(appended.sequenceNumber.value);
|
|
101
120
|
invocationsBeforeReplay = [...invocations];
|
|
102
121
|
|
|
103
122
|
jobId = (await connection.observers.replay({
|
|
@@ -108,7 +127,7 @@ describe.skipIf(!connectionString && !process.env.CI)('when replaying a reactor
|
|
|
108
127
|
})).JobId;
|
|
109
128
|
await eventually(() => invocations.includes('replayable:b') && invocations.filter(_ => _ === 'replayable:b').length === 2);
|
|
110
129
|
await new Promise(resolve => setTimeout(resolve, 3_000));
|
|
111
|
-
});
|
|
130
|
+
}, 150_000);
|
|
112
131
|
|
|
113
132
|
afterAll(() => client?.dispose());
|
|
114
133
|
|
|
@@ -0,0 +1,35 @@
|
|
|
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 type { ReadModelChangeset } from './ReadModelChangeset.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A single-consumer stream of read model changes, started when createWatcher() is called.
|
|
8
|
+
* Terminal stream or connection failures retain buffered changes: iteration drains them
|
|
9
|
+
* before rejecting with the original error. Overflow and disposal discard buffered changes.
|
|
10
|
+
*/
|
|
11
|
+
export interface IReadModelWatcher<TReadModel> extends AsyncIterable<ReadModelChangeset<TReadModel>> {
|
|
12
|
+
/**
|
|
13
|
+
* Resolves on the kernel's subscription acknowledgment, without requiring iteration.
|
|
14
|
+
* When resumption is opted into, read this property again after disconnect for the next subscription.
|
|
15
|
+
* An already pending promise carries across lifecycle reconnects; a previously resolved
|
|
16
|
+
* promise cannot be revoked. Rejects if the watcher stops before acknowledgment.
|
|
17
|
+
*/
|
|
18
|
+
readonly subscribed: Promise<void>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registers a callback after each subscription acknowledgment following the first.
|
|
22
|
+
* Having at least one registered callback opts into resumption on transport failures and
|
|
23
|
+
* completion. Without callbacks or the resume option, transport errors reject iteration.
|
|
24
|
+
* Refresh your query here: changes during an outage are not replayed. Register before
|
|
25
|
+
* awaiting subscribed; callbacks are not replayed for late registrations.
|
|
26
|
+
* Callbacks run in registration order and are awaited before reading more changes on
|
|
27
|
+
* the resumed stream. Do not wait for iteration inside a callback. A thrown error or
|
|
28
|
+
* rejected promise fails iteration. Reconnects retain already received changes.
|
|
29
|
+
* @returns An idempotent function that unregisters the callback. Disposal also unregisters it.
|
|
30
|
+
*/
|
|
31
|
+
onResubscribed(callback: () => void | Promise<void>): () => void;
|
|
32
|
+
|
|
33
|
+
/** Stops the stream and completes pending iteration. Safe to call more than once. */
|
|
34
|
+
dispose(): void;
|
|
35
|
+
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Constructor } from '@cratis/fundamentals';
|
|
5
5
|
import type { IMaterializedReadModels } from './IMaterializedReadModels.js';
|
|
6
|
+
import type { IReadModelWatcher } from './IReadModelWatcher.js';
|
|
6
7
|
import type { ReadModelChangeset } from './ReadModelChangeset.js';
|
|
8
|
+
import type { ReadModelWatchOptions } from './ReadModelWatchOptions.js';
|
|
7
9
|
import type { ReadModelSnapshot } from './ReadModelSnapshot.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -65,12 +67,25 @@ export interface IReadModels {
|
|
|
65
67
|
getSnapshotsById<TReadModel>(readModelType: Constructor<TReadModel>, key: string): Promise<ReadModelSnapshot<TReadModel>[]>;
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
|
-
* Watches changes for a specific read model type.
|
|
70
|
+
* Watches changes for a specific read model type, opening the stream on the first next().
|
|
71
|
+
* Pulls changes only as the consumer requests them, without a client-side buffer or overflow limit.
|
|
69
72
|
* @param readModelType - The read model type to observe.
|
|
70
|
-
* @returns An async iterable of read model changes.
|
|
73
|
+
* @returns An async iterable of read model changes. Transport failures reject iteration.
|
|
74
|
+
* @remarks Keeps its original return type so existing watch implementations and mocks can
|
|
75
|
+
* return plain async iterables. Use createWatcher for readiness and lifetime controls.
|
|
71
76
|
*/
|
|
72
77
|
watch<TReadModel>(readModelType: Constructor<TReadModel>): AsyncIterable<ReadModelChangeset<TReadModel>>;
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Starts a single-consumer watcher immediately, without requiring iteration for readiness.
|
|
81
|
+
* @param readModelType - The read model type to observe.
|
|
82
|
+
* @param options - Cancellation, opt-in resumption, and an optional overflow-failure limit.
|
|
83
|
+
* @returns A watcher with subscription readiness, resubscription callbacks, and explicit disposal.
|
|
84
|
+
* Transport failures reject iteration unless resume is true or an onResubscribed callback
|
|
85
|
+
* is registered. Resumed watches retain received changes but do not replay outage gaps.
|
|
86
|
+
*/
|
|
87
|
+
createWatcher<TReadModel>(readModelType: Constructor<TReadModel>, options?: ReadModelWatchOptions): IReadModelWatcher<TReadModel>;
|
|
88
|
+
|
|
74
89
|
/**
|
|
75
90
|
* Dehydrates a read model session.
|
|
76
91
|
* @param sessionId - The session identifier to dehydrate.
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
/** The triggering event metadata available on the kernel's read model change stream. */
|
|
5
|
+
export interface ReadModelChangeContext {
|
|
6
|
+
readonly eventStore: string;
|
|
7
|
+
readonly namespace: string;
|
|
8
|
+
readonly sequenceNumber: bigint;
|
|
9
|
+
readonly correlationId?: string;
|
|
10
|
+
readonly occurred?: Date;
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
/** The kind of change to a read model instance. */
|
|
5
|
+
export enum ReadModelChangeType {
|
|
6
|
+
Added = 0,
|
|
7
|
+
Modified = 1,
|
|
8
|
+
Removed = 2
|
|
9
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
|
+
import type { ReadModelChangeType } from './ReadModelChangeType.js';
|
|
5
|
+
import type { ReadModelChangeContext } from './ReadModelChangeContext.js';
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Represents a change observed for a read model.
|
|
6
9
|
*/
|
|
@@ -16,4 +19,10 @@ export interface ReadModelChangeset<TReadModel> {
|
|
|
16
19
|
|
|
17
20
|
/** Whether the read model was removed. */
|
|
18
21
|
readonly removed: boolean;
|
|
22
|
+
|
|
23
|
+
/** The kind of change. Optional for compatibility with user-created changesets. */
|
|
24
|
+
readonly changeType?: ReadModelChangeType;
|
|
25
|
+
|
|
26
|
+
/** Triggering event metadata supplied by the kernel, not a complete event context. */
|
|
27
|
+
readonly changeContext?: ReadModelChangeContext;
|
|
19
28
|
}
|
|
@@ -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
|
+
/** Options for creating a read model watcher. */
|
|
5
|
+
export interface ReadModelWatchOptions {
|
|
6
|
+
/** Aborting completes iteration and rejects pending readiness. */
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resume after transport failure or completion. Defaults to false; registering an
|
|
11
|
+
* onResubscribed callback also opts in while at least one callback is registered.
|
|
12
|
+
* Changes missed during an outage are not replayed; refresh your query after resubscription.
|
|
13
|
+
*/
|
|
14
|
+
readonly resume?: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Opt into failing on overflow instead of applying backpressure. Must be a positive safe integer.
|
|
18
|
+
* By default, pause reads at 1,024 buffered changes after subscription acknowledgment.
|
|
19
|
+
* Before each acknowledgment, keep reading to reach the marker; exceeding 1,024 changes
|
|
20
|
+
* (or this explicit limit) fails iteration and pending readiness rather than dropping changes.
|
|
21
|
+
*/
|
|
22
|
+
readonly maxBuffered?: number;
|
|
23
|
+
}
|