@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,125 @@
|
|
|
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 { ConcurrencyDimensions } from '../eventSources/ConcurrencyDimensions.js';
|
|
5
|
+
import { ResolvedEventRouting } from '../eventSources/ResolvedEventRouting.js';
|
|
6
|
+
import type { IEventSources } from '../eventSources/IEventSources.js';
|
|
7
|
+
import type { AppendOptions } from './AppendOptions.js';
|
|
8
|
+
import type { ConcurrencyScope } from './ConcurrencyScope.js';
|
|
9
|
+
import type { EventForEventSourceId } from './EventForEventSourceId.js';
|
|
10
|
+
import { EventSequenceNumber } from './EventSequenceNumber.js';
|
|
11
|
+
|
|
12
|
+
/** Resolves the definition routing of a single append, or undefined for a legacy append. */
|
|
13
|
+
export function resolveSingleRouting(eventSources: IEventSources | undefined, options?: AppendOptions): ResolvedEventRouting | undefined {
|
|
14
|
+
return ResolvedEventRouting.resolve(eventSources, options?.eventSource, options?.eventStream,
|
|
15
|
+
{ sourceType: options?.sourceType, streamType: options?.streamType });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Resolves the definition routing of one event of a batch; per-event values win over the shared options. */
|
|
19
|
+
export function resolveBatchRouting(
|
|
20
|
+
eventSources: IEventSources | undefined,
|
|
21
|
+
event: EventForEventSourceId,
|
|
22
|
+
options?: AppendOptions
|
|
23
|
+
): ResolvedEventRouting | undefined {
|
|
24
|
+
const ownSource = event.eventSource !== undefined;
|
|
25
|
+
const source = event.eventSource ?? options?.eventSource;
|
|
26
|
+
// A stream belongs to its event source: never inherit the shared stream into a different source.
|
|
27
|
+
const stream = event.eventStream ?? (ownSource ? undefined : options?.eventStream);
|
|
28
|
+
return ResolvedEventRouting.resolve(eventSources, source, stream, {
|
|
29
|
+
sourceType: event.eventSourceType ?? options?.sourceType,
|
|
30
|
+
streamType: event.eventStreamType ?? options?.streamType
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A derived guard required by a registered-definition event of a batch. */
|
|
35
|
+
export interface RequiredGuard {
|
|
36
|
+
/** The routing whose dimensions produce the guard. */
|
|
37
|
+
readonly routing: ResolvedEventRouting;
|
|
38
|
+
/** The stream id the guard's stream-id dimension (if any) applies to. */
|
|
39
|
+
readonly streamId: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The predicate a guard evaluates: only the values of the dimensions the routing selects. */
|
|
43
|
+
function guardPredicate(routing: ResolvedEventRouting, eventSourceId: string, streamId: string | undefined): string | undefined {
|
|
44
|
+
const dimensions = routing.dimensions;
|
|
45
|
+
if (dimensions === ConcurrencyDimensions.none) return undefined;
|
|
46
|
+
const has = (flag: number) => (dimensions & flag) !== 0;
|
|
47
|
+
return JSON.stringify([
|
|
48
|
+
has(ConcurrencyDimensions.eventSourceId) ? eventSourceId : null,
|
|
49
|
+
has(ConcurrencyDimensions.eventSourceType) ? routing.sourceType : null,
|
|
50
|
+
has(ConcurrencyDimensions.eventStreamType) ? routing.streamType ?? null : null,
|
|
51
|
+
has(ConcurrencyDimensions.eventStreamId) ? streamId ?? null : null
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Determines the single derived guard every event source id of a batch needs.
|
|
57
|
+
* The Kernel accepts one concurrency scope per event source id, so equivalent predicates share one
|
|
58
|
+
* scope (an unguarded event never suppresses a guarded one) and differing predicates are rejected
|
|
59
|
+
* before anything is written.
|
|
60
|
+
* @param routings - The resolved routing per event, aligned with {@link events}.
|
|
61
|
+
* @param events - The batch events.
|
|
62
|
+
* @param sharedStreamId - The shared stream id from the append options.
|
|
63
|
+
* @param explicitIds - Event source ids that carry an explicit scope; they are never derived.
|
|
64
|
+
* @returns The guard to derive per event source id.
|
|
65
|
+
* @throws Error when one event source id needs differing guards.
|
|
66
|
+
*/
|
|
67
|
+
export function planDerivedGuards(
|
|
68
|
+
routings: Array<ResolvedEventRouting | undefined>,
|
|
69
|
+
events: EventForEventSourceId[],
|
|
70
|
+
sharedStreamId: string | undefined,
|
|
71
|
+
explicitIds: ReadonlySet<string> = new Set()
|
|
72
|
+
): Map<string, RequiredGuard> {
|
|
73
|
+
const guards = new Map<string, { guard: RequiredGuard; predicate: string }>();
|
|
74
|
+
routings.forEach((routing, index) => {
|
|
75
|
+
const { eventSourceId, eventStreamId } = events[index];
|
|
76
|
+
if (!routing || explicitIds.has(eventSourceId)) return;
|
|
77
|
+
const streamId = eventStreamId ?? sharedStreamId;
|
|
78
|
+
const predicate = guardPredicate(routing, eventSourceId, streamId);
|
|
79
|
+
if (predicate === undefined) return;
|
|
80
|
+
const existing = guards.get(eventSourceId);
|
|
81
|
+
if (!existing) {
|
|
82
|
+
guards.set(eventSourceId, { guard: { routing, streamId }, predicate });
|
|
83
|
+
} else if (existing.predicate !== predicate) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Event source id '${eventSourceId}' needs differing concurrency guards within one batch ` +
|
|
86
|
+
`('${existing.guard.routing.eventSource}' and '${routing.eventSource}'), but Chronicle accepts one concurrency scope per event source id. ` +
|
|
87
|
+
'Pass an explicit shared concurrencyScopes entry for the id, or append the events in separate batches.');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return new Map([...guards].map(([id, { guard }]) => [id, guard]));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The tail reader a derived concurrency scope needs. */
|
|
94
|
+
export type TailReader = (eventSourceId?: string, sourceType?: string, streamType?: string, streamId?: string) => Promise<EventSequenceNumber>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Derives an optimistic concurrency scope from the dimensions a definition declares.
|
|
98
|
+
* Only call this when no explicit scope exists; explicit scopes always win.
|
|
99
|
+
* @returns The scope, or undefined when the definition declares no dimensions.
|
|
100
|
+
*/
|
|
101
|
+
export async function deriveConcurrencyScope(
|
|
102
|
+
routing: ResolvedEventRouting,
|
|
103
|
+
eventSourceId: string,
|
|
104
|
+
streamId: string | undefined,
|
|
105
|
+
readTail: TailReader
|
|
106
|
+
): Promise<ConcurrencyScope | undefined> {
|
|
107
|
+
const dimensions = routing.dimensions;
|
|
108
|
+
if (dimensions === ConcurrencyDimensions.none) return undefined;
|
|
109
|
+
|
|
110
|
+
const has = (flag: number) => (dimensions & flag) !== 0;
|
|
111
|
+
const scopedSource = has(ConcurrencyDimensions.eventSourceId) ? eventSourceId : undefined;
|
|
112
|
+
const scopedSourceType = has(ConcurrencyDimensions.eventSourceType) ? routing.sourceType : undefined;
|
|
113
|
+
const scopedStreamType = has(ConcurrencyDimensions.eventStreamType) ? routing.streamType : undefined;
|
|
114
|
+
const scopedStreamId = has(ConcurrencyDimensions.eventStreamId) ? streamId : undefined;
|
|
115
|
+
|
|
116
|
+
const tail = await readTail(scopedSource, scopedSourceType, scopedStreamType, scopedStreamId);
|
|
117
|
+
return {
|
|
118
|
+
// An empty scope is checked against "no matching event", like an explicit first append.
|
|
119
|
+
sequenceNumber: tail.value === EventSequenceNumber.unset.value ? EventSequenceNumber.beforeFirst.value : tail.value,
|
|
120
|
+
eventSourceId: scopedSource !== undefined,
|
|
121
|
+
eventSourceType: scopedSourceType,
|
|
122
|
+
eventStreamType: scopedStreamType,
|
|
123
|
+
eventStreamId: scopedStreamId
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -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
|
+
/**
|
|
5
|
+
* Flags describing which dimensions of an append take part in a concurrency check.
|
|
6
|
+
* Values match the wire contract and can be combined with bitwise OR.
|
|
7
|
+
*/
|
|
8
|
+
export const ConcurrencyDimensions = {
|
|
9
|
+
/** No dimensions declared; the append keeps its existing concurrency behavior. */
|
|
10
|
+
none: 0,
|
|
11
|
+
|
|
12
|
+
/** The event source identifier. */
|
|
13
|
+
eventSourceId: 1,
|
|
14
|
+
|
|
15
|
+
/** The event source type. */
|
|
16
|
+
eventSourceType: 2,
|
|
17
|
+
|
|
18
|
+
/** The event stream type. */
|
|
19
|
+
eventStreamType: 4,
|
|
20
|
+
|
|
21
|
+
/** The event stream identifier. */
|
|
22
|
+
eventStreamId: 8
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
/** A bitwise combination of {@link ConcurrencyDimensions} values. */
|
|
26
|
+
export type ConcurrencyDimensionFlags = number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when two discovered event source definitions share a name. */
|
|
5
|
+
export class DuplicateEventSourceName extends Error {
|
|
6
|
+
constructor(readonly eventSource: string) {
|
|
7
|
+
super(`More than one event source definition is named '${eventSource}'.`);
|
|
8
|
+
this.name = 'DuplicateEventSourceName';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when an event source definition declares the same stream name twice. */
|
|
5
|
+
export class DuplicateEventStreamName extends Error {
|
|
6
|
+
constructor(readonly eventSource: string, readonly stream: string) {
|
|
7
|
+
super(`The event source '${eventSource}' declares the event stream '${stream}' more than once.`);
|
|
8
|
+
this.name = 'DuplicateEventStreamName';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when explicit routing values contradict the event source definition. */
|
|
5
|
+
export class EventRoutingContradictsEventSource extends Error {
|
|
6
|
+
constructor(readonly eventSource: string, readonly dimension: string, readonly expected: string, readonly actual: string) {
|
|
7
|
+
super(`The explicit ${dimension} '${actual}' contradicts the event source '${eventSource}', which requires '${expected}'.`);
|
|
8
|
+
this.name = 'EventRoutingContradictsEventSource';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 { Constructor } from '@cratis/fundamentals';
|
|
5
|
+
import type { ConcurrencyDimensionFlags } from './ConcurrencyDimensions.js';
|
|
6
|
+
import type { EventStreamDefinition } from './EventStreamDefinition.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Describes a registered event source: a stable name, its default concurrency dimensions and its streams.
|
|
10
|
+
* Routing belongs to the append, not to event types.
|
|
11
|
+
*/
|
|
12
|
+
export interface EventSourceDefinition {
|
|
13
|
+
/** The class carrying the definition. */
|
|
14
|
+
readonly type: Constructor;
|
|
15
|
+
|
|
16
|
+
/** The stable name of the event source; this is the event source type of appended events. */
|
|
17
|
+
readonly name: string;
|
|
18
|
+
|
|
19
|
+
/** The description of the event source. */
|
|
20
|
+
readonly description: string;
|
|
21
|
+
|
|
22
|
+
/** The default dimensions that take part in concurrency checks. */
|
|
23
|
+
readonly concurrency: ConcurrencyDimensionFlags;
|
|
24
|
+
|
|
25
|
+
/** The streams declared by the event source. */
|
|
26
|
+
readonly streams: ReadonlyArray<EventStreamDefinition>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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 { Constructor } from '@cratis/fundamentals';
|
|
5
|
+
import type { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
6
|
+
import type { ChronicleConnection } from '../connection/index.js';
|
|
7
|
+
import { EventSourceOwner } from '@cratis/chronicle.contracts';
|
|
8
|
+
import { ensureCommandSuccess } from '../connection/callResults.js';
|
|
9
|
+
import type { EventSourceDefinition } from './EventSourceDefinition.js';
|
|
10
|
+
import { getEventSourceMetadata, getEventStreamsFor } from './eventSource.js';
|
|
11
|
+
import { DuplicateEventSourceName } from './DuplicateEventSourceName.js';
|
|
12
|
+
import { DuplicateEventStreamName } from './DuplicateEventStreamName.js';
|
|
13
|
+
import { UnknownEventSource } from './UnknownEventSource.js';
|
|
14
|
+
import type { IEventSources } from './IEventSources.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Implements {@link IEventSources}, discovering `@eventSource` classes and registering them with the Kernel.
|
|
18
|
+
*/
|
|
19
|
+
export class EventSources implements IEventSources {
|
|
20
|
+
private _byType = new Map<Constructor, EventSourceDefinition>();
|
|
21
|
+
private _byName = new Map<string, EventSourceDefinition>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new {@link EventSources}.
|
|
25
|
+
* @param _eventStore - The name of the event store the definitions belong to.
|
|
26
|
+
* @param _connection - The connection used to communicate with the Kernel.
|
|
27
|
+
* @param _clientArtifacts - Provider for discovered client artifact types.
|
|
28
|
+
*/
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly _eventStore: string,
|
|
31
|
+
private readonly _connection: ChronicleConnection,
|
|
32
|
+
private readonly _clientArtifacts: IClientArtifactsProvider
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
/** @inheritdoc */
|
|
36
|
+
get all(): ReadonlyArray<EventSourceDefinition> {
|
|
37
|
+
return [...this._byType.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @inheritdoc */
|
|
41
|
+
async discover(): Promise<void> {
|
|
42
|
+
const byType = new Map<Constructor, EventSourceDefinition>();
|
|
43
|
+
const byName = new Map<string, EventSourceDefinition>();
|
|
44
|
+
for (const type of this._clientArtifacts.eventSources ?? []) {
|
|
45
|
+
const metadata = getEventSourceMetadata(type);
|
|
46
|
+
if (!metadata) continue;
|
|
47
|
+
if (byName.has(metadata.name)) throw new DuplicateEventSourceName(metadata.name);
|
|
48
|
+
const streams = getEventStreamsFor(type);
|
|
49
|
+
const seen = new Set<string>();
|
|
50
|
+
for (const stream of streams) {
|
|
51
|
+
if (seen.has(stream.name)) throw new DuplicateEventStreamName(metadata.name, stream.name);
|
|
52
|
+
seen.add(stream.name);
|
|
53
|
+
}
|
|
54
|
+
const definition: EventSourceDefinition = {
|
|
55
|
+
type,
|
|
56
|
+
name: metadata.name,
|
|
57
|
+
description: metadata.description,
|
|
58
|
+
concurrency: metadata.concurrency,
|
|
59
|
+
streams: streams.map(stream => ({ ...stream }))
|
|
60
|
+
};
|
|
61
|
+
byType.set(type, definition);
|
|
62
|
+
byName.set(definition.name, definition);
|
|
63
|
+
}
|
|
64
|
+
this._byType = byType;
|
|
65
|
+
this._byName = byName;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @inheritdoc */
|
|
69
|
+
async register(): Promise<void> {
|
|
70
|
+
if (this._byType.size === 0) return;
|
|
71
|
+
ensureCommandSuccess('register event sources', await this._connection.eventSources.registerEventSources({
|
|
72
|
+
EventStore: this._eventStore,
|
|
73
|
+
Sources: this.all.map(definition => ({
|
|
74
|
+
Name: definition.name,
|
|
75
|
+
Description: definition.description,
|
|
76
|
+
Owner: EventSourceOwner.Client,
|
|
77
|
+
Concurrency: definition.concurrency,
|
|
78
|
+
Streams: definition.streams.map(stream => ({
|
|
79
|
+
Name: stream.name,
|
|
80
|
+
Description: stream.description,
|
|
81
|
+
Concurrency: stream.concurrency
|
|
82
|
+
}))
|
|
83
|
+
}))
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @inheritdoc */
|
|
88
|
+
getFor(eventSource: Constructor | string): EventSourceDefinition {
|
|
89
|
+
const definition = typeof eventSource === 'string' ? this._byName.get(eventSource) : this._byType.get(eventSource);
|
|
90
|
+
if (!definition) throw new UnknownEventSource(typeof eventSource === 'string' ? eventSource : eventSource.name);
|
|
91
|
+
return definition;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -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 type { ConcurrencyDimensionFlags } from './ConcurrencyDimensions.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Describes an event stream declared by an event source definition.
|
|
8
|
+
*/
|
|
9
|
+
export interface EventStreamDefinition {
|
|
10
|
+
/** The stable name of the stream; this is the event stream type of appended events. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
|
|
13
|
+
/** The description of the stream. */
|
|
14
|
+
readonly description: string;
|
|
15
|
+
|
|
16
|
+
/** The dimensions that take part in concurrency checks for the stream. Falls back to the event source when none. */
|
|
17
|
+
readonly concurrency: ConcurrencyDimensionFlags;
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when an append names a stream the event source does not declare. */
|
|
5
|
+
export class EventStreamDoesNotBelongToEventSource extends Error {
|
|
6
|
+
constructor(readonly eventSource: string, readonly stream: string) {
|
|
7
|
+
super(`The event stream '${stream}' is not declared by the event source '${eventSource}'.`);
|
|
8
|
+
this.name = 'EventStreamDoesNotBelongToEventSource';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when an append names a stream without naming the event source that declares it. */
|
|
5
|
+
export class EventStreamRequiresEventSource extends Error {
|
|
6
|
+
constructor(readonly stream: string) {
|
|
7
|
+
super(`The event stream '${stream}' can only be used together with an event source.`);
|
|
8
|
+
this.name = 'EventStreamRequiresEventSource';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { Constructor } from '@cratis/fundamentals';
|
|
5
|
+
import type { EventSourceDefinition } from './EventSourceDefinition.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Defines the discovery and registration of event source definitions.
|
|
9
|
+
*/
|
|
10
|
+
export interface IEventSources {
|
|
11
|
+
/** Gets all discovered event source definitions. */
|
|
12
|
+
readonly all: ReadonlyArray<EventSourceDefinition>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Discovers the event source definitions from the client artifacts.
|
|
16
|
+
* @throws DuplicateEventSourceName when two definitions share a name.
|
|
17
|
+
* @throws DuplicateEventStreamName when a definition declares the same stream twice.
|
|
18
|
+
*/
|
|
19
|
+
discover(): Promise<void>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers the discovered definitions with the Kernel. Registration is an upsert; the Kernel retains
|
|
23
|
+
* historical definitions that are no longer registered. Does nothing when there are no definitions.
|
|
24
|
+
*/
|
|
25
|
+
register(): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Gets the definition by class or name.
|
|
29
|
+
* @param eventSource - The class decorated with `@eventSource`, or the definition name.
|
|
30
|
+
* @throws UnknownEventSource when there is no such definition.
|
|
31
|
+
*/
|
|
32
|
+
getFor(eventSource: Constructor | string): EventSourceDefinition;
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { Constructor } from '@cratis/fundamentals';
|
|
5
|
+
import type { ConcurrencyDimensionFlags } from './ConcurrencyDimensions.js';
|
|
6
|
+
import type { EventSourceDefinition } from './EventSourceDefinition.js';
|
|
7
|
+
import type { EventStreamDefinition } from './EventStreamDefinition.js';
|
|
8
|
+
import type { IEventSources } from './IEventSources.js';
|
|
9
|
+
import { EventRoutingContradictsEventSource } from './EventRoutingContradictsEventSource.js';
|
|
10
|
+
import { EventStreamDoesNotBelongToEventSource } from './EventStreamDoesNotBelongToEventSource.js';
|
|
11
|
+
import { EventStreamRequiresEventSource } from './EventStreamRequiresEventSource.js';
|
|
12
|
+
import { UnknownEventSource } from './UnknownEventSource.js';
|
|
13
|
+
|
|
14
|
+
/** The explicit routing a caller supplied next to a definition. */
|
|
15
|
+
export interface ExplicitRouting {
|
|
16
|
+
/** Explicit event source type, if any. */
|
|
17
|
+
sourceType?: string;
|
|
18
|
+
|
|
19
|
+
/** Explicit event stream type, if any. */
|
|
20
|
+
streamType?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The routing of an append resolved through an event source definition. */
|
|
24
|
+
export class ResolvedEventRouting {
|
|
25
|
+
private constructor(readonly definition: EventSourceDefinition, readonly stream?: EventStreamDefinition) {}
|
|
26
|
+
|
|
27
|
+
/** The registered event source name written on the event. */
|
|
28
|
+
get eventSource(): string { return this.definition.name; }
|
|
29
|
+
|
|
30
|
+
/** The event source type written on the event. */
|
|
31
|
+
get sourceType(): string { return this.definition.name; }
|
|
32
|
+
|
|
33
|
+
/** The event stream type to write, or undefined to let the Kernel choose. */
|
|
34
|
+
get streamType(): string | undefined { return this.stream?.name; }
|
|
35
|
+
|
|
36
|
+
/** The concurrency dimensions that apply: the stream's when it declares any, otherwise the source's. */
|
|
37
|
+
get dimensions(): ConcurrencyDimensionFlags {
|
|
38
|
+
return this.stream && this.stream.concurrency !== 0 ? this.stream.concurrency : this.definition.concurrency;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolves the routing of an append.
|
|
43
|
+
* @param eventSources - The event sources; undefined when the store has none.
|
|
44
|
+
* @param eventSource - The definition class or name; undefined for a legacy append.
|
|
45
|
+
* @param stream - The declared stream name, if any.
|
|
46
|
+
* @param explicit - Explicit routing that must not contradict the definition.
|
|
47
|
+
* @returns The routing, or undefined when no definition is involved.
|
|
48
|
+
* @throws UnknownEventSource, EventStreamDoesNotBelongToEventSource, EventRoutingContradictsEventSource, EventStreamRequiresEventSource.
|
|
49
|
+
*/
|
|
50
|
+
static resolve(
|
|
51
|
+
eventSources: IEventSources | undefined,
|
|
52
|
+
eventSource: Constructor | string | undefined,
|
|
53
|
+
stream: string | undefined,
|
|
54
|
+
explicit: ExplicitRouting = {}
|
|
55
|
+
): ResolvedEventRouting | undefined {
|
|
56
|
+
if (eventSource === undefined) {
|
|
57
|
+
if (stream !== undefined) throw new EventStreamRequiresEventSource(stream);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (!eventSources) throw new UnknownEventSource(typeof eventSource === 'string' ? eventSource : eventSource.name);
|
|
61
|
+
const definition = eventSources.getFor(eventSource);
|
|
62
|
+
|
|
63
|
+
const isSet = (value: string | undefined, unspecified: string[]) => value !== undefined && value !== '' && !unspecified.includes(value);
|
|
64
|
+
if (isSet(explicit.sourceType, ['Default', 'Unspecified']) && explicit.sourceType !== definition.name) {
|
|
65
|
+
throw new EventRoutingContradictsEventSource(definition.name, 'event source type', definition.name, explicit.sourceType!);
|
|
66
|
+
}
|
|
67
|
+
const explicitStream = isSet(explicit.streamType, ['All']) ? explicit.streamType : undefined;
|
|
68
|
+
if (stream !== undefined && explicitStream !== undefined && stream !== explicitStream) {
|
|
69
|
+
throw new EventRoutingContradictsEventSource(definition.name, 'event stream type', stream, explicitStream);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const name = stream ?? explicitStream;
|
|
73
|
+
if (name === undefined) return new ResolvedEventRouting(definition);
|
|
74
|
+
const found = definition.streams.find(candidate => candidate.name === name);
|
|
75
|
+
if (!found) throw new EventStreamDoesNotBelongToEventSource(definition.name, name);
|
|
76
|
+
return new ResolvedEventRouting(definition, found);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Thrown when an append names an event source that is not a discovered definition. */
|
|
5
|
+
export class UnknownEventSource extends Error {
|
|
6
|
+
constructor(readonly eventSource: string) {
|
|
7
|
+
super(`The event source '${eventSource}' is not a discovered event source definition.`);
|
|
8
|
+
this.name = 'UnknownEventSource';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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 type { Constructor } from '@cratis/fundamentals';
|
|
6
|
+
import { DecoratorType, TypeDiscoverer } from '../types/index.js';
|
|
7
|
+
import type { ChronicleClassDecorator } from '../types/standardDecoratorMetadata.js';
|
|
8
|
+
import type { ConcurrencyDimensionFlags } from './ConcurrencyDimensions.js';
|
|
9
|
+
|
|
10
|
+
const EVENT_SOURCE_METADATA_KEY = 'chronicle:eventSource';
|
|
11
|
+
const EVENT_STREAMS_METADATA_KEY = 'chronicle:eventSource:streams';
|
|
12
|
+
let registrationCounter = 0;
|
|
13
|
+
|
|
14
|
+
/** Optional settings for {@link eventSource}. */
|
|
15
|
+
export interface EventSourceOptions {
|
|
16
|
+
/** The stable name. Defaults to the class name without a trailing `EventSource`. */
|
|
17
|
+
name?: string;
|
|
18
|
+
|
|
19
|
+
/** The description of the event source. */
|
|
20
|
+
description?: string;
|
|
21
|
+
|
|
22
|
+
/** The default dimensions that take part in concurrency checks; combine {@link ConcurrencyDimensions} values with `|`. */
|
|
23
|
+
concurrency?: ConcurrencyDimensionFlags;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Optional settings for {@link eventStream}. */
|
|
27
|
+
export interface EventStreamOptions {
|
|
28
|
+
/** The description of the stream. */
|
|
29
|
+
description?: string;
|
|
30
|
+
|
|
31
|
+
/** The dimensions that take part in concurrency checks for the stream. Falls back to the event source when omitted or none. */
|
|
32
|
+
concurrency?: ConcurrencyDimensionFlags;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Metadata recorded by {@link eventSource}. */
|
|
36
|
+
export interface EventSourceMetadata {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly description: string;
|
|
39
|
+
readonly concurrency: ConcurrencyDimensionFlags;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Metadata recorded by {@link eventStream}. */
|
|
43
|
+
export interface EventStreamMetadata {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly description: string;
|
|
46
|
+
readonly concurrency: ConcurrencyDimensionFlags;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Declares a class as an event source definition, discovered and registered with the Kernel at startup.
|
|
51
|
+
* The definition is not tied to event types; an append routes through it with `AppendOptions.eventSource`.
|
|
52
|
+
* Supports legacy and standard class decorators.
|
|
53
|
+
* @param options - Optional name (defaults to the class name without a trailing `EventSource`), description and concurrency.
|
|
54
|
+
* @returns A class decorator.
|
|
55
|
+
*/
|
|
56
|
+
export function eventSource(options?: EventSourceOptions): ChronicleClassDecorator {
|
|
57
|
+
return ((target: Function, context?: ClassDecoratorContext) => {
|
|
58
|
+
let constructor = target;
|
|
59
|
+
const metadata: EventSourceMetadata = {
|
|
60
|
+
name: options?.name ?? defaultNameFor(constructor),
|
|
61
|
+
description: options?.description ?? '',
|
|
62
|
+
concurrency: options?.concurrency ?? 0
|
|
63
|
+
};
|
|
64
|
+
Reflect.defineMetadata(EVENT_SOURCE_METADATA_KEY, metadata, target);
|
|
65
|
+
TypeDiscoverer.default.register(DecoratorType.EventSource, constructor as Constructor, `${metadata.name}#${registrationCounter++}`);
|
|
66
|
+
context?.addInitializer(function () {
|
|
67
|
+
if (this !== constructor) {
|
|
68
|
+
constructor = this as Function;
|
|
69
|
+
Reflect.defineMetadata(EVENT_SOURCE_METADATA_KEY, metadata, constructor);
|
|
70
|
+
Reflect.defineMetadata(EVENT_STREAMS_METADATA_KEY, Reflect.getOwnMetadata(EVENT_STREAMS_METADATA_KEY, target), constructor);
|
|
71
|
+
TypeDiscoverer.default.register(DecoratorType.EventSource, constructor as Constructor, `${metadata.name}#${registrationCounter++}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}) as ChronicleClassDecorator;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Declares a stream of an event source. Repeat the decorator for several streams.
|
|
79
|
+
* Only takes effect on a class that is also decorated with {@link eventSource}.
|
|
80
|
+
* @param name - The stable name of the stream, which is the event stream type of appended events.
|
|
81
|
+
* @param options - Optional description and concurrency dimensions.
|
|
82
|
+
* @returns A class decorator.
|
|
83
|
+
*/
|
|
84
|
+
export function eventStream(name: string, options?: EventStreamOptions): ChronicleClassDecorator {
|
|
85
|
+
return ((target: Function) => {
|
|
86
|
+
const existing = (Reflect.getOwnMetadata(EVENT_STREAMS_METADATA_KEY, target) as EventStreamMetadata[] | undefined) ?? [];
|
|
87
|
+
// Decorators evaluate bottom-up; unshift keeps declaration order.
|
|
88
|
+
Reflect.defineMetadata(EVENT_STREAMS_METADATA_KEY, [
|
|
89
|
+
{ name, description: options?.description ?? '', concurrency: options?.concurrency ?? 0 },
|
|
90
|
+
...existing
|
|
91
|
+
], target);
|
|
92
|
+
}) as ChronicleClassDecorator;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gets the event source metadata declared on a class.
|
|
97
|
+
* @param target - The class constructor.
|
|
98
|
+
* @returns The metadata, or undefined when the class is not an event source.
|
|
99
|
+
*/
|
|
100
|
+
export function getEventSourceMetadata(target: Function): EventSourceMetadata | undefined {
|
|
101
|
+
return Reflect.getOwnMetadata(EVENT_SOURCE_METADATA_KEY, target) as EventSourceMetadata | undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets the streams declared on a class, in declaration order (duplicates are preserved so they can be rejected).
|
|
106
|
+
* @param target - The class constructor.
|
|
107
|
+
* @returns The declared streams.
|
|
108
|
+
*/
|
|
109
|
+
export function getEventStreamsFor(target: Function): ReadonlyArray<EventStreamMetadata> {
|
|
110
|
+
return (Reflect.getOwnMetadata(EVENT_STREAMS_METADATA_KEY, target) as EventStreamMetadata[] | undefined) ?? [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function defaultNameFor(type: Function): string {
|
|
114
|
+
const suffix = 'EventSource';
|
|
115
|
+
return type.name.length > suffix.length && type.name.endsWith(suffix) ? type.name.slice(0, -suffix.length) : type.name;
|
|
116
|
+
}
|