@cratis/chronicle 6.49.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/dist/eventSequences/AppendOptions.d.ts +10 -0
- package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
- package/dist/eventSequences/EventForEventSourceId.d.ts +6 -0
- package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +28 -8
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventSequences/ITransactionalEventSequence.d.ts +5 -5
- 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 -3
- 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/events/EventContext.d.ts +7 -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/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 +3 -1
- package/dist/events/toClientEventContext.js.map +1 -1
- package/dist/projections/eventContextPropertyExpression.d.ts.map +1 -1
- package/dist/projections/eventContextPropertyExpression.js +2 -1
- 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/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 -3
- 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/UnitOfWork.d.ts +2 -2
- package/dist/transactions/UnitOfWork.d.ts.map +1 -1
- package/dist/transactions/UnitOfWork.js +9 -3
- package/dist/transactions/UnitOfWork.js.map +1 -1
- package/dist/transactions/index.d.ts +1 -0
- package/dist/transactions/index.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eventSequences/AppendOptions.ts +11 -0
- package/eventSequences/EventForEventSourceId.ts +7 -0
- package/eventSequences/EventSequence.ts +30 -8
- package/eventSequences/ITransactionalEventSequence.ts +5 -5
- package/eventSequences/NamedTagsWithRegisteredEventSourceNotSupported.ts +12 -0
- package/eventSequences/TransactionalEventSequence.ts +8 -5
- 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/events/EventContext.ts +8 -0
- package/events/InvalidNamedTag.ts +13 -0
- package/events/NamedTag.ts +44 -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 +3 -1
- package/package.json +5 -5
- package/projections/eventContextPropertyExpression.ts +3 -2
- 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/singleAppendRoute.ts +13 -1
- package/transactions/IUnitOfWork.ts +4 -3
- package/transactions/TransactionalAppendOptions.ts +17 -0
- package/transactions/UnitOfWork.ts +13 -4
- package/transactions/for_UnitOfWork/when_committing_events_with_named_tags.spec.ts +112 -0
- package/transactions/index.ts +1 -0
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
|
}
|
package/events/EventContext.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { EventType } from './EventType.js';
|
|
5
5
|
import { CausationEntry } from './CausationEntry.js';
|
|
6
6
|
import { Tag } from './Tag.js';
|
|
7
|
+
import type { NamedTag } from './NamedTag.js';
|
|
7
8
|
import type { Identity } from '../identity/Identity.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -63,4 +64,11 @@ export interface EventContext {
|
|
|
63
64
|
|
|
64
65
|
/** The tags the event carries. */
|
|
65
66
|
readonly tags: ReadonlyArray<Tag>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The structured named tags the event carries, in the order the kernel stored them.
|
|
70
|
+
* Always present on events read from or delivered by the kernel (empty when the event has none);
|
|
71
|
+
* optional so that contexts constructed by hand remain valid.
|
|
72
|
+
*/
|
|
73
|
+
readonly namedTags?: ReadonlyArray<NamedTag>;
|
|
66
74
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
* The error that is thrown when a named tag has a blank name or a non-string value, or a named tag
|
|
6
|
+
* collection contains an entry that is not a named tag. Mirrors the .NET client's `InvalidNamedTag`.
|
|
7
|
+
*/
|
|
8
|
+
export class InvalidNamedTag extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('Named tags require a nonblank name and a non-null value; named tag collections cannot contain null elements.');
|
|
11
|
+
this.name = 'InvalidNamedTag';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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 { InvalidNamedTag } from './InvalidNamedTag.js';
|
|
5
|
+
|
|
6
|
+
// Matches .NET string.IsNullOrWhiteSpace, which also treats U+0085 and other Unicode white space as blank.
|
|
7
|
+
const blank = /^\p{White_Space}*$/u;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Associates a name with an exact, opaque event tag value. Names and values are compared exactly
|
|
11
|
+
* (ordinal and case-sensitive); one name may carry several values on the same event.
|
|
12
|
+
* Mirrors the .NET client's `NamedTag`.
|
|
13
|
+
*/
|
|
14
|
+
export class NamedTag {
|
|
15
|
+
/** The name of the tag. Never blank. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
|
|
18
|
+
/** The exact, opaque value of the tag. May be empty. */
|
|
19
|
+
readonly value: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initializes a new instance of {@link NamedTag}.
|
|
23
|
+
* @param name - The name. Must be a nonblank string.
|
|
24
|
+
* @param value - The opaque value. Must be a string; the empty string is allowed.
|
|
25
|
+
* @throws {@link InvalidNamedTag} when the name is blank or not a string, or the value is not a string.
|
|
26
|
+
*/
|
|
27
|
+
constructor(name: string, value: string) {
|
|
28
|
+
if (typeof name !== 'string' || blank.test(name) || typeof value !== 'string') {
|
|
29
|
+
throw new InvalidNamedTag();
|
|
30
|
+
}
|
|
31
|
+
this.name = name;
|
|
32
|
+
this.value = value;
|
|
33
|
+
Object.freeze(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Determines whether this tag has the same name and value as another, compared exactly.
|
|
38
|
+
* @param other - The tag to compare with.
|
|
39
|
+
* @returns True when both name and value are equal.
|
|
40
|
+
*/
|
|
41
|
+
equals(other: NamedTag): boolean {
|
|
42
|
+
return other instanceof NamedTag && other.name === this.name && other.value === this.value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { chai, describe, it } from 'vitest';
|
|
5
|
+
import { InvalidNamedTag } from '../../InvalidNamedTag.js';
|
|
6
|
+
import { NamedTag } from '../../NamedTag.js';
|
|
7
|
+
|
|
8
|
+
chai.should();
|
|
9
|
+
|
|
10
|
+
describe('when constructing a named tag with invalid values', () => {
|
|
11
|
+
it('should reject an empty name', () => (() => new NamedTag('', 'value')).should.throw(InvalidNamedTag));
|
|
12
|
+
it('should reject a whitespace name', () => (() => new NamedTag(' \t', 'value')).should.throw(InvalidNamedTag));
|
|
13
|
+
it('should reject a name of only next-line characters like .NET', () => (() => new NamedTag('\u0085', 'value')).should.throw(InvalidNamedTag));
|
|
14
|
+
it('should reject a missing name', () => (() => new NamedTag(undefined as unknown as string, 'value')).should.throw(InvalidNamedTag));
|
|
15
|
+
it('should reject a null value', () => (() => new NamedTag('name', null as unknown as string)).should.throw(InvalidNamedTag));
|
|
16
|
+
it('should reject a non-string value', () => (() => new NamedTag('name', 42 as unknown as string)).should.throw(InvalidNamedTag));
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { chai, describe, it } from 'vitest';
|
|
5
|
+
import { NamedTag } from '../../NamedTag.js';
|
|
6
|
+
|
|
7
|
+
chai.should();
|
|
8
|
+
|
|
9
|
+
describe('when constructing a named tag with a name and an empty value', () => {
|
|
10
|
+
const tag = new NamedTag('import-batch', '');
|
|
11
|
+
it('should keep the name exactly', () => tag.name.should.equal('import-batch'));
|
|
12
|
+
it('should keep the empty value', () => tag.value.should.equal(''));
|
|
13
|
+
it('should be immutable', () => Object.isFrozen(tag).should.be.true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('when comparing named tags', () => {
|
|
17
|
+
it('should treat the same name and value as equal', () => new NamedTag('a', 'B').equals(new NamedTag('a', 'B')).should.be.true);
|
|
18
|
+
it('should compare values case-sensitively', () => new NamedTag('a', 'B').equals(new NamedTag('a', 'b')).should.be.false);
|
|
19
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import { mergeNamedTags } from '../../mergeNamedTags.js';
|
|
6
|
+
import { NamedTag } from '../../NamedTag.js';
|
|
7
|
+
|
|
8
|
+
chai.should();
|
|
9
|
+
|
|
10
|
+
describe('when merging event and call named tags', () => {
|
|
11
|
+
let result: NamedTag[];
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
result = mergeNamedTags(
|
|
14
|
+
[new NamedTag('side', 'debit'), new NamedTag('transfer', 't-1')],
|
|
15
|
+
[new NamedTag('transfer', 't-1'), new NamedTag('transfer', 'T-1'), new NamedTag('side', 'credit')]);
|
|
16
|
+
});
|
|
17
|
+
it('should keep the first occurrence of each exact pair in order, including several values per name', () =>
|
|
18
|
+
result.map(tag => [tag.name, tag.value]).should.deep.equal([
|
|
19
|
+
['side', 'debit'], ['transfer', 't-1'], ['transfer', 'T-1'], ['side', 'credit']
|
|
20
|
+
]));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('when merging plain named tag objects', () => {
|
|
24
|
+
let result: NamedTag[];
|
|
25
|
+
beforeEach(() => { result = mergeNamedTags([{ name: 'channel', value: 'web' } as NamedTag]); });
|
|
26
|
+
it('should return validated named tag instances', () => result[0].should.be.instanceOf(NamedTag));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('when merging no named tags', () => {
|
|
30
|
+
it('should return an empty list', () => mergeNamedTags(undefined, null, []).should.deep.equal([]));
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { chai, describe, it } from 'vitest';
|
|
5
|
+
import { InvalidNamedTag } from '../../InvalidNamedTag.js';
|
|
6
|
+
import { mergeNamedTags } from '../../mergeNamedTags.js';
|
|
7
|
+
import type { NamedTag } from '../../NamedTag.js';
|
|
8
|
+
|
|
9
|
+
chai.should();
|
|
10
|
+
|
|
11
|
+
describe('when merging named tags with invalid entries', () => {
|
|
12
|
+
it('should reject a null element', () => (() => mergeNamedTags([null as unknown as NamedTag])).should.throw(InvalidNamedTag));
|
|
13
|
+
it('should reject a plain object with a blank name', () =>
|
|
14
|
+
(() => mergeNamedTags([{ name: ' ', value: 'x' } as NamedTag])).should.throw(InvalidNamedTag));
|
|
15
|
+
it('should reject a string instead of a collection', () =>
|
|
16
|
+
(() => mergeNamedTags('name' as unknown as NamedTag[])).should.throw(InvalidNamedTag));
|
|
17
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import type { EventContext } from '../EventContext.js';
|
|
6
|
+
import { NamedTag } from '../NamedTag.js';
|
|
7
|
+
import { toClientEventContext } from '../toClientEventContext.js';
|
|
8
|
+
import type { WireEventContext } from '../WireEventContext.js';
|
|
9
|
+
|
|
10
|
+
chai.should();
|
|
11
|
+
|
|
12
|
+
function wireContext(namedTags?: { Name: string; Value: string }[]): WireEventContext {
|
|
13
|
+
return {
|
|
14
|
+
EventSourceId: 'source', SequenceNumber: 1n, EventSourceType: 'Default', EventStreamType: 'All', EventStreamId: 'Default',
|
|
15
|
+
EventType: { Id: 'type', Generation: 1, Tombstone: false }, Occurred: { Value: '2025-01-01T00:00:00.000Z' },
|
|
16
|
+
CorrelationId: undefined, Causation: [], CausedBy: undefined, Tags: [], Hash: '', ObservationState: 1, Subject: 'source',
|
|
17
|
+
NamedTags: namedTags
|
|
18
|
+
} as unknown as WireEventContext;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('when converting a delivered context with named tags', () => {
|
|
22
|
+
let context: EventContext;
|
|
23
|
+
beforeEach(() => { context = toClientEventContext(wireContext([{ Name: 'batch', Value: 'b-1' }, { Name: 'batch', Value: '' }])); });
|
|
24
|
+
it('should expose every named tag in delivered order', () =>
|
|
25
|
+
context.namedTags!.map(tag => [tag.name, tag.value]).should.deep.equal([['batch', 'b-1'], ['batch', '']]));
|
|
26
|
+
it('should expose named tag instances', () => context.namedTags![0].should.be.instanceOf(NamedTag));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('when converting a delivered context without named tags', () => {
|
|
30
|
+
it('should expose an empty list', () => toClientEventContext(wireContext()).namedTags!.should.deep.equal([]));
|
|
31
|
+
});
|
package/events/index.ts
CHANGED
|
@@ -21,5 +21,8 @@ export { filterEventsByTag, getFilterTagsFor } from './filterEventsByTagDecorato
|
|
|
21
21
|
export { eventSourceType, getEventSourceTypeFor } from './eventSourceTypeDecorator.js';
|
|
22
22
|
export { eventStreamType, getEventStreamTypeFor } from './eventStreamTypeDecorator.js';
|
|
23
23
|
export { mergeTags } from './mergeTags.js';
|
|
24
|
+
export { NamedTag } from './NamedTag.js';
|
|
25
|
+
export { InvalidNamedTag } from './InvalidNamedTag.js';
|
|
26
|
+
export { mergeNamedTags } from './mergeNamedTags.js';
|
|
24
27
|
export * from './constraints/index.js';
|
|
25
28
|
export * from './migrations/index.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { InvalidNamedTag } from './InvalidNamedTag.js';
|
|
5
|
+
import { NamedTag } from './NamedTag.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates and merges structured named tags - for example the tags carried by an individual event and
|
|
9
|
+
* the tags supplied for a whole append call - into one distinct list. The first occurrence of each exact
|
|
10
|
+
* (ordinal, case-sensitive) name and value pair is kept, in order. Mirrors the .NET client's
|
|
11
|
+
* `NamedTagConverters.Merge`.
|
|
12
|
+
* @param sources - The named tag sources to merge, in precedence order. An undefined or null source contributes nothing.
|
|
13
|
+
* @returns The distinct, validated named tags.
|
|
14
|
+
* @throws {@link InvalidNamedTag} when a source is not iterable, contains an entry that is not an object,
|
|
15
|
+
* or contains a tag with a blank name or a non-string value.
|
|
16
|
+
*/
|
|
17
|
+
export function mergeNamedTags(...sources: ReadonlyArray<Iterable<NamedTag> | undefined | null>): NamedTag[] {
|
|
18
|
+
const seen = new Set<string>();
|
|
19
|
+
const merged: NamedTag[] = [];
|
|
20
|
+
for (const source of sources) {
|
|
21
|
+
if (source === undefined || source === null) continue;
|
|
22
|
+
if (typeof source !== 'object' || typeof (source as Iterable<NamedTag>)[Symbol.iterator] !== 'function') {
|
|
23
|
+
throw new InvalidNamedTag();
|
|
24
|
+
}
|
|
25
|
+
for (const entry of source as Iterable<unknown>) {
|
|
26
|
+
if (entry === null || typeof entry !== 'object') throw new InvalidNamedTag();
|
|
27
|
+
// Reconstruct so plain { name, value } objects are validated exactly like NamedTag instances.
|
|
28
|
+
const tag = entry instanceof NamedTag ? entry
|
|
29
|
+
: new NamedTag(Reflect.get(entry, 'name') as string, Reflect.get(entry, 'value') as string);
|
|
30
|
+
const key = JSON.stringify([tag.name, tag.value]);
|
|
31
|
+
if (seen.has(key)) continue;
|
|
32
|
+
seen.add(key);
|
|
33
|
+
merged.push(tag);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return merged;
|
|
37
|
+
}
|
|
@@ -8,6 +8,7 @@ import { EventType } from './EventType.js';
|
|
|
8
8
|
import { EventTypeGeneration } from './EventTypeGeneration.js';
|
|
9
9
|
import { EventTypeId } from './EventTypeId.js';
|
|
10
10
|
import { Tag } from './Tag.js';
|
|
11
|
+
import { NamedTag } from './NamedTag.js';
|
|
11
12
|
import type { WireEventContext } from './WireEventContext.js';
|
|
12
13
|
|
|
13
14
|
/** Converts kernel metadata identically for reads, reactors, and reducers. */
|
|
@@ -37,7 +38,8 @@ export function toClientEventContext(context: WireEventContext): EventContext {
|
|
|
37
38
|
occurred: causation.Occurred ? new Date(causation.Occurred.Value) : undefined,
|
|
38
39
|
properties: { ...causation.Properties }
|
|
39
40
|
})),
|
|
40
|
-
tags: (context.Tags ?? []).map(value => new Tag(value))
|
|
41
|
+
tags: (context.Tags ?? []).map(value => new Tag(value)),
|
|
42
|
+
namedTags: (context.NamedTags ?? []).map(tag => new NamedTag(tag.Name, tag.Value))
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/chronicle",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.50.0",
|
|
4
4
|
"description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
|
|
5
5
|
"author": "Cratis",
|
|
6
6
|
"license": "MIT",
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
},
|
|
179
179
|
"dependencies": {
|
|
180
180
|
"@bufbuild/protobuf": "^2.16.0",
|
|
181
|
-
"@cratis/chronicle.contracts": "19.
|
|
181
|
+
"@cratis/chronicle.contracts": "19.31.10",
|
|
182
182
|
"@grpc/grpc-js": "^1.14.5",
|
|
183
183
|
"@opentelemetry/api": "^1.9.1",
|
|
184
184
|
"glob": "^13.0.6",
|
|
@@ -188,14 +188,14 @@
|
|
|
188
188
|
"undici": "^8.11.2"
|
|
189
189
|
},
|
|
190
190
|
"devDependencies": {
|
|
191
|
-
"@cratis/fundamentals": "7.
|
|
191
|
+
"@cratis/fundamentals": "7.22.4",
|
|
192
192
|
"@opentelemetry/context-async-hooks": "2.11.0",
|
|
193
193
|
"@opentelemetry/core": "2.11.0",
|
|
194
194
|
"@opentelemetry/instrumentation-grpc": "0.222.0",
|
|
195
195
|
"@opentelemetry/instrumentation-http": "0.222.0",
|
|
196
196
|
"@opentelemetry/sdk-metrics": "2.11.0",
|
|
197
197
|
"@opentelemetry/sdk-trace-base": "2.11.0",
|
|
198
|
-
"@types/node": "^26.6.
|
|
198
|
+
"@types/node": "^26.6.4",
|
|
199
199
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
200
200
|
"esbuild": "^0.28.2",
|
|
201
201
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
@@ -203,6 +203,6 @@
|
|
|
203
203
|
"vitest": "^5.0.3"
|
|
204
204
|
},
|
|
205
205
|
"peerDependencies": {
|
|
206
|
-
"@cratis/fundamentals": "^7.
|
|
206
|
+
"@cratis/fundamentals": "^7.22.4"
|
|
207
207
|
}
|
|
208
208
|
}
|
|
@@ -23,8 +23,9 @@ const contextProperties = {
|
|
|
23
23
|
correlationId: true,
|
|
24
24
|
causation: true,
|
|
25
25
|
tags: true
|
|
26
|
-
// eventSource
|
|
27
|
-
|
|
26
|
+
// eventSource and namedTags are deliberately not projectable: their kernel resolution is not part of the
|
|
27
|
+
// supported expression set.
|
|
28
|
+
} satisfies Record<Exclude<keyof EventContext, 'eventSource' | 'namedTags'>, true>;
|
|
28
29
|
|
|
29
30
|
// Mirror the kernel's DerivedPropertyFunctions registry: Week() applies to dates only.
|
|
30
31
|
const derivedFunctions: Record<string, string> = { week: 'Week' };
|
|
@@ -17,7 +17,7 @@ import { EventTypeId } from '../events/EventTypeId.js';
|
|
|
17
17
|
import { EventTypeGeneration } from '../events/EventTypeGeneration.js';
|
|
18
18
|
import { Identity } from '../identity/Identity.js';
|
|
19
19
|
import type { AppendOptions } from '../eventSequences/AppendOptions.js';
|
|
20
|
-
import { singleAppendRoute } from './singleAppendRoute.js';
|
|
20
|
+
import { hasNamedTags, singleAppendRoute } from './singleAppendRoute.js';
|
|
21
21
|
import type { AppendResult } from '../eventSequences/AppendResult.js';
|
|
22
22
|
import type { AppendedEventWithResult } from '../eventSequences/AppendedEventWithResult.js';
|
|
23
23
|
import type { EventForEventSourceId } from '../eventSequences/EventForEventSourceId.js';
|
|
@@ -215,7 +215,8 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
215
215
|
subject: resolveStoredSubject(prepared.subject, eventSourceId), hash, causedBy: prepared.identity, observationState: EventObservationState.Initial,
|
|
216
216
|
eventType: prepared.eventType, occurred, correlationId: prepared.correlationId.toString(),
|
|
217
217
|
causation: prepared.causationChain.map(item => ({ type: item.type.name, occurred: item.occurred, properties: { ...item.properties } })),
|
|
218
|
-
tags: prepared.tags.map(tag => new Tag(tag))
|
|
218
|
+
tags: prepared.tags.map(tag => new Tag(tag)),
|
|
219
|
+
namedTags: []
|
|
219
220
|
}
|
|
220
221
|
};
|
|
221
222
|
const violations = this.validateConstraints([...this._history, ...(this._stagedSeeds ?? [])], [stored]);
|
|
@@ -254,10 +255,13 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
254
255
|
for (const entry of entries) {
|
|
255
256
|
if (typeof sourceOrEvents === 'string') break;
|
|
256
257
|
if (!entry || typeof entry !== 'object' || Reflect.ownKeys(entry).some(key =>
|
|
257
|
-
!['eventSourceId', 'event', 'eventSourceType', 'eventStreamType', 'eventStreamId', 'subject', 'occurred', 'tags'].includes(String(key)))) {
|
|
258
|
+
!['eventSourceId', 'event', 'eventSourceType', 'eventStreamType', 'eventStreamId', 'subject', 'occurred', 'tags', 'namedTags'].includes(String(key)))) {
|
|
258
259
|
throw this.unsupported('appendMany.entry', this.id.value, 'Unrecognized per-entry metadata.');
|
|
259
260
|
}
|
|
260
261
|
const individual = entry as EventForEventSourceId;
|
|
262
|
+
if (hasNamedTags(individual.namedTags)) {
|
|
263
|
+
throw this.unsupported('appendMany.namedTags', this.id.value, 'Named tags are not fixture-backed.');
|
|
264
|
+
}
|
|
261
265
|
if (individual.occurred !== undefined) this.checkedDate(individual.occurred, 'appendMany.occurred');
|
|
262
266
|
if (individual.subject !== undefined && individual.subject !== null && typeof individual.subject !== 'string') {
|
|
263
267
|
throw this.unsupported('appendMany.subject', this.id.value, 'Subject must be a string.');
|
|
@@ -271,7 +275,8 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
271
275
|
try {
|
|
272
276
|
const batchOptions: AppendOptions | undefined = shared?.correlationId === undefined && this._correlationId
|
|
273
277
|
? { correlationId: this._correlationId(), sourceType: shared?.sourceType, streamType: shared?.streamType,
|
|
274
|
-
streamId: shared?.streamId, subject: shared?.subject, occurred: shared?.occurred, tags: shared?.tags
|
|
278
|
+
streamId: shared?.streamId, subject: shared?.subject, occurred: shared?.occurred, tags: shared?.tags,
|
|
279
|
+
namedTags: shared?.namedTags }
|
|
275
280
|
: shared;
|
|
276
281
|
if (batchOptions?.correlationId !== undefined) this.validateGuid(batchOptions.correlationId, 'appendMany.correlationId');
|
|
277
282
|
prepared = typeof sourceOrEvents === 'string'
|
|
@@ -318,7 +323,8 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
318
323
|
eventStreamId: wire.EventStreamId || 'Default', subject: resolveStoredSubject(wire.Subject, eventSourceId), hash, causedBy: identity,
|
|
319
324
|
observationState: EventObservationState.Initial, eventType, occurred, correlationId: correlationId.toString(),
|
|
320
325
|
causation: batchCausationChain.map(item => ({ type: item.type.name, occurred: item.occurred, properties: { ...item.properties } })),
|
|
321
|
-
tags: wire.Tags.map(tag => new Tag(tag))
|
|
326
|
+
tags: wire.Tags.map(tag => new Tag(tag)),
|
|
327
|
+
namedTags: []
|
|
322
328
|
} };
|
|
323
329
|
return { stored, result: this.success(sequenceNumber, event.constructor.name) };
|
|
324
330
|
});
|
|
@@ -411,7 +417,10 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
411
417
|
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
412
418
|
throw this.unsupported('appendMany.options', this.id.value, 'Append options must be an object.');
|
|
413
419
|
}
|
|
414
|
-
|
|
420
|
+
if (hasNamedTags(options.namedTags)) {
|
|
421
|
+
throw this.unsupported('appendMany.namedTags', this.id.value, 'Named tags are not fixture-backed.');
|
|
422
|
+
}
|
|
423
|
+
const allowed = ['correlationId', 'sourceType', 'streamType', 'streamId', 'subject', 'occurred', 'tags', 'namedTags'];
|
|
415
424
|
if (Reflect.ownKeys(options).some(key => !allowed.includes(String(key))) ||
|
|
416
425
|
options.concurrencyScope !== undefined || options.concurrencyScopes !== undefined || options.eventSourceId !== undefined) {
|
|
417
426
|
throw this.unsupported('appendMany.options', this.id.value, 'Concurrency and unrecognized metadata are not fixture-backed.');
|
|
@@ -492,7 +501,8 @@ export class InProcessEventSequence implements IEventSequence {
|
|
|
492
501
|
context: { ...event.context, eventType, occurred: new Date(event.context.occurred),
|
|
493
502
|
causedBy: causedBy && new Identity(causedBy.subject, causedBy.name, causedBy.userName, causedBy.onBehalfOf),
|
|
494
503
|
causation: event.context.causation.map(item => ({ ...item, occurred: item.occurred && new Date(item.occurred), properties: { ...item.properties } })),
|
|
495
|
-
tags: event.context.tags.map(item => new Tag(item.value))
|
|
504
|
+
tags: event.context.tags.map(item => new Tag(item.value)),
|
|
505
|
+
namedTags: [...(event.context.namedTags ?? [])] }
|
|
496
506
|
};
|
|
497
507
|
}
|
|
498
508
|
|
|
@@ -13,11 +13,13 @@ import type { AppendedEvent } from '../events/AppendedEvent.js';
|
|
|
13
13
|
import type { EventContext } from '../events/EventContext.js';
|
|
14
14
|
import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
|
|
15
15
|
import { getFilterTagsFor } from '../events/filterEventsByTagDecorator.js';
|
|
16
|
+
import { mergeNamedTags } from '../events/mergeNamedTags.js';
|
|
16
17
|
import { getEventSourceTypeFor } from '../events/eventSourceTypeDecorator.js';
|
|
17
18
|
import { getDeclaredEventStreamTypeFor, getEventStreamTypeFor } from '../events/eventStreamTypeDecorator.js';
|
|
18
19
|
import type { IEventLog } from '../eventSequences/IEventLog.js';
|
|
19
20
|
import type { EventForEventSourceId } from '../eventSequences/EventForEventSourceId.js';
|
|
20
21
|
import type { AppendOptions } from '../eventSequences/AppendOptions.js';
|
|
22
|
+
import { ensureNamedTagBatchIsSupported } from '../eventSequences/ensureNamedTagBatchIsSupported.js';
|
|
21
23
|
import { EventStoreName } from '../EventStoreName.js';
|
|
22
24
|
import { EventStoreNamespaceName } from '../EventStoreNamespaceName.js';
|
|
23
25
|
import type { IEventStore } from '../IEventStore.js';
|
|
@@ -297,6 +299,10 @@ export class ReactorScenario {
|
|
|
297
299
|
throw new UnsupportedReactorOperation('handler.return', this._reactor.name,
|
|
298
300
|
'Only registered events and production EventForEventSourceId wrappers may be recorded; commands and unknown values are unsupported.');
|
|
299
301
|
}
|
|
302
|
+
// Production validates named tags before appending, failing the delivery on an invalid tag; so does recording.
|
|
303
|
+
for (const target of events) mergeNamedTags(target.namedTags);
|
|
304
|
+
ensureNamedTagBatchIsSupported(events.some(event => mergeNamedTags(event.namedTags).length > 0),
|
|
305
|
+
events.some(event => event.eventSource !== undefined));
|
|
300
306
|
for (const target of events) {
|
|
301
307
|
this._effects.push(Object.freeze({ kind: 'event', value: target.event, target, triggeringContext: context,
|
|
302
308
|
handler, deliveryIndex }));
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { NamedTag } from '../../../events/NamedTag.js';
|
|
6
|
+
import { EventScenario, UnsupportedEventSequenceOperation } from '../../index.js';
|
|
7
|
+
import { artifacts, Claim, createEvents, rejection } from '../given/routed_scenario.fixture.js';
|
|
8
|
+
|
|
9
|
+
chai.should();
|
|
10
|
+
const namedTags = [new NamedTag('batch', 'b-1')];
|
|
11
|
+
|
|
12
|
+
describe('when appending a single event with named tags in an event scenario', () => {
|
|
13
|
+
let scenario: EventScenario;
|
|
14
|
+
let error: UnsupportedEventSequenceOperation;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
scenario = createEvents();
|
|
17
|
+
error = await rejection(() => scenario.append('source', new Claim('one'), { namedTags }));
|
|
18
|
+
});
|
|
19
|
+
it('should reject the named tags explicitly', () => error.message.should.contain('append.namedTags'));
|
|
20
|
+
it('should not append the event', () => scenario.appendedEvents.should.have.lengthOf(0));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('when appending a batch with shared named tags in an event scenario', () => {
|
|
24
|
+
let error: UnsupportedEventSequenceOperation;
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
error = await rejection(() => createEvents().appendMany('source', [new Claim('one')], { namedTags }));
|
|
27
|
+
});
|
|
28
|
+
it('should reject the named tags explicitly', () => error.message.should.contain('appendMany.namedTags'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('when appending a batch entry with named tags in an event scenario', () => {
|
|
32
|
+
let error: UnsupportedEventSequenceOperation;
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
error = await rejection(() => createEvents().appendMany([{ eventSourceId: 'source', event: new Claim('one'), namedTags }]));
|
|
35
|
+
});
|
|
36
|
+
it('should reject the named tags explicitly', () => error.message.should.contain('appendMany.namedTags'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('when appending with an empty named tag list in an event scenario', () => {
|
|
40
|
+
let scenario: EventScenario;
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
scenario = new EventScenario({ artifacts });
|
|
43
|
+
await scenario.append('source', new Claim('one'), { namedTags: [] });
|
|
44
|
+
});
|
|
45
|
+
it('should append the event', () => scenario.appendedEvents.should.have.lengthOf(1));
|
|
46
|
+
it('should expose an empty named tag list on the stored context like a kernel delivery', () =>
|
|
47
|
+
scenario.appendedEvents[0].context.namedTags!.should.deep.equal([]));
|
|
48
|
+
});
|