@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
package/eventSequences/for_EventSequence/when_appending_named_tags/with_an_invalid_tag.spec.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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 { InvalidNamedTag, type NamedTag } from '../../../events/index.js';
|
|
6
|
+
import { a_sequence, NamedTagRecorded } from './given.fixture.js';
|
|
7
|
+
|
|
8
|
+
chai.should();
|
|
9
|
+
const blank = { name: '', value: 'x' } as NamedTag;
|
|
10
|
+
|
|
11
|
+
describe('when appending a single event with an invalid named tag', () => {
|
|
12
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
13
|
+
let error: unknown;
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
calls = a_sequence();
|
|
16
|
+
error = await calls.sequence.append('source', new NamedTagRecorded(), { namedTags: [blank] }).catch(caught => caught);
|
|
17
|
+
});
|
|
18
|
+
it('should fail with an invalid named tag error', () => (error as Error).should.be.instanceOf(InvalidNamedTag));
|
|
19
|
+
it('should not call the kernel', () =>
|
|
20
|
+
(calls.append.mock.calls.length + calls.appendWithNamedTags.mock.calls.length).should.equal(0));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('when appending a batch where one entry has an invalid named tag', () => {
|
|
24
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
25
|
+
let error: unknown;
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
calls = a_sequence();
|
|
28
|
+
error = await calls.sequence.appendMany([
|
|
29
|
+
{ eventSourceId: 'one', event: new NamedTagRecorded() },
|
|
30
|
+
{ eventSourceId: 'two', event: new NamedTagRecorded(), namedTags: [null as unknown as NamedTag] }
|
|
31
|
+
]).catch(caught => caught);
|
|
32
|
+
});
|
|
33
|
+
it('should fail with an invalid named tag error', () => (error as Error).should.be.instanceOf(InvalidNamedTag));
|
|
34
|
+
it('should not append any of the events', () =>
|
|
35
|
+
(calls.appendManyForEventSources.mock.calls.length + calls.appendManyForEventSourcesWithNamedTags.mock.calls.length).should.equal(0));
|
|
36
|
+
});
|
package/eventSequences/for_EventSequence/when_appending_named_tags/with_registered_sources.spec.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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 { ConcurrencyDimensions } from '../../../eventSources/index.js';
|
|
6
|
+
import { NamedTag } from '../../../events/index.js';
|
|
7
|
+
import { NamedTagsWithRegisteredEventSourceNotSupported, type AppendOptions, type EventForEventSourceId } from '../../index.js';
|
|
8
|
+
import { Account, a_sequence, NamedTagRecorded, registered_sources } from './given.fixture.js';
|
|
9
|
+
|
|
10
|
+
chai.should();
|
|
11
|
+
const namedTags = [new NamedTag('batch', 'b-1')];
|
|
12
|
+
const entry = (options: Partial<EventForEventSourceId> = {}): EventForEventSourceId =>
|
|
13
|
+
({ eventSourceId: 'source', event: new NamedTagRecorded(), ...options });
|
|
14
|
+
|
|
15
|
+
const batches: Array<{ description: string; events: EventForEventSourceId[]; options?: AppendOptions }> = [
|
|
16
|
+
{ description: 'tags and a registered source on the same entry', events: [entry({ eventSource: Account, namedTags })] },
|
|
17
|
+
{ description: 'a tagged entry before an untagged registered entry', events: [entry({ namedTags }), entry({ eventSource: 'Account' })] },
|
|
18
|
+
{ description: 'an untagged registered entry before a tagged entry', events: [entry({ eventSource: 'Account' }), entry({ namedTags })] },
|
|
19
|
+
{ description: 'shared tags and a registered entry', events: [entry({ eventSource: 'Account' })], options: { namedTags } },
|
|
20
|
+
{ description: 'per-entry tags and a shared registered source', events: [entry({ namedTags })], options: { eventSource: Account } }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const batch of batches) {
|
|
24
|
+
describe(`when appending a batch with ${batch.description}`, () => {
|
|
25
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
26
|
+
let error: unknown;
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
calls = a_sequence(registered_sources(ConcurrencyDimensions.eventSourceId));
|
|
29
|
+
error = await calls.sequence.appendMany(batch.events, batch.options).catch(caught => caught);
|
|
30
|
+
});
|
|
31
|
+
it('should reject with the typed unsupported combination error', () =>
|
|
32
|
+
(error as Error).should.be.instanceOf(NamedTagsWithRegisteredEventSourceNotSupported));
|
|
33
|
+
it('should explain the kernel limitation', () => (error as Error).message.should.contain('drops registered event source routing'));
|
|
34
|
+
it('should link to the kernel issue', () => (error as Error).message.should.contain('https://github.com/Cratis/Chronicle/issues/4603'));
|
|
35
|
+
it('should not send a plain batch', () => calls.appendManyForEventSources.mock.calls.should.have.lengthOf(0));
|
|
36
|
+
it('should not send a named-tag batch', () => calls.appendManyForEventSourcesWithNamedTags.mock.calls.should.have.lengthOf(0));
|
|
37
|
+
it('should not read a tail to derive a guard', () => calls.tailSequenceNumber.mock.calls.should.have.lengthOf(0));
|
|
38
|
+
it('should not fall back to single appends', () =>
|
|
39
|
+
[...calls.append.mock.calls, ...calls.appendWithNamedTags.mock.calls].should.have.lengthOf(0));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('when appending a same-source batch with shared named tags and registered routing', () => {
|
|
44
|
+
let error: unknown;
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
const calls = a_sequence(registered_sources());
|
|
47
|
+
error = await calls.sequence.appendMany('source', [new NamedTagRecorded()], { eventSource: Account, namedTags }).catch(caught => caught);
|
|
48
|
+
});
|
|
49
|
+
it('should reject with the typed unsupported combination error', () =>
|
|
50
|
+
(error as Error).should.be.instanceOf(NamedTagsWithRegisteredEventSourceNotSupported));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('when appending a registered-source batch with empty named tags', () => {
|
|
54
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
calls = a_sequence(registered_sources());
|
|
57
|
+
await calls.sequence.appendMany([entry({ eventSource: Account, namedTags: [] })], { namedTags: [] });
|
|
58
|
+
});
|
|
59
|
+
it('should retain registered routing on the plain batch call', () =>
|
|
60
|
+
calls.appendManyForEventSources.mock.calls[0][0].Events[0].EventSource.should.equal('Account'));
|
|
61
|
+
it('should not send a named-tag batch', () => calls.appendManyForEventSourcesWithNamedTags.mock.calls.should.have.lengthOf(0));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('when appending a single event with named tags and a registered source', () => {
|
|
65
|
+
let calls: ReturnType<typeof a_sequence>;
|
|
66
|
+
beforeEach(async () => {
|
|
67
|
+
calls = a_sequence(registered_sources());
|
|
68
|
+
await calls.sequence.append('source', new NamedTagRecorded(), { eventSource: Account, namedTags });
|
|
69
|
+
});
|
|
70
|
+
it('should retain registered routing on the dedicated single append call', () =>
|
|
71
|
+
calls.appendWithNamedTags.mock.calls[0][0].EventSource.should.equal('Account'));
|
|
72
|
+
it('should retain the named tags on the single append', () =>
|
|
73
|
+
calls.appendWithNamedTags.mock.calls[0][0].NamedTags.should.deep.equal([{ Name: 'batch', Value: 'b-1' }]));
|
|
74
|
+
});
|
package/eventSequences/for_TransactionalEventSequence/when_appending_many_with_named_tags.spec.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it, vi } from 'vitest';
|
|
5
|
+
import { InvalidNamedTag, NamedTag } from '../../events/index.js';
|
|
6
|
+
import type { IUnitOfWorkManager } from '../../transactions/index.js';
|
|
7
|
+
import { EventSequenceId } from '../EventSequenceId.js';
|
|
8
|
+
import type { IEventSequence } from '../IEventSequence.js';
|
|
9
|
+
import { TransactionalEventSequence } from '../TransactionalEventSequence.js';
|
|
10
|
+
|
|
11
|
+
chai.should();
|
|
12
|
+
|
|
13
|
+
function a_transactional_sequence() {
|
|
14
|
+
const addEvent = vi.fn();
|
|
15
|
+
const manager = { current: { addEvent } } as unknown as IUnitOfWorkManager;
|
|
16
|
+
return { sequence: new TransactionalEventSequence({ id: EventSequenceId.eventLog } as IEventSequence, manager), addEvent };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('when appending many events transactionally with shared named tags', () => {
|
|
20
|
+
let addEvent: ReturnType<typeof vi.fn>;
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
const given = a_transactional_sequence();
|
|
23
|
+
addEvent = given.addEvent;
|
|
24
|
+
await given.sequence.appendMany('source', [{ one: 1 }, { two: 2 }], { eventSource: 'Account', namedTags: [new NamedTag('import', 'i-1')] });
|
|
25
|
+
});
|
|
26
|
+
it('should add every event with the shared named tags and routing', () =>
|
|
27
|
+
addEvent.mock.calls.map(call => [call[3].eventSource, call[3].namedTags.map((tag: NamedTag) => tag.value)])
|
|
28
|
+
.should.deep.equal([['Account', ['i-1']], ['Account', ['i-1']]]));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('when appending many events transactionally with an invalid named tag', () => {
|
|
32
|
+
let addEvent: ReturnType<typeof vi.fn>;
|
|
33
|
+
let error: unknown;
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
const given = a_transactional_sequence();
|
|
36
|
+
addEvent = given.addEvent;
|
|
37
|
+
error = await given.sequence.appendMany('source', [{ one: 1 }], { namedTags: [{ name: '', value: '' } as NamedTag] }).catch(caught => caught);
|
|
38
|
+
});
|
|
39
|
+
it('should fail with an invalid named tag error', () => (error as Error).should.be.instanceOf(InvalidNamedTag));
|
|
40
|
+
it('should not add any event', () => addEvent.mock.calls.should.have.lengthOf(0));
|
|
41
|
+
});
|
package/eventSequences/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type { AppendResult } from './AppendResult.js';
|
|
|
10
10
|
export type { AppendedEventWithResult } from './AppendedEventWithResult.js';
|
|
11
11
|
export type { AppendOptions } from './AppendOptions.js';
|
|
12
12
|
export { CompleteStreamError } from './CompleteStreamError.js';
|
|
13
|
+
export { NamedTagsWithRegisteredEventSourceNotSupported } from './NamedTagsWithRegisteredEventSourceNotSupported.js';
|
|
13
14
|
export type { CompleteStreamResult } from './CompleteStreamResult.js';
|
|
14
15
|
export type { ConcurrencyScope } from './ConcurrencyScope.js';
|
|
15
16
|
export type { EventForEventSourceId } from './EventForEventSourceId.js';
|
|
@@ -7,6 +7,7 @@ import { correlationIdManager } from '../correlation/index.js';
|
|
|
7
7
|
import { getEventTypeFor } from '../events/eventTypeDecorator.js';
|
|
8
8
|
import { getTagsFor } from '../events/tagDecorator.js';
|
|
9
9
|
import { mergeTags } from '../events/mergeTags.js';
|
|
10
|
+
import { mergeNamedTags } from '../events/mergeNamedTags.js';
|
|
10
11
|
import { identityProvider } from '../identity/index.js';
|
|
11
12
|
import { resolveEventSubject } from '../compliance/resolveEventSubject.js';
|
|
12
13
|
import type { AppendOptions } from './AppendOptions.js';
|
|
@@ -45,6 +46,8 @@ export function prepareBatchAppend(
|
|
|
45
46
|
for (const { eventSourceId } of eventsForEventSourceIds) {
|
|
46
47
|
if (!concurrencyScopes.has(eventSourceId)) concurrencyScopes.set(eventSourceId, appendOptions?.concurrencyScope);
|
|
47
48
|
}
|
|
49
|
+
// Validate every event's named tags before anything is sent; the order of entries is preserved.
|
|
50
|
+
const namedTags = eventsForEventSourceIds.map(({ namedTags: instanceNamedTags }) => mergeNamedTags(instanceNamedTags, appendOptions?.namedTags));
|
|
48
51
|
const eventsToAppend = eventsForEventSourceIds.map(({ eventSourceId, event, eventStreamType, eventStreamId, eventSourceType, subject, occurred, tags: instanceTags }) => {
|
|
49
52
|
const eventType = getEventTypeFor(event.constructor as Function);
|
|
50
53
|
const tags = mergeTags(getTagsFor(event.constructor as Function), instanceTags, appendOptions?.tags);
|
|
@@ -60,5 +63,5 @@ export function prepareBatchAppend(
|
|
|
60
63
|
Subject: subject ?? appendOptions?.subject ?? resolveEventSubject(event) ?? eventSourceId
|
|
61
64
|
};
|
|
62
65
|
});
|
|
63
|
-
return { eventsForEventSourceIds, appendOptions, correlationId, batchCausationChain, identity, concurrencyScopes, eventsToAppend };
|
|
66
|
+
return { eventsForEventSourceIds, appendOptions, correlationId, batchCausationChain, identity, concurrencyScopes, eventsToAppend, namedTags };
|
|
64
67
|
}
|
|
@@ -5,6 +5,7 @@ import { Guid, JsonSerializer } from '@cratis/fundamentals';
|
|
|
5
5
|
import { getEventTypeFor } from '../events/eventTypeDecorator.js';
|
|
6
6
|
import { getTagsFor } from '../events/tagDecorator.js';
|
|
7
7
|
import { mergeTags } from '../events/mergeTags.js';
|
|
8
|
+
import { mergeNamedTags } from '../events/mergeNamedTags.js';
|
|
8
9
|
import { identityProvider } from '../identity/index.js';
|
|
9
10
|
import { causationManager, CausationType } from '../auditing/index.js';
|
|
10
11
|
import { correlationIdManager } from '../correlation/index.js';
|
|
@@ -20,8 +21,9 @@ export function prepareSingleAppend(event: object, options?: AppendOptions) {
|
|
|
20
21
|
const subject = options?.subject ?? resolveEventSubject(event);
|
|
21
22
|
const content = JsonSerializer.serialize(event);
|
|
22
23
|
const tags = mergeTags(getTagsFor(event.constructor as Function), options?.tags);
|
|
24
|
+
const namedTags = mergeNamedTags(options?.namedTags);
|
|
23
25
|
const causationChain = causationManager.run(CausationType.appendEvent, { eventType: eventType.id.value },
|
|
24
26
|
() => causationManager.getCurrentChain());
|
|
25
27
|
const identity = identityProvider.getCurrent();
|
|
26
|
-
return { eventType, correlationId, content, tags, causationChain, identity, subject };
|
|
28
|
+
return { eventType, correlationId, content, tags, namedTags, causationChain, identity, subject };
|
|
27
29
|
}
|
|
@@ -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
|
+
}
|