@cratis/chronicle 6.48.0 → 6.50.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/EventStore.ts +8 -2
- 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/dist/EventStore.d.ts +2 -0
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +7 -2
- 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/eventSequences/AppendOptions.d.ts +22 -0
- package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
- package/dist/eventSequences/EventForEventSourceId.d.ts +17 -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 +59 -13
- 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/NamedTagsWithRegisteredEventSourceNotSupported.d.ts +5 -0
- package/dist/eventSequences/NamedTagsWithRegisteredEventSourceNotSupported.d.ts.map +1 -0
- package/dist/eventSequences/NamedTagsWithRegisteredEventSourceNotSupported.js +12 -0
- package/dist/eventSequences/NamedTagsWithRegisteredEventSourceNotSupported.js.map +1 -0
- package/dist/eventSequences/TransactionalEventSequence.d.ts +3 -2
- package/dist/eventSequences/TransactionalEventSequence.d.ts.map +1 -1
- package/dist/eventSequences/TransactionalEventSequence.js +7 -4
- package/dist/eventSequences/TransactionalEventSequence.js.map +1 -1
- package/dist/eventSequences/createAppendNotification.d.ts +2 -1
- package/dist/eventSequences/createAppendNotification.d.ts.map +1 -1
- package/dist/eventSequences/createAppendNotification.js +3 -2
- package/dist/eventSequences/createAppendNotification.js.map +1 -1
- package/dist/eventSequences/ensureNamedTagBatchIsSupported.d.ts +3 -0
- package/dist/eventSequences/ensureNamedTagBatchIsSupported.d.ts.map +1 -0
- package/dist/eventSequences/ensureNamedTagBatchIsSupported.js +10 -0
- package/dist/eventSequences/ensureNamedTagBatchIsSupported.js.map +1 -0
- package/dist/eventSequences/index.d.ts +1 -0
- package/dist/eventSequences/index.d.ts.map +1 -1
- package/dist/eventSequences/index.js +1 -0
- package/dist/eventSequences/index.js.map +1 -1
- package/dist/eventSequences/prepareBatchAppend.d.ts +1 -0
- package/dist/eventSequences/prepareBatchAppend.d.ts.map +1 -1
- package/dist/eventSequences/prepareBatchAppend.js +4 -1
- package/dist/eventSequences/prepareBatchAppend.js.map +1 -1
- package/dist/eventSequences/prepareSingleAppend.d.ts +1 -0
- package/dist/eventSequences/prepareSingleAppend.d.ts.map +1 -1
- package/dist/eventSequences/prepareSingleAppend.js +3 -1
- package/dist/eventSequences/prepareSingleAppend.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 +12 -0
- package/dist/events/EventContext.d.ts.map +1 -1
- package/dist/events/InvalidNamedTag.d.ts +8 -0
- package/dist/events/InvalidNamedTag.d.ts.map +1 -0
- package/dist/events/InvalidNamedTag.js +13 -0
- package/dist/events/InvalidNamedTag.js.map +1 -0
- package/dist/events/NamedTag.d.ts +25 -0
- package/dist/events/NamedTag.d.ts.map +1 -0
- package/dist/events/NamedTag.js +39 -0
- package/dist/events/NamedTag.js.map +1 -0
- package/dist/events/WireEventContext.d.ts +2 -0
- package/dist/events/WireEventContext.d.ts.map +1 -1
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/mergeNamedTags.d.ts +13 -0
- package/dist/events/mergeNamedTags.d.ts.map +1 -0
- package/dist/events/mergeNamedTags.js +39 -0
- package/dist/events/mergeNamedTags.js.map +1 -0
- package/dist/events/toClientEventContext.d.ts.map +1 -1
- package/dist/events/toClientEventContext.js +4 -1
- 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 +2 -0
- package/dist/projections/eventContextPropertyExpression.js.map +1 -1
- package/dist/testing/InProcessEventSequence.d.ts.map +1 -1
- package/dist/testing/InProcessEventSequence.js +17 -7
- package/dist/testing/InProcessEventSequence.js.map +1 -1
- package/dist/testing/ReactorScenario.d.ts.map +1 -1
- package/dist/testing/ReactorScenario.js +6 -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/testing/singleAppendRoute.d.ts +5 -0
- package/dist/testing/singleAppendRoute.d.ts.map +1 -1
- package/dist/testing/singleAppendRoute.js +12 -1
- package/dist/testing/singleAppendRoute.js.map +1 -1
- package/dist/transactions/IUnitOfWork.d.ts +4 -1
- package/dist/transactions/IUnitOfWork.d.ts.map +1 -1
- package/dist/transactions/TransactionalAppendOptions.d.ts +14 -0
- package/dist/transactions/TransactionalAppendOptions.d.ts.map +1 -0
- package/dist/transactions/TransactionalAppendOptions.js +4 -0
- package/dist/transactions/TransactionalAppendOptions.js.map +1 -0
- 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 +17 -2
- package/dist/transactions/UnitOfWork.js.map +1 -1
- package/dist/transactions/index.d.ts +2 -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 +25 -0
- package/eventSequences/EventForEventSourceId.ts +20 -0
- package/eventSequences/EventLog.ts +4 -2
- package/eventSequences/EventSequence.ts +62 -13
- package/eventSequences/ITransactionalEventSequence.ts +5 -2
- package/eventSequences/NamedTagsWithRegisteredEventSourceNotSupported.ts +12 -0
- package/eventSequences/TransactionalEventSequence.ts +8 -4
- package/eventSequences/createAppendNotification.ts +4 -2
- package/eventSequences/ensureNamedTagBatchIsSupported.ts +11 -0
- package/eventSequences/for_EventSequence/against_a_kernel/when_appending_named_tags.integration.spec.ts +107 -0
- package/eventSequences/for_EventSequence/when_appending_named_tags/given.fixture.ts +38 -0
- package/eventSequences/for_EventSequence/when_appending_named_tags/with_a_batch.spec.ts +49 -0
- package/eventSequences/for_EventSequence/when_appending_named_tags/with_a_single_event.spec.ts +47 -0
- package/eventSequences/for_EventSequence/when_appending_named_tags/with_an_invalid_tag.spec.ts +36 -0
- package/eventSequences/for_EventSequence/when_appending_named_tags/with_registered_sources.spec.ts +74 -0
- package/eventSequences/for_TransactionalEventSequence/when_appending_many_with_named_tags.spec.ts +41 -0
- package/eventSequences/index.ts +1 -0
- package/eventSequences/prepareBatchAppend.ts +4 -1
- package/eventSequences/prepareSingleAppend.ts +3 -1
- 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 +14 -0
- package/events/InvalidNamedTag.ts +13 -0
- package/events/NamedTag.ts +44 -0
- package/events/WireEventContext.ts +2 -0
- package/events/for_EventContext/with_event_source.spec.ts +22 -0
- package/events/for_NamedTag/when_constructing/with_invalid_values.spec.ts +17 -0
- package/events/for_NamedTag/when_constructing/with_valid_values.spec.ts +19 -0
- package/events/for_mergeNamedTags/when_merging/with_event_and_call_tags.spec.ts +31 -0
- package/events/for_mergeNamedTags/when_merging/with_invalid_entries.spec.ts +17 -0
- package/events/for_toClientEventContext/when_converting_named_tags.spec.ts +31 -0
- package/events/index.ts +3 -0
- package/events/mergeNamedTags.ts +37 -0
- package/events/toClientEventContext.ts +4 -1
- package/for_EventStore/when_implementing_a_legacy_event_store.spec.ts +19 -0
- package/index.ts +3 -0
- package/package.json +6 -6
- package/projections/eventContextPropertyExpression.ts +3 -1
- package/testing/EventScenario.event-sources.spec.ts +34 -0
- package/testing/InProcessEventSequence.ts +17 -7
- package/testing/ReactorScenario.ts +6 -0
- package/testing/for_EventScenario/when_appending_named_tags/with_named_tags.spec.ts +48 -0
- package/testing/for_ReactorScenario/when_recording_side_effects/with_named_tags.spec.ts +119 -0
- package/testing/for_ReadModelScenario/when_seeding_with_options/with_named_tags.spec.ts +35 -0
- package/testing/projections/ProjectionReadModelProcessor.ts +2 -1
- package/testing/singleAppendRoute.ts +13 -1
- package/transactions/IUnitOfWork.ts +4 -1
- package/transactions/TransactionalAppendOptions.ts +17 -0
- package/transactions/TransactionalEventRouting.ts +16 -0
- package/transactions/UnitOfWork.ts +22 -2
- package/transactions/for_UnitOfWork/when_committing_events_with_event_source_routing.spec.ts +41 -0
- package/transactions/for_UnitOfWork/when_committing_events_with_named_tags.spec.ts +112 -0
- package/transactions/index.ts +2 -0
- package/types/DecoratorType.ts +4 -1
|
@@ -25,5 +25,7 @@ export var DecoratorType;
|
|
|
25
25
|
DecoratorType["EventTypeMigration"] = "eventTypeMigration";
|
|
26
26
|
/** Global read model variant handler artifacts discovered through the globalFor decorator. */
|
|
27
27
|
DecoratorType["GlobalForHandler"] = "globalForHandler";
|
|
28
|
+
/** Event source definition artifacts discovered through the eventSource decorator. */
|
|
29
|
+
DecoratorType["EventSource"] = "eventSource";
|
|
28
30
|
})(DecoratorType || (DecoratorType = {}));
|
|
29
31
|
//# sourceMappingURL=DecoratorType.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DecoratorType.js","sourceRoot":"","sources":["../../types/DecoratorType.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG;;GAEG;AACH,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"DecoratorType.js","sourceRoot":"","sources":["../../types/DecoratorType.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG;;GAEG;AACH,MAAM,CAAN,IAAY,aAiCX;AAjCD,WAAY,aAAa;IACrB,uEAAuE;IACvE,wCAAuB,CAAA;IAEvB,uEAAuE;IACvE,wCAAuB,CAAA;IAEvB,kEAAkE;IAClE,oCAAmB,CAAA;IAEnB,kEAAkE;IAClE,oCAAmB,CAAA;IAEnB,gEAAgE;IAChE,kCAAiB,CAAA;IAEjB,wEAAwE;IACxE,0CAAyB,CAAA;IAEzB,oFAAoF;IACpF,0CAAyB,CAAA;IAEzB,kEAAkE;IAClE,oCAAmB,CAAA;IAEnB,0FAA0F;IAC1F,0DAAyC,CAAA;IAEzC,8FAA8F;IAC9F,sDAAqC,CAAA;IAErC,sFAAsF;IACtF,4CAA2B,CAAA;AAC/B,CAAC,EAjCW,aAAa,KAAb,aAAa,QAiCxB"}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { Guid } from '@cratis/fundamentals';
|
|
5
5
|
import type { Tag } from '../events/Tag.js';
|
|
6
|
+
import type { NamedTag } from '../events/NamedTag.js';
|
|
7
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
6
8
|
import type { ConcurrencyScope } from './ConcurrencyScope.js';
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -41,10 +43,33 @@ export interface AppendOptions {
|
|
|
41
43
|
*/
|
|
42
44
|
tags?: ReadonlyArray<string | Tag>;
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Optional structured named tags shared by every event being appended. For the
|
|
48
|
+
* `appendMany(events: EventForEventSourceId[], options?)` overload they are merged after each entry's own
|
|
49
|
+
* {@link EventForEventSourceId.namedTags}; the first occurrence of each exact name and value pair is kept.
|
|
50
|
+
* When any event ends up with named tags, the append uses the kernel's dedicated named-tag call, so a kernel
|
|
51
|
+
* without named-tag support rejects it instead of silently dropping the tags.
|
|
52
|
+
* @throws {@link InvalidNamedTag} (from the append) when an entry has a blank name or a non-string value.
|
|
53
|
+
*/
|
|
54
|
+
namedTags?: ReadonlyArray<NamedTag>;
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* Optional labeled concurrency scopes, keyed by event source id. Labels need not be append targets.
|
|
46
58
|
* Any target without an explicit scope falls back to {@link concurrencyScope}.
|
|
47
59
|
* The kernel requires at least one event in a batch, so scope-only batches are not supported.
|
|
48
60
|
*/
|
|
49
61
|
concurrencyScopes?: Record<string, ConcurrencyScope>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Routes the append through a registered event source definition: the class decorated with `@eventSource`, or its name.
|
|
65
|
+
* Sets the event source type and the wire `EventSource` key. Explicit `sourceType`/`streamType` values must not contradict it.
|
|
66
|
+
* When no `concurrencyScope` is supplied and the definition declares concurrency dimensions, the scope is derived from them.
|
|
67
|
+
*/
|
|
68
|
+
eventSource?: Constructor | string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The name of a stream declared by {@link eventSource}. Becomes the event stream type. Requires `eventSource`.
|
|
72
|
+
* Use `streamId` for the identifier within the stream.
|
|
73
|
+
*/
|
|
74
|
+
eventStream?: string;
|
|
50
75
|
}
|
|
@@ -1,7 +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 { Constructor } from '@cratis/fundamentals';
|
|
4
5
|
import type { Tag } from '../events/Tag.js';
|
|
6
|
+
import type { NamedTag } from '../events/NamedTag.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Represents an event paired with the event source identifier it belongs to.
|
|
@@ -34,4 +36,22 @@ export interface EventForEventSourceId {
|
|
|
34
36
|
* append time.
|
|
35
37
|
*/
|
|
36
38
|
readonly tags?: ReadonlyArray<string | Tag>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Optional structured named tags for this event. They are merged, in order, with the shared
|
|
42
|
+
* `AppendOptions.namedTags`; the first occurrence of each exact name and value pair is kept.
|
|
43
|
+
*/
|
|
44
|
+
readonly namedTags?: ReadonlyArray<NamedTag>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Optional registered event source definition (class decorated with `@eventSource`, or its name) for this event.
|
|
48
|
+
* Overrides `AppendOptions.eventSource`, so one batch can mix several event sources.
|
|
49
|
+
*/
|
|
50
|
+
readonly eventSource?: Constructor | string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional stream name declared by the event's event source. Overrides `AppendOptions.eventStream`.
|
|
54
|
+
* When this event names a different `eventSource` than the options, the options' stream is not inherited.
|
|
55
|
+
*/
|
|
56
|
+
readonly eventStream?: string;
|
|
37
57
|
}
|
|
@@ -7,6 +7,7 @@ import { EventSequenceId } from './EventSequenceId.js';
|
|
|
7
7
|
import { IEventLog } from './IEventLog.js';
|
|
8
8
|
import { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
|
|
9
9
|
import type { ConstraintViolation } from './ConstraintViolation.js';
|
|
10
|
+
import type { IEventSources } from '../eventSources/IEventSources.js';
|
|
10
11
|
import type { ChronicleTelemetryOptions } from '../telemetry/ChronicleTelemetryOptions.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -20,8 +21,9 @@ export class EventLog extends EventSequence implements IEventLog {
|
|
|
20
21
|
connection: ChronicleConnection,
|
|
21
22
|
unitOfWorkManager: IUnitOfWorkManager,
|
|
22
23
|
resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation,
|
|
23
|
-
telemetry?: ChronicleTelemetryOptions
|
|
24
|
+
telemetry?: ChronicleTelemetryOptions,
|
|
25
|
+
eventSources?: IEventSources
|
|
24
26
|
) {
|
|
25
|
-
super(EventSequenceId.eventLog, eventStoreName, namespace, connection, unitOfWorkManager, resolveConstraintMessage, telemetry);
|
|
27
|
+
super(EventSequenceId.eventLog, eventStoreName, namespace, connection, unitOfWorkManager, resolveConstraintMessage, telemetry, eventSources);
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -14,6 +14,7 @@ import { prepareBatchAppend } from './prepareBatchAppend.js';
|
|
|
14
14
|
import { createAppendNotification, mapAppendNotificationCausation } from './createAppendNotification.js';
|
|
15
15
|
import { getEventTypeFor } from '../events/eventTypeDecorator.js';
|
|
16
16
|
import type { AppendedEvent } from '../events/AppendedEvent.js';
|
|
17
|
+
import type { NamedTag } from '../events/NamedTag.js';
|
|
17
18
|
import { toClientEventContext } from '../events/toClientEventContext.js';
|
|
18
19
|
import { DecoratorType } from '../types/DecoratorType.js';
|
|
19
20
|
import { TypeDiscoverer } from '../types/TypeDiscoverer.js';
|
|
@@ -47,7 +48,10 @@ import { causationManager, CausationType } from '../auditing/index.js';
|
|
|
47
48
|
import { toContractsGuid } from '../connection/Guid.js';
|
|
48
49
|
import { ensureCommandResponse, ensureCommandSuccess, ensureQuerySuccess } from '../connection/callResults.js';
|
|
49
50
|
import type { ConcurrencyScope } from './ConcurrencyScope.js';
|
|
51
|
+
import type { IEventSources } from '../eventSources/IEventSources.js';
|
|
52
|
+
import { deriveConcurrencyScope, planDerivedGuards, resolveBatchRouting, resolveSingleRouting } from './resolveEventSourceRouting.js';
|
|
50
53
|
import { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
|
|
54
|
+
import { ensureNamedTagBatchIsSupported } from './ensureNamedTagBatchIsSupported.js';
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
57
|
* Implements {@link IEventSequence} by communicating with the Chronicle Kernel
|
|
@@ -64,14 +68,16 @@ export class EventSequence implements IEventSequence {
|
|
|
64
68
|
private readonly _connection: ChronicleConnection,
|
|
65
69
|
private readonly _unitOfWorkManager: IUnitOfWorkManager,
|
|
66
70
|
private readonly _resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation,
|
|
67
|
-
private readonly _telemetry?: ChronicleTelemetryOptions
|
|
71
|
+
private readonly _telemetry?: ChronicleTelemetryOptions,
|
|
72
|
+
private readonly _eventSources?: IEventSources
|
|
68
73
|
) {
|
|
69
74
|
this.transactional = new TransactionalEventSequence(this, this._unitOfWorkManager);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/** @inheritdoc */
|
|
73
78
|
async append(eventSourceId: string, event: object, options?: AppendOptions): Promise<AppendResult> {
|
|
74
|
-
const
|
|
79
|
+
const routing = resolveSingleRouting(this._eventSources, options);
|
|
80
|
+
const { eventType, correlationId, content, tags, namedTags, causationChain, identity, subject } = prepareSingleAppend(event, options);
|
|
75
81
|
|
|
76
82
|
const sequenceMetricAttributes = {
|
|
77
83
|
[names.attributes.eventStore]: this._eventStoreName,
|
|
@@ -88,14 +94,17 @@ export class EventSequence implements IEventSequence {
|
|
|
88
94
|
if (options?.sourceType) span.setAttribute(names.attributes.eventSourceType, options.sourceType);
|
|
89
95
|
const startTime = performance.now();
|
|
90
96
|
try {
|
|
91
|
-
const
|
|
97
|
+
const concurrencyScope = options?.concurrencyScope ?? (routing
|
|
98
|
+
? await deriveConcurrencyScope(routing, eventSourceId, options?.streamId, this.tailReader)
|
|
99
|
+
: undefined);
|
|
100
|
+
const request = {
|
|
92
101
|
EventStore: this._eventStoreName,
|
|
93
102
|
Namespace: this._namespace,
|
|
94
103
|
EventSequenceId: this.id.value,
|
|
95
104
|
CorrelationId: toContractsGuid(correlationId),
|
|
96
|
-
EventSourceType: options?.sourceType,
|
|
105
|
+
EventSourceType: routing?.sourceType ?? options?.sourceType,
|
|
97
106
|
EventSourceId: eventSourceId,
|
|
98
|
-
EventStreamType: options?.streamType,
|
|
107
|
+
EventStreamType: routing?.streamType ?? options?.streamType,
|
|
99
108
|
EventStreamId: options?.streamId,
|
|
100
109
|
EventType: {
|
|
101
110
|
Id: eventType.id.value,
|
|
@@ -109,11 +118,16 @@ export class EventSequence implements IEventSequence {
|
|
|
109
118
|
Properties: { ...c.properties }
|
|
110
119
|
})),
|
|
111
120
|
CausedBy: toContractsCausedBy(identity),
|
|
112
|
-
ConcurrencyScope: this.toContractConcurrencyScope(
|
|
121
|
+
ConcurrencyScope: this.toContractConcurrencyScope(concurrencyScope),
|
|
122
|
+
EventSource: routing?.eventSource,
|
|
113
123
|
Tags: tags,
|
|
114
124
|
Occurred: options?.occurred === undefined ? undefined : { Value: options.occurred.toISOString() },
|
|
115
125
|
Subject: subject ?? eventSourceId
|
|
116
|
-
}
|
|
126
|
+
};
|
|
127
|
+
// Named tags travel on the dedicated call so a kernel without support rejects them instead of dropping them.
|
|
128
|
+
const response = namedTags.length === 0
|
|
129
|
+
? await this._connection.eventSequences.append(request)
|
|
130
|
+
: await this._connection.eventSequences.appendWithNamedTags({ ...request, NamedTags: toContractsNamedTags(namedTags) });
|
|
117
131
|
|
|
118
132
|
const appendResponse = ensureCommandResponse('append event', response);
|
|
119
133
|
const durationInSeconds = (performance.now() - startTime) / 1000;
|
|
@@ -137,7 +151,7 @@ export class EventSequence implements IEventSequence {
|
|
|
137
151
|
|
|
138
152
|
if (this.appendOperations.hasSubscribers) {
|
|
139
153
|
this.appendOperations.publish([createAppendNotification(eventSourceId, event, result,
|
|
140
|
-
correlationId.toString(), mapAppendNotificationCausation(causationChain), tags)]);
|
|
154
|
+
correlationId.toString(), mapAppendNotificationCausation(causationChain), tags, undefined, namedTags)]);
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
return result;
|
|
@@ -159,9 +173,14 @@ export class EventSequence implements IEventSequence {
|
|
|
159
173
|
eventsOrOptions?: object[] | AppendOptions,
|
|
160
174
|
options?: AppendOptions
|
|
161
175
|
): Promise<AppendResult[]> {
|
|
162
|
-
const { eventsForEventSourceIds, correlationId, batchCausationChain, identity, concurrencyScopes, eventsToAppend } =
|
|
176
|
+
const { eventsForEventSourceIds, appendOptions, correlationId, batchCausationChain, identity, concurrencyScopes, eventsToAppend, namedTags } =
|
|
163
177
|
prepareBatchAppend(eventSourceIdOrEvents, eventsOrOptions, options);
|
|
164
178
|
|
|
179
|
+
const wireEvents: Array<(typeof eventsToAppend)[number] & { EventSource?: string }> = eventsToAppend;
|
|
180
|
+
const routings = eventsForEventSourceIds.map(event => resolveBatchRouting(this._eventSources, event, appendOptions));
|
|
181
|
+
ensureNamedTagBatchIsSupported(namedTags.some(tagsForEvent => tagsForEvent.length > 0), routings.some(routing => routing !== undefined));
|
|
182
|
+
const explicitIds = new Set([...concurrencyScopes].filter(([, scope]) => scope !== undefined).map(([id]) => id));
|
|
183
|
+
const derivedGuards = planDerivedGuards(routings, eventsForEventSourceIds, appendOptions?.streamId, explicitIds);
|
|
165
184
|
const distinctEventSourceIds = [...new Set(eventsForEventSourceIds.map(_ => _.eventSourceId))];
|
|
166
185
|
|
|
167
186
|
const batchMetricAttributes = {
|
|
@@ -178,12 +197,24 @@ export class EventSequence implements IEventSequence {
|
|
|
178
197
|
setTelemetryAttribute(span, 'eventCount', eventsForEventSourceIds.length);
|
|
179
198
|
const startTime = performance.now();
|
|
180
199
|
try {
|
|
181
|
-
|
|
200
|
+
routings.forEach((routing, index) => {
|
|
201
|
+
if (!routing) return;
|
|
202
|
+
wireEvents[index] = {
|
|
203
|
+
...wireEvents[index],
|
|
204
|
+
EventSourceType: routing.sourceType,
|
|
205
|
+
EventStreamType: routing.streamType ?? wireEvents[index].EventStreamType,
|
|
206
|
+
EventSource: routing.eventSource
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
for (const [id, { routing, streamId }] of derivedGuards) {
|
|
210
|
+
concurrencyScopes.set(id, await deriveConcurrencyScope(routing, id, streamId, this.tailReader));
|
|
211
|
+
}
|
|
212
|
+
const request = {
|
|
182
213
|
EventStore: this._eventStoreName,
|
|
183
214
|
Namespace: this._namespace,
|
|
184
215
|
EventSequenceId: this.id.value,
|
|
185
216
|
CorrelationId: toContractsGuid(correlationId),
|
|
186
|
-
Events:
|
|
217
|
+
Events: wireEvents,
|
|
187
218
|
Causation: batchCausationChain.map(c => ({
|
|
188
219
|
Occurred: { Value: c.occurred.toISOString() },
|
|
189
220
|
Type: c.type.name,
|
|
@@ -194,7 +225,13 @@ export class EventSequence implements IEventSequence {
|
|
|
194
225
|
EventSourceId: eventSourceId,
|
|
195
226
|
Scope: this.toContractConcurrencyScope(scope)
|
|
196
227
|
}))
|
|
197
|
-
}
|
|
228
|
+
};
|
|
229
|
+
const response = namedTags.every(tagsForEvent => tagsForEvent.length === 0)
|
|
230
|
+
? await this._connection.eventSequences.appendManyForEventSources(request)
|
|
231
|
+
: await this._connection.eventSequences.appendManyForEventSourcesWithNamedTags({
|
|
232
|
+
...request,
|
|
233
|
+
Events: wireEvents.map((wireEvent, index) => ({ ...wireEvent, NamedTags: toContractsNamedTags(namedTags[index]) }))
|
|
234
|
+
});
|
|
198
235
|
|
|
199
236
|
const appendManyResponse = ensureCommandResponse('append many events', response);
|
|
200
237
|
const durationInSeconds = (performance.now() - startTime) / 1000;
|
|
@@ -242,7 +279,7 @@ export class EventSequence implements IEventSequence {
|
|
|
242
279
|
this.appendOperations.publish(result.map((appendResult: AppendResult, index: number) => {
|
|
243
280
|
const { eventSourceId, event } = eventsForEventSourceIds[index];
|
|
244
281
|
return createAppendNotification(eventSourceId, event, appendResult, correlationId.toString(),
|
|
245
|
-
causationEntries, eventsToAppend[index].Tags, occurredAt);
|
|
282
|
+
causationEntries, eventsToAppend[index].Tags, occurredAt, namedTags[index]);
|
|
246
283
|
}));
|
|
247
284
|
}
|
|
248
285
|
|
|
@@ -598,6 +635,9 @@ export class EventSequence implements IEventSequence {
|
|
|
598
635
|
}
|
|
599
636
|
}
|
|
600
637
|
|
|
638
|
+
private readonly tailReader = (source?: string, sourceType?: string, streamType?: string, streamId?: string) =>
|
|
639
|
+
this.getTailSequenceNumber(source, sourceType, streamType, streamId);
|
|
640
|
+
|
|
601
641
|
private toContractConcurrencyScope(scope?: ConcurrencyScope) {
|
|
602
642
|
return {
|
|
603
643
|
SequenceNumber: scope?.sequenceNumber === EventSequenceNumber.beforeFirst.value
|
|
@@ -617,6 +657,15 @@ export class EventSequence implements IEventSequence {
|
|
|
617
657
|
}
|
|
618
658
|
}
|
|
619
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Converts structured named tags into the sequence contract shape.
|
|
662
|
+
* @param namedTags - The validated named tags.
|
|
663
|
+
* @returns The wire named tags.
|
|
664
|
+
*/
|
|
665
|
+
function toContractsNamedTags(namedTags: readonly NamedTag[]): { Name: string; Value: string }[] {
|
|
666
|
+
return namedTags.map(tag => ({ Name: tag.name, Value: tag.value }));
|
|
667
|
+
}
|
|
668
|
+
|
|
620
669
|
/**
|
|
621
670
|
* Converts an {@link Identity} into the CausedBy shape used by Chronicle contracts.
|
|
622
671
|
* @param identity - The identity to convert.
|
|
@@ -1,6 +1,7 @@
|
|
|
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 { TransactionalAppendOptions } from '../transactions/TransactionalAppendOptions.js';
|
|
4
5
|
import { IUnitOfWork } from '../transactions/IUnitOfWork.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -14,13 +15,15 @@ export interface ITransactionalEventSequence {
|
|
|
14
15
|
* Adds a single event to the current unit of work.
|
|
15
16
|
* @param eventSourceId - The identifier of the event source.
|
|
16
17
|
* @param event - The event to append.
|
|
18
|
+
* @param options - Optional registered event source / stream routing and named tags.
|
|
17
19
|
*/
|
|
18
|
-
append(eventSourceId: string, event: object): Promise<void>;
|
|
20
|
+
append(eventSourceId: string, event: object, options?: TransactionalAppendOptions): Promise<void>;
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Adds multiple events to the current unit of work.
|
|
22
24
|
* @param eventSourceId - The identifier of the event source.
|
|
23
25
|
* @param events - The events to append.
|
|
26
|
+
* @param options - Optional registered event source / stream routing and named tags applied to every event.
|
|
24
27
|
*/
|
|
25
|
-
appendMany(eventSourceId: string, events: object[]): Promise<void>;
|
|
28
|
+
appendMany(eventSourceId: string, events: object[], options?: TransactionalAppendOptions): Promise<void>;
|
|
26
29
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
/** Named-tag batches cannot preserve registered event source routing on the current kernel. */
|
|
5
|
+
export class NamedTagsWithRegisteredEventSourceNotSupported extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super('Batches and unit-of-work commits cannot combine non-empty named tags with registered event sources: ' +
|
|
8
|
+
'the kernel named-tag batch operation drops registered event source routing. ' +
|
|
9
|
+
'Use single append calls instead. See https://github.com/Cratis/Chronicle/issues/4603.');
|
|
10
|
+
this.name = 'NamedTagsWithRegisteredEventSourceNotSupported';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
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 { TransactionalAppendOptions } from '../transactions/TransactionalAppendOptions.js';
|
|
5
|
+
import { mergeNamedTags } from '../events/mergeNamedTags.js';
|
|
4
6
|
import { IUnitOfWork } from '../transactions/IUnitOfWork.js';
|
|
5
7
|
import { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager.js';
|
|
6
8
|
import { IEventSequence } from './IEventSequence.js';
|
|
@@ -21,14 +23,16 @@ export class TransactionalEventSequence implements ITransactionalEventSequence {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/** @inheritdoc */
|
|
24
|
-
async append(eventSourceId: string, event: object): Promise<void> {
|
|
25
|
-
this.unitOfWork.addEvent(this._eventSequence.id, eventSourceId, event);
|
|
26
|
+
async append(eventSourceId: string, event: object, options?: TransactionalAppendOptions): Promise<void> {
|
|
27
|
+
this.unitOfWork.addEvent(this._eventSequence.id, eventSourceId, event, options);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/** @inheritdoc */
|
|
29
|
-
async appendMany(eventSourceId: string, events: object[]): Promise<void> {
|
|
31
|
+
async appendMany(eventSourceId: string, events: object[], options?: TransactionalAppendOptions): Promise<void> {
|
|
32
|
+
// Validate once up front so an invalid tag adds none of the events.
|
|
33
|
+
const shared = options?.namedTags === undefined ? options : { ...options, namedTags: mergeNamedTags(options.namedTags) };
|
|
30
34
|
for (const event of events) {
|
|
31
|
-
this.unitOfWork.addEvent(this._eventSequence.id, eventSourceId, event);
|
|
35
|
+
this.unitOfWork.addEvent(this._eventSequence.id, eventSourceId, event, shared);
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { getEventTypeFor } from '../events/eventTypeDecorator.js';
|
|
5
5
|
import { Tag } from '../events/Tag.js';
|
|
6
|
+
import type { NamedTag } from '../events/NamedTag.js';
|
|
6
7
|
import type { CausationEntry } from '../events/CausationEntry.js';
|
|
7
8
|
import type { AppendResult } from './AppendResult.js';
|
|
8
9
|
import type { AppendedEventWithResult } from './AppendedEventWithResult.js';
|
|
@@ -17,7 +18,7 @@ export function mapAppendNotificationCausation(chain: readonly Causation[]): Cau
|
|
|
17
18
|
/** Maps a successful append into the client-side appendOperations notification shape. */
|
|
18
19
|
export function createAppendNotification(
|
|
19
20
|
eventSourceId: string, event: object, result: AppendResult, correlationId: string,
|
|
20
|
-
causation: CausationEntry[], tags: readonly string[], occurredAt = new Date()
|
|
21
|
+
causation: CausationEntry[], tags: readonly string[], occurredAt = new Date(), namedTags: readonly NamedTag[] = []
|
|
21
22
|
): AppendedEventWithResult {
|
|
22
23
|
const eventType = getEventTypeFor(event.constructor);
|
|
23
24
|
return {
|
|
@@ -25,7 +26,8 @@ export function createAppendNotification(
|
|
|
25
26
|
context: {
|
|
26
27
|
sequenceNumber: result.sequenceNumber.value, eventSourceId, eventType, occurred: occurredAt, correlationId,
|
|
27
28
|
causation,
|
|
28
|
-
tags: tags.map(value => new Tag(value))
|
|
29
|
+
tags: tags.map(value => new Tag(value)),
|
|
30
|
+
namedTags: [...namedTags]
|
|
29
31
|
},
|
|
30
32
|
eventType,
|
|
31
33
|
content: event as Record<string, unknown>
|
|
@@ -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
|
+
import { NamedTagsWithRegisteredEventSourceNotSupported } from './NamedTagsWithRegisteredEventSourceNotSupported.js';
|
|
5
|
+
|
|
6
|
+
/** Rejects the combination before any RPC, even when tags and registered routing belong to different entries. */
|
|
7
|
+
export function ensureNamedTagBatchIsSupported(hasNamedTags: boolean, hasRegisteredEventSource: boolean): void {
|
|
8
|
+
if (hasNamedTags && hasRegisteredEventSource) {
|
|
9
|
+
throw new NamedTagsWithRegisteredEventSourceNotSupported();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { afterAll, beforeAll, chai, describe, it } from 'vitest';
|
|
6
|
+
import { field } from '@cratis/fundamentals';
|
|
7
|
+
import { ObserverRunningState } from '@cratis/chronicle.contracts';
|
|
8
|
+
import { ChronicleClient, ChronicleOptions, NamedTag, eventType, reactor, type AppendedEvent, type EventContext,
|
|
9
|
+
type IClientArtifactsProvider, type IEventStore } from '../../../index.js';
|
|
10
|
+
import type { ChronicleConnection } from '../../../connection/index.js';
|
|
11
|
+
|
|
12
|
+
chai.should();
|
|
13
|
+
function should(value: unknown): ReturnType<typeof chai.expect> {
|
|
14
|
+
return (value as { should: ReturnType<typeof chai.expect> }).should;
|
|
15
|
+
}
|
|
16
|
+
const connectionString = process.env.CHRONICLE_INTEGRATION_CONNECTION_STRING;
|
|
17
|
+
|
|
18
|
+
@eventType()
|
|
19
|
+
class NamedTagsItemImported {
|
|
20
|
+
@field(String) sku = '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const delivered = new Map<string, EventContext>();
|
|
24
|
+
@reactor()
|
|
25
|
+
class NamedTagsImportTracker {
|
|
26
|
+
namedTagsItemImported(event: NamedTagsItemImported, context: EventContext) {
|
|
27
|
+
delivered.set(event.sku, context);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const artifacts: IClientArtifactsProvider = {
|
|
32
|
+
eventTypes: [NamedTagsItemImported], reactors: [NamedTagsImportTracker], reducers: [], readModels: [], seeders: [],
|
|
33
|
+
constraints: [], projections: [], webhooks: [], eventTypeMigrations: [], globalForHandlers: []
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
async function eventually(accept: () => boolean | Promise<boolean>, timeoutMs = 30_000): Promise<void> {
|
|
37
|
+
const deadline = Date.now() + timeoutMs;
|
|
38
|
+
while (!await accept()) {
|
|
39
|
+
if (Date.now() > deadline) throw new Error('Timed out waiting for the kernel');
|
|
40
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pairs = (context: { namedTags?: ReadonlyArray<NamedTag> }) => (context.namedTags ?? []).map(tag => `${tag.name}=${tag.value}`);
|
|
45
|
+
const item = (sku: string) => Object.assign(new NamedTagsItemImported(), { sku });
|
|
46
|
+
|
|
47
|
+
describe.skipIf(!connectionString && !process.env.CI)('when appending named tags against a kernel', () => {
|
|
48
|
+
const storeName = `NamedTags${randomUUID().replaceAll('-', '').slice(0, 12)}`;
|
|
49
|
+
const source = randomUUID();
|
|
50
|
+
let client: ChronicleClient;
|
|
51
|
+
let store: IEventStore;
|
|
52
|
+
let stored: Map<string, AppendedEvent>;
|
|
53
|
+
|
|
54
|
+
beforeAll(async () => {
|
|
55
|
+
client = new ChronicleClient(ChronicleOptions.fromConnectionString(connectionString!, {
|
|
56
|
+
discoveryPatterns: [], clientArtifactsProvider: artifacts
|
|
57
|
+
}));
|
|
58
|
+
store = await client.getEventStore(storeName);
|
|
59
|
+
const connection = (store as unknown as { _connection: ChronicleConnection })._connection;
|
|
60
|
+
await eventually(async () => {
|
|
61
|
+
const observer = await connection.observers.getObserverInformation({ EventStore: storeName, Namespace: 'Default',
|
|
62
|
+
ObserverId: NamedTagsImportTracker.name, EventSequenceId: 'event-log' });
|
|
63
|
+
return observer.IsSubscribed && observer.RunningState === ObserverRunningState.Active;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const single = await store.eventLog.append(source, item('single'), {
|
|
67
|
+
tags: ['import'],
|
|
68
|
+
namedTags: [new NamedTag('import-batch', 'b-1'), new NamedTag('import-batch', 'B-1'), new NamedTag('empty', ''),
|
|
69
|
+
new NamedTag('import-batch', 'b-1')]
|
|
70
|
+
});
|
|
71
|
+
should(single.isSuccess).be.true;
|
|
72
|
+
const batch = await store.eventLog.appendMany([
|
|
73
|
+
{ eventSourceId: source, event: item('debit'), namedTags: [new NamedTag('side', 'debit')] },
|
|
74
|
+
{ eventSourceId: source, event: item('credit'), namedTags: [new NamedTag('side', 'credit'), new NamedTag('transfer', 't-1')] }
|
|
75
|
+
], { namedTags: [new NamedTag('transfer', 't-1')] });
|
|
76
|
+
should(batch.every(result => result.isSuccess)).be.true;
|
|
77
|
+
const untagged = await store.eventLog.append(source, item('untagged'));
|
|
78
|
+
should(untagged.isSuccess).be.true;
|
|
79
|
+
|
|
80
|
+
const unitOfWork = store.unitOfWorkManager.begin();
|
|
81
|
+
await store.eventLog.transactional.append(source, item('transactional'), { namedTags: [new NamedTag('unit', 'u-1')] });
|
|
82
|
+
await unitOfWork.commit();
|
|
83
|
+
should(unitOfWork.isSuccess).be.true;
|
|
84
|
+
|
|
85
|
+
const events = await store.eventLog.getForEventSourceIdAndEventTypes(source, [NamedTagsItemImported]);
|
|
86
|
+
stored = new Map(events.map(event => [(event.content as { sku: string }).sku, event]));
|
|
87
|
+
await eventually(() => delivered.has('transactional'));
|
|
88
|
+
});
|
|
89
|
+
afterAll(() => client?.dispose());
|
|
90
|
+
|
|
91
|
+
it('should round-trip distinct single-append named tags in order through a read', () =>
|
|
92
|
+
should(pairs(stored.get('single')!.context)).deep.equal(['import-batch=b-1', 'import-batch=B-1', 'empty=']));
|
|
93
|
+
it('should keep plain tags beside named tags', () =>
|
|
94
|
+
should(stored.get('single')!.context.tags.map(tag => tag.value)).deep.equal(['import']));
|
|
95
|
+
it('should store each batch event with its own tags followed by the shared tags', () => {
|
|
96
|
+
should(pairs(stored.get('debit')!.context)).deep.equal(['side=debit', 'transfer=t-1']);
|
|
97
|
+
should(pairs(stored.get('credit')!.context)).deep.equal(['side=credit', 'transfer=t-1']);
|
|
98
|
+
});
|
|
99
|
+
it('should store an untagged event with no named tags', () => should(pairs(stored.get('untagged')!.context)).deep.equal([]));
|
|
100
|
+
it('should store named tags added through a unit of work', () =>
|
|
101
|
+
should(pairs(stored.get('transactional')!.context)).deep.equal(['unit=u-1']));
|
|
102
|
+
it('should deliver the named tags to reactors in the event context', () => {
|
|
103
|
+
should(pairs(delivered.get('single')!)).deep.equal(['import-batch=b-1', 'import-batch=B-1', 'empty=']);
|
|
104
|
+
should(pairs(delivered.get('credit')!)).deep.equal(['side=credit', 'transfer=t-1']);
|
|
105
|
+
should(pairs(delivered.get('untagged')!)).deep.equal([]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { vi } from 'vitest';
|
|
5
|
+
import { EventSequence, EventSequenceId } from '../../index.js';
|
|
6
|
+
import { eventType } from '../../../events/index.js';
|
|
7
|
+
import type { ChronicleConnection } from '../../../connection/index.js';
|
|
8
|
+
import type { IUnitOfWorkManager } from '../../../transactions/index.js';
|
|
9
|
+
import { ConcurrencyDimensions, type IEventSources } from '../../../eventSources/index.js';
|
|
10
|
+
|
|
11
|
+
export class Account {}
|
|
12
|
+
|
|
13
|
+
/** A registered source, optionally requiring a derived tail read. */
|
|
14
|
+
export function registered_sources(concurrency = ConcurrencyDimensions.none): IEventSources {
|
|
15
|
+
return {
|
|
16
|
+
getFor: () => ({ type: Account, name: 'Account', description: '', concurrency, streams: [] })
|
|
17
|
+
} as unknown as IEventSources;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class NamedTagRecorded { constructor(readonly value = 'payload') {} }
|
|
21
|
+
eventType('NamedTagRecorded')(NamedTagRecorded);
|
|
22
|
+
|
|
23
|
+
/** An event sequence whose four append calls are recorded. */
|
|
24
|
+
export function a_sequence(eventSources?: IEventSources) {
|
|
25
|
+
const single = { Response: { SequenceNumber: 0n, ConstraintViolations: [], Errors: [] } };
|
|
26
|
+
const batch = async (request: { Events: unknown[] }) =>
|
|
27
|
+
({ Response: { SequenceNumbers: request.Events.map((_, index) => BigInt(index)), ConstraintViolations: [], Errors: [] } });
|
|
28
|
+
const append = vi.fn().mockResolvedValue(single);
|
|
29
|
+
const appendWithNamedTags = vi.fn().mockResolvedValue(single);
|
|
30
|
+
const appendManyForEventSources = vi.fn().mockImplementation(batch);
|
|
31
|
+
const appendManyForEventSourcesWithNamedTags = vi.fn().mockImplementation(batch);
|
|
32
|
+
const tailSequenceNumber = vi.fn().mockResolvedValue({ Data: { SequenceNumber: 0n } });
|
|
33
|
+
const connection = { eventSequences: { append, appendWithNamedTags, appendManyForEventSources, appendManyForEventSourcesWithNamedTags, tailSequenceNumber } } as unknown as ChronicleConnection;
|
|
34
|
+
return {
|
|
35
|
+
sequence: new EventSequence(EventSequenceId.eventLog, 'store', 'tenant', connection, {} as IUnitOfWorkManager, undefined, undefined, eventSources),
|
|
36
|
+
append, appendWithNamedTags, appendManyForEventSources, appendManyForEventSourcesWithNamedTags, tailSequenceNumber
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import type { AppendManyForEventSourcesWithNamedTagsRequest } from '@cratis/chronicle.contracts';
|
|
6
|
+
import { NamedTag } from '../../../events/index.js';
|
|
7
|
+
import { a_sequence, NamedTagRecorded } from './given.fixture.js';
|
|
8
|
+
|
|
9
|
+
chai.should();
|
|
10
|
+
|
|
11
|
+
describe('when appending a batch with per-event and shared named tags', () => {
|
|
12
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
13
|
+
let request: AppendManyForEventSourcesWithNamedTagsRequest;
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
calls = a_sequence();
|
|
16
|
+
await calls.sequence.appendMany([
|
|
17
|
+
{ eventSourceId: 'from', event: new NamedTagRecorded(), namedTags: [new NamedTag('side', 'debit'), new NamedTag('transfer', 't-1')] },
|
|
18
|
+
{ eventSourceId: 'to', event: new NamedTagRecorded(), namedTags: [new NamedTag('side', 'credit')] },
|
|
19
|
+
{ eventSourceId: 'audit', event: new NamedTagRecorded() }
|
|
20
|
+
], { namedTags: [new NamedTag('transfer', 't-1')] });
|
|
21
|
+
request = calls.appendManyForEventSourcesWithNamedTags.mock.calls[0][0];
|
|
22
|
+
});
|
|
23
|
+
it('should use the dedicated named-tag batch call', () => calls.appendManyForEventSources.mock.calls.should.have.lengthOf(0));
|
|
24
|
+
it('should send each event its own tags before the shared tags without duplicates', () =>
|
|
25
|
+
request.Events.map(event => event.NamedTags.map(tag => `${tag.Name}=${tag.Value}`)).should.deep.equal([
|
|
26
|
+
['side=debit', 'transfer=t-1'], ['side=credit', 'transfer=t-1'], ['transfer=t-1']
|
|
27
|
+
]));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('when appending many events for one source with shared named tags', () => {
|
|
31
|
+
let request: AppendManyForEventSourcesWithNamedTagsRequest;
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
const calls = a_sequence();
|
|
34
|
+
await calls.sequence.appendMany('source', [new NamedTagRecorded(), new NamedTagRecorded()], { namedTags: [new NamedTag('import', 'i-1')] });
|
|
35
|
+
request = calls.appendManyForEventSourcesWithNamedTags.mock.calls[0][0];
|
|
36
|
+
});
|
|
37
|
+
it('should give every event the shared named tags', () =>
|
|
38
|
+
request.Events.map(event => event.NamedTags).should.deep.equal([[{ Name: 'import', Value: 'i-1' }], [{ Name: 'import', Value: 'i-1' }]]));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('when appending a batch without named tags', () => {
|
|
42
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
calls = a_sequence();
|
|
45
|
+
await calls.sequence.appendMany([{ eventSourceId: 'one', event: new NamedTagRecorded(), namedTags: [] }]);
|
|
46
|
+
});
|
|
47
|
+
it('should keep using the plain batch call', () => calls.appendManyForEventSources.mock.calls.should.have.lengthOf(1));
|
|
48
|
+
it('should not use the named-tag batch call', () => calls.appendManyForEventSourcesWithNamedTags.mock.calls.should.have.lengthOf(0));
|
|
49
|
+
});
|
package/eventSequences/for_EventSequence/when_appending_named_tags/with_a_single_event.spec.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import { AppendWithNamedTagsRequest } from '@cratis/chronicle.contracts';
|
|
6
|
+
import { NamedTag } from '../../../events/index.js';
|
|
7
|
+
import type { AppendedEventWithResult } from '../../AppendedEventWithResult.js';
|
|
8
|
+
import { a_sequence, NamedTagRecorded } from './given.fixture.js';
|
|
9
|
+
|
|
10
|
+
chai.should();
|
|
11
|
+
|
|
12
|
+
describe('when appending a single event with named tags', () => {
|
|
13
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
14
|
+
let request: AppendWithNamedTagsRequest;
|
|
15
|
+
let notification: AppendedEventWithResult;
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
calls = a_sequence();
|
|
18
|
+
const iterator = calls.sequence.appendOperations[Symbol.asyncIterator]();
|
|
19
|
+
const next = iterator.next();
|
|
20
|
+
await calls.sequence.append('source', new NamedTagRecorded(), {
|
|
21
|
+
tags: ['checkout'],
|
|
22
|
+
namedTags: [new NamedTag('session', 's-1'), new NamedTag('session', 's-1'), new NamedTag('channel', 'web')]
|
|
23
|
+
});
|
|
24
|
+
notification = (await next).value[0];
|
|
25
|
+
await iterator.return?.();
|
|
26
|
+
request = calls.appendWithNamedTags.mock.calls[0][0];
|
|
27
|
+
});
|
|
28
|
+
it('should use the dedicated named-tag call instead of the plain append', () => calls.append.mock.calls.should.have.lengthOf(0));
|
|
29
|
+
it('should send the distinct named tags in order', () =>
|
|
30
|
+
request.NamedTags.should.deep.equal([{ Name: 'session', Value: 's-1' }, { Name: 'channel', Value: 'web' }]));
|
|
31
|
+
it('should keep the plain tags beside the named tags', () => request.Tags.should.deep.equal(['checkout']));
|
|
32
|
+
it('should encode through the published contract', () =>
|
|
33
|
+
AppendWithNamedTagsRequest.decode(AppendWithNamedTagsRequest.encode(AppendWithNamedTagsRequest.fromPartial(request)).finish())
|
|
34
|
+
.NamedTags.should.have.lengthOf(2));
|
|
35
|
+
it('should carry the named tags on the append notification context', () =>
|
|
36
|
+
notification.event.context.namedTags!.map(tag => tag.name).should.deep.equal(['session', 'channel']));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('when appending a single event with an empty named tag list', () => {
|
|
40
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
calls = a_sequence();
|
|
43
|
+
await calls.sequence.append('source', new NamedTagRecorded(), { namedTags: [] });
|
|
44
|
+
});
|
|
45
|
+
it('should use the plain append call', () => calls.append.mock.calls.should.have.lengthOf(1));
|
|
46
|
+
it('should not use the named-tag call', () => calls.appendWithNamedTags.mock.calls.should.have.lengthOf(0));
|
|
47
|
+
});
|