@cratis/chronicle 6.45.0 → 6.47.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/events/eventSourceTypeDecorator.d.ts +18 -0
- package/dist/events/eventSourceTypeDecorator.d.ts.map +1 -0
- package/dist/events/eventSourceTypeDecorator.js +25 -0
- package/dist/events/eventSourceTypeDecorator.js.map +1 -0
- package/dist/events/eventStreamTypeDecorator.d.ts +20 -0
- package/dist/events/eventStreamTypeDecorator.d.ts.map +1 -0
- package/dist/events/eventStreamTypeDecorator.js +29 -0
- package/dist/events/eventStreamTypeDecorator.js.map +1 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -1
- package/dist/projections/declarative/CompositeKeyBuilder.d.ts +4 -1
- package/dist/projections/declarative/CompositeKeyBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/CompositeKeyBuilder.js +15 -8
- package/dist/projections/declarative/CompositeKeyBuilder.js.map +1 -1
- package/dist/projections/declarative/ICompositeKeyBuilder.d.ts +8 -1
- package/dist/projections/declarative/ICompositeKeyBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/ICompositeKeySetBuilder.d.ts +17 -0
- package/dist/projections/declarative/ICompositeKeySetBuilder.d.ts.map +1 -0
- package/dist/projections/declarative/ICompositeKeySetBuilder.js +4 -0
- package/dist/projections/declarative/ICompositeKeySetBuilder.js.map +1 -0
- package/dist/projections/declarative/index.d.ts +1 -0
- package/dist/projections/declarative/index.d.ts.map +1 -1
- package/dist/reactors/ReactorSideEffects.d.ts +6 -3
- package/dist/reactors/ReactorSideEffects.d.ts.map +1 -1
- package/dist/reactors/ReactorSideEffects.js +12 -6
- package/dist/reactors/ReactorSideEffects.js.map +1 -1
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +4 -2
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +4 -2
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/testing/ReactorScenario.d.ts.map +1 -1
- package/dist/testing/ReactorScenario.js +9 -1
- package/dist/testing/ReactorScenario.js.map +1 -1
- package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -1
- package/dist/testing/ReducerReadModelProcessor.js +13 -0
- package/dist/testing/ReducerReadModelProcessor.js.map +1 -1
- package/dist/testing/UnsupportedReducerOperation.d.ts +7 -0
- package/dist/testing/UnsupportedReducerOperation.d.ts.map +1 -0
- package/dist/testing/UnsupportedReducerOperation.js +14 -0
- package/dist/testing/UnsupportedReducerOperation.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/events/eventSourceTypeDecorator.ts +28 -0
- package/events/eventStreamTypeDecorator.ts +33 -0
- package/events/for_observerFilters/when_registering/filter_registration_behaviors.fixture.ts +55 -0
- package/events/for_observerFilters/when_registering/with_legacy_decorators.legacy.spec.ts +30 -0
- package/events/for_observerFilters/when_registering/with_standard_decorators.standard.spec.ts +30 -0
- package/events/index.ts +2 -0
- package/package.json +2 -2
- package/projections/declarative/CompositeKeyBuilder.ts +26 -9
- package/projections/declarative/ICompositeKeyBuilder.ts +9 -1
- package/projections/declarative/ICompositeKeySetBuilder.ts +21 -0
- package/projections/declarative/for_CompositeKeyBuilder/when_matching_kernel_fixtures.spec.ts +108 -0
- package/projections/declarative/for_CompositeKeyBuilder/when_setting_a_duplicate_part.spec.ts +43 -0
- package/projections/declarative/for_CompositeKeyBuilder/when_setting_expression_parts.spec.ts +84 -0
- package/projections/declarative/for_CompositeKeyBuilder/when_typing_constant_parts.standard.spec.ts +43 -0
- package/projections/declarative/index.ts +1 -0
- package/reactors/ReactorSideEffects.ts +16 -6
- package/reactors/Reactors.ts +4 -2
- package/reactors/for_ReactorSideEffects/when_dispatching/with_reactor_source_and_stream_types.spec.ts +75 -0
- package/reactors/for_Reactors/against_a_kernel/when_filtering_source_and_stream_types.integration.spec.ts +147 -0
- package/reducers/Reducers.ts +4 -2
- package/testing/ReactorScenario.ts +10 -1
- package/testing/ReducerReadModelProcessor.ts +13 -0
- package/testing/UnsupportedReducerOperation.ts +10 -0
- package/testing/for_ReactorScenario/when_recording_side_effects/with_declared_stream_type.spec.ts +50 -0
- package/testing/for_observerScenarios/when_selecting_filters/with_reducer_tag_filters.spec.ts +50 -0
- package/testing/for_observerScenarios/when_selecting_filters/with_source_and_stream_types.spec.ts +48 -0
- package/testing/index.ts +1 -0
- package/testing/projections/ProjectionReadModelProcessor.fixtures.spec.ts +5 -0
- package/testing/projections/for_ProjectionCapabilities/when_rejecting_composite_expression_parts.spec.ts +40 -0
|
@@ -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 'reflect-metadata';
|
|
5
|
+
|
|
6
|
+
const EVENT_STREAM_TYPE_METADATA_KEY = 'chronicle:eventStreamType';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Restricts a reactor or reducer to events in the given event stream type.
|
|
10
|
+
* Like .NET `[EventStreamType]`, also sets the stream type of bare events returned by a reactor.
|
|
11
|
+
* Explicit EventForEventSourceId returns and service append options retain their own metadata.
|
|
12
|
+
* Does not filter projections.
|
|
13
|
+
* Supports legacy and standard class decorators; derived observers inherit the value unless overridden.
|
|
14
|
+
* @param value - The event stream type to observe. 'All' leaves the stream type unrestricted.
|
|
15
|
+
* @returns A class decorator.
|
|
16
|
+
*/
|
|
17
|
+
export function eventStreamType(value: string): ClassDecorator {
|
|
18
|
+
return (target: object) => { Reflect.defineMetadata(EVENT_STREAM_TYPE_METADATA_KEY, value, target); };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves an observer's event stream type filter, including inherited metadata.
|
|
23
|
+
* @param target - The observer constructor.
|
|
24
|
+
* @returns The configured value, or 'All' (unrestricted).
|
|
25
|
+
*/
|
|
26
|
+
export function getEventStreamTypeFor(target: Function): string {
|
|
27
|
+
return getDeclaredEventStreamTypeFor(target) ?? 'All';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolves declared (including inherited) metadata without the observer's 'All' delivery default. */
|
|
31
|
+
export function getDeclaredEventStreamTypeFor(target: Function): string | undefined {
|
|
32
|
+
return Reflect.getMetadata(EVENT_STREAM_TYPE_METADATA_KEY, target) as string | undefined;
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { chai, describe, it } from 'vitest';
|
|
5
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
6
|
+
import type { IClientArtifactsProvider } from '../../../artifacts/index.js';
|
|
7
|
+
import type { ChronicleConnection } from '../../../connection/index.js';
|
|
8
|
+
import { ConnectionLifecycle } from '../../../connection/ConnectionLifecycle.js';
|
|
9
|
+
import type { IEventLog } from '../../../eventSequences/IEventLog.js';
|
|
10
|
+
import { Reactors } from '../../../reactors/Reactors.js';
|
|
11
|
+
import { reactor } from '../../../reactors/reactor.js';
|
|
12
|
+
import { Reducers } from '../../../reducers/Reducers.js';
|
|
13
|
+
import { reducer } from '../../../reducers/reducer.js';
|
|
14
|
+
|
|
15
|
+
chai.should();
|
|
16
|
+
|
|
17
|
+
export function filterRegistrationBehaviors(cases: readonly { type: Constructor; source: string; stream: string; tags: string[] }[]): void {
|
|
18
|
+
for (const kind of ['reactor', 'reducer'] as const) {
|
|
19
|
+
for (const { type, source, stream, tags } of cases) {
|
|
20
|
+
describe(`when registering ${kind} ${type.name}`, () => {
|
|
21
|
+
it('should combine source and stream type filters with tag filters without changing observer labels', async () => {
|
|
22
|
+
let registration: unknown;
|
|
23
|
+
let received!: () => void;
|
|
24
|
+
const captured = new Promise<void>(resolve => { received = resolve; });
|
|
25
|
+
const observe = async function* (queue: AsyncIterable<unknown>) {
|
|
26
|
+
registration = (await queue[Symbol.asyncIterator]().next()).value;
|
|
27
|
+
received();
|
|
28
|
+
// An empty stream is enough to capture registration, not evidence of kernel filtering.
|
|
29
|
+
yield* [];
|
|
30
|
+
};
|
|
31
|
+
const connection = { reactors: { observe }, reducers: { observe }, readModels: {
|
|
32
|
+
registerMany: async () => ({})
|
|
33
|
+
} } as unknown as ChronicleConnection;
|
|
34
|
+
reactor(type.name)(type);
|
|
35
|
+
reducer(type.name)(type);
|
|
36
|
+
const artifacts = { reactors: kind === 'reactor' ? [type] : [], reducers: kind === 'reducer' ? [type] : [],
|
|
37
|
+
eventTypes: [], readModels: [] } as unknown as IClientArtifactsProvider;
|
|
38
|
+
const observer = kind === 'reactor'
|
|
39
|
+
? new Reactors(artifacts, connection, 'store', 'Default', new ConnectionLifecycle(), {} as IEventLog)
|
|
40
|
+
: new Reducers(artifacts, connection, 'store', 'Default', new ConnectionLifecycle(), 'sink');
|
|
41
|
+
try {
|
|
42
|
+
await observer.register();
|
|
43
|
+
await captured;
|
|
44
|
+
const message = registration as { Content: { Value0: Record<string, { Filters: object; Tags: string[] }> } };
|
|
45
|
+
const definition = message.Content.Value0[kind === 'reactor' ? 'Reactor' : 'Reducer'];
|
|
46
|
+
definition.Filters.should.deep.equal({ EventSourceType: source, EventStreamType: stream, FilterTags: tags });
|
|
47
|
+
definition.Tags.should.deep.equal(tags.length ? ['Analytics'] : []);
|
|
48
|
+
} finally {
|
|
49
|
+
observer.dispose();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { eventSourceType, eventStreamType, filterEventsByTag, tag } from '../../../index.js';
|
|
5
|
+
import { filterRegistrationBehaviors } from './filter_registration_behaviors.fixture.js';
|
|
6
|
+
|
|
7
|
+
class Unfiltered {}
|
|
8
|
+
@eventSourceType('Customers')
|
|
9
|
+
class SourceFiltered {}
|
|
10
|
+
@eventStreamType('Orders')
|
|
11
|
+
class StreamFiltered {}
|
|
12
|
+
@eventSourceType('Customers')
|
|
13
|
+
@eventStreamType('Orders')
|
|
14
|
+
@filterEventsByTag('vip')
|
|
15
|
+
@filterEventsByTag('priority')
|
|
16
|
+
@tag('Analytics')
|
|
17
|
+
class Combined {}
|
|
18
|
+
class Inherited extends Combined {}
|
|
19
|
+
@eventSourceType('')
|
|
20
|
+
@eventStreamType('All')
|
|
21
|
+
class Reset extends Combined {}
|
|
22
|
+
|
|
23
|
+
filterRegistrationBehaviors([
|
|
24
|
+
{ type: Unfiltered, source: '', stream: 'All', tags: [] },
|
|
25
|
+
{ type: SourceFiltered, source: 'Customers', stream: 'All', tags: [] },
|
|
26
|
+
{ type: StreamFiltered, source: '', stream: 'Orders', tags: [] },
|
|
27
|
+
{ type: Combined, source: 'Customers', stream: 'Orders', tags: ['priority', 'vip'] },
|
|
28
|
+
{ type: Inherited, source: 'Customers', stream: 'Orders', tags: ['priority', 'vip'] },
|
|
29
|
+
{ type: Reset, source: '', stream: 'All', tags: ['priority', 'vip'] }
|
|
30
|
+
]);
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { eventSourceType, eventStreamType, filterEventsByTag, tag } from '../../../index.js';
|
|
5
|
+
import { filterRegistrationBehaviors } from './filter_registration_behaviors.fixture.js';
|
|
6
|
+
|
|
7
|
+
class Unfiltered {}
|
|
8
|
+
@eventSourceType('Customers')
|
|
9
|
+
class SourceFiltered {}
|
|
10
|
+
@eventStreamType('Orders')
|
|
11
|
+
class StreamFiltered {}
|
|
12
|
+
@eventSourceType('Customers')
|
|
13
|
+
@eventStreamType('Orders')
|
|
14
|
+
@filterEventsByTag('vip')
|
|
15
|
+
@filterEventsByTag('priority')
|
|
16
|
+
@tag('Analytics')
|
|
17
|
+
class Combined {}
|
|
18
|
+
class Inherited extends Combined {}
|
|
19
|
+
@eventSourceType('')
|
|
20
|
+
@eventStreamType('All')
|
|
21
|
+
class Reset extends Combined {}
|
|
22
|
+
|
|
23
|
+
filterRegistrationBehaviors([
|
|
24
|
+
{ type: Unfiltered, source: '', stream: 'All', tags: [] },
|
|
25
|
+
{ type: SourceFiltered, source: 'Customers', stream: 'All', tags: [] },
|
|
26
|
+
{ type: StreamFiltered, source: '', stream: 'Orders', tags: [] },
|
|
27
|
+
{ type: Combined, source: 'Customers', stream: 'Orders', tags: ['priority', 'vip'] },
|
|
28
|
+
{ type: Inherited, source: 'Customers', stream: 'Orders', tags: ['priority', 'vip'] },
|
|
29
|
+
{ type: Reset, source: '', stream: 'All', tags: ['priority', 'vip'] }
|
|
30
|
+
]);
|
package/events/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { isRegisteredEvent } from './isRegisteredEvent.js';
|
|
|
18
18
|
export { Tag } from './Tag.js';
|
|
19
19
|
export { tag, tags, getTagsFor } from './tagDecorator.js';
|
|
20
20
|
export { filterEventsByTag, getFilterTagsFor } from './filterEventsByTagDecorator.js';
|
|
21
|
+
export { eventSourceType, getEventSourceTypeFor } from './eventSourceTypeDecorator.js';
|
|
22
|
+
export { eventStreamType, getEventStreamTypeFor } from './eventStreamTypeDecorator.js';
|
|
21
23
|
export { mergeTags } from './mergeTags.js';
|
|
22
24
|
export * from './constraints/index.js';
|
|
23
25
|
export * from './migrations/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/chronicle",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.47.0",
|
|
4
4
|
"description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
|
|
5
5
|
"author": "Cratis",
|
|
6
6
|
"license": "MIT",
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"clean": "rm -rf dist",
|
|
174
174
|
"test": "vitest run",
|
|
175
175
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
176
|
-
"test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts events/for_handles/when_decorating_a_method.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_field_types/with_standard_decorators.standard.spec.ts eventSequences/for_EventSequence/when_appending/with_subject_annotations.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_tags/with_standard_decorators.standard.spec.ts telemetry/for_ChronicleTelemetry/when_exposing_names/with_convention_names.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts",
|
|
176
|
+
"test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/declarative/for_CompositeKeyBuilder/when_typing_constant_parts.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts events/for_handles/when_decorating_a_method.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_arithmetic/with_standard_decorators.standard.spec.ts testing/for_ReadModelScenario/when_evaluating_joins/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_field_types/with_standard_decorators.standard.spec.ts eventSequences/for_EventSequence/when_appending/with_subject_annotations.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all.standard.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_tags/with_standard_decorators.standard.spec.ts telemetry/for_ChronicleTelemetry/when_exposing_names/with_convention_names.spec.ts projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts events/for_observerFilters/when_registering/with_standard_decorators.standard.spec.ts reactors/for_Reactors/against_a_kernel/when_filtering_source_and_stream_types.integration.spec.ts testing/for_observerScenarios/when_selecting_filters/with_source_and_stream_types.spec.ts",
|
|
177
177
|
"test:native": "node ./scripts/check-native-esm.mjs"
|
|
178
178
|
},
|
|
179
179
|
"dependencies": {
|
|
@@ -3,36 +3,53 @@
|
|
|
3
3
|
|
|
4
4
|
import { PropertyAccessor, PropertyPathResolverProxyHandler } from '@cratis/fundamentals';
|
|
5
5
|
import { ICompositeKeyBuilder } from './ICompositeKeyBuilder.js';
|
|
6
|
+
import { ISetBuilder } from './ISetBuilder.js';
|
|
7
|
+
import { ICompositeKeySetBuilder } from './ICompositeKeySetBuilder.js';
|
|
8
|
+
import { SetBuilder } from './SetBuilder.js';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Concrete implementation of {@link ICompositeKeyBuilder} that builds a `$composite(...)` key
|
|
9
|
-
* expression from
|
|
12
|
+
* expression from event properties, event context, source identifiers and constants.
|
|
10
13
|
* @template TKeyType - The composite key type.
|
|
11
14
|
* @template TEvent - The event type.
|
|
12
15
|
*/
|
|
13
16
|
export class CompositeKeyBuilder<TKeyType, TEvent> implements ICompositeKeyBuilder<TKeyType, TEvent> {
|
|
14
|
-
private readonly _parts: Array<{ property: string; expression
|
|
17
|
+
private readonly _parts: Array<{ property: string; expression?: string }> = [];
|
|
15
18
|
|
|
19
|
+
/** @inheritdoc */
|
|
20
|
+
set<TProperty>(targetPropertyAccessor: (key: TKeyType) => TProperty): ICompositeKeySetBuilder<TKeyType, TEvent, TProperty>;
|
|
16
21
|
/** @inheritdoc */
|
|
17
22
|
set(
|
|
18
23
|
targetPropertyAccessor: PropertyAccessor<TKeyType>,
|
|
19
24
|
sourcePropertyAccessor: PropertyAccessor<TEvent>
|
|
20
|
-
): ICompositeKeyBuilder<TKeyType, TEvent
|
|
25
|
+
): ICompositeKeyBuilder<TKeyType, TEvent>;
|
|
26
|
+
set(
|
|
27
|
+
targetPropertyAccessor: PropertyAccessor<TKeyType>,
|
|
28
|
+
sourcePropertyAccessor?: PropertyAccessor<TEvent>
|
|
29
|
+
): ICompositeKeyBuilder<TKeyType, TEvent> | ISetBuilder<TEvent, ICompositeKeyBuilder<TKeyType, TEvent>> {
|
|
21
30
|
const targetHandler = new PropertyPathResolverProxyHandler();
|
|
22
31
|
const targetProxy = new Proxy({}, targetHandler);
|
|
23
32
|
targetPropertyAccessor(targetProxy as TKeyType);
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
if (this._parts.some(part => part.property === targetHandler.property)) {
|
|
35
|
+
throw new Error(`Composite key part '${targetHandler.property}' is already configured.`);
|
|
36
|
+
}
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
const part: { property: string; expression?: string } = { property: targetHandler.property };
|
|
39
|
+
this._parts.push(part);
|
|
40
|
+
const setBuilder = new SetBuilder<TEvent, ICompositeKeyBuilder<TKeyType, TEvent>>(
|
|
41
|
+
part.property, (_property, expression) => { part.expression = expression; }, this);
|
|
42
|
+
return sourcePropertyAccessor ? setBuilder.to(sourcePropertyAccessor) : setBuilder;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
/** @inheritdoc */
|
|
34
46
|
build(): string {
|
|
35
|
-
const parts = this._parts.map(part =>
|
|
47
|
+
const parts = this._parts.map(part => {
|
|
48
|
+
if (part.expression === undefined) {
|
|
49
|
+
throw new Error(`Composite key part '${part.property}' is missing a to expression.`);
|
|
50
|
+
}
|
|
51
|
+
return `${part.property}=${part.expression}`;
|
|
52
|
+
}).join(',');
|
|
36
53
|
return `$composite(${parts})`;
|
|
37
54
|
}
|
|
38
55
|
}
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { PropertyAccessor } from '@cratis/fundamentals';
|
|
5
|
+
import { ICompositeKeySetBuilder } from './ICompositeKeySetBuilder.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Defines a builder for constructing composite keys from
|
|
8
|
+
* Defines a builder for constructing composite keys from event properties, context and constants.
|
|
8
9
|
* @template TKeyType - The composite key type.
|
|
9
10
|
* @template TEvent - The event type.
|
|
10
11
|
*/
|
|
11
12
|
export interface ICompositeKeyBuilder<TKeyType, TEvent> {
|
|
13
|
+
/**
|
|
14
|
+
* Configures a key part from an event property, event context property, event source id or constant.
|
|
15
|
+
* @param targetPropertyAccessor - Accessor for the property on the key type to populate.
|
|
16
|
+
* @returns A set-expression builder whose to methods return this composite key builder.
|
|
17
|
+
*/
|
|
18
|
+
set<TProperty>(targetPropertyAccessor: (key: TKeyType) => TProperty): ICompositeKeySetBuilder<TKeyType, TEvent, TProperty>;
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
21
|
* Maps a source event property to a target key property.
|
|
14
22
|
* @param targetPropertyAccessor - Accessor for the property on the key type to populate.
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { ICompositeKeyBuilder } from './ICompositeKeyBuilder.js';
|
|
5
|
+
import { ISetBuilder } from './ISetBuilder.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configures an expression for a composite key part, retaining its target property type.
|
|
9
|
+
* @template TKeyType - The composite key type.
|
|
10
|
+
* @template TEvent - The event type.
|
|
11
|
+
* @template TProperty - The selected key property's type.
|
|
12
|
+
*/
|
|
13
|
+
export interface ICompositeKeySetBuilder<TKeyType, TEvent, TProperty>
|
|
14
|
+
extends Omit<ISetBuilder<TEvent, ICompositeKeyBuilder<TKeyType, TEvent>>, 'toValue'> {
|
|
15
|
+
/**
|
|
16
|
+
* Sets the key part to a constant of the selected property's type.
|
|
17
|
+
* @param value - The constant value to assign.
|
|
18
|
+
* @returns The composite key builder for fluent chaining.
|
|
19
|
+
*/
|
|
20
|
+
toValue(value: TProperty): ICompositeKeyBuilder<TKeyType, TEvent>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
5
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
6
|
+
import { eventType } from '../../../events/eventTypeDecorator.js';
|
|
7
|
+
import { ProjectionBuilderFor, type ICompositeKeyBuilder } from '../index.js';
|
|
8
|
+
import type { ChildrenDefinitionLike } from '../ProjectionBuilderCore.js';
|
|
9
|
+
|
|
10
|
+
chai.should();
|
|
11
|
+
|
|
12
|
+
class RecordChanged { orderId!: string; title!: string; }
|
|
13
|
+
class OrderCreated { orderId!: string; title!: string; }
|
|
14
|
+
class ItemChanged { orderId!: string; itemId!: string; name!: string; }
|
|
15
|
+
class OrderPlaced { customerId!: string; }
|
|
16
|
+
class CustomerNamed { customerId!: string; name!: string; }
|
|
17
|
+
class ItemAdded { itemId!: string; title!: string; name!: string; }
|
|
18
|
+
class ItemNamed { itemId!: string; name!: string; }
|
|
19
|
+
for (const event of [RecordChanged, OrderCreated, ItemChanged, OrderPlaced, CustomerNamed, ItemAdded, ItemNamed]) eventType(event.name)(event);
|
|
20
|
+
|
|
21
|
+
class Key { orderId!: string; customerId!: string; itemId!: string; subject!: string; sourceId!: string; category!: string; }
|
|
22
|
+
class Item { id!: string; name!: string; }
|
|
23
|
+
class CompositeItem { id!: Key; name!: string; }
|
|
24
|
+
class Model { id!: Key; title!: string; items!: Item[]; }
|
|
25
|
+
class ChildJoinModel { id!: Key; title!: string; items!: CompositeItem[]; }
|
|
26
|
+
class JoinModel { id!: string; customerId!: string; customerName!: string; }
|
|
27
|
+
|
|
28
|
+
type Definition = ReturnType<ProjectionBuilderFor<Model>['build']>;
|
|
29
|
+
|
|
30
|
+
function contextParts<TEvent>(key: ICompositeKeyBuilder<Key, TEvent>): ICompositeKeyBuilder<Key, TEvent> {
|
|
31
|
+
return key.set(target => target.subject).toEventContextProperty('subject')
|
|
32
|
+
.set(target => target.sourceId).toEventSourceId();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function childKey<TEvent extends { itemId: string }>(key: ICompositeKeyBuilder<Key, TEvent>): void {
|
|
36
|
+
contextParts(key.set(target => target.itemId, event => event.itemId)).set(target => target.category).toValue('items');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildRoot(): Definition {
|
|
40
|
+
return new ProjectionBuilderFor<Model>().noAutoMap()
|
|
41
|
+
.from(RecordChanged, from => from
|
|
42
|
+
.usingCompositeKey<Key>(key => contextParts(key.set(target => target.orderId, event => event.orderId))
|
|
43
|
+
.set(target => target.category).toValue('orders'))
|
|
44
|
+
.set(model => model.title).to(event => event.title))
|
|
45
|
+
.build('oracle-composite-expression-parts', 'OracleReadModel');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildParent(withConstant: boolean): Definition {
|
|
49
|
+
const parentKey = <TEvent extends { orderId: string }>(key: ICompositeKeyBuilder<Key, TEvent>): void => {
|
|
50
|
+
if (!withConstant) key.set(target => target.orderId, event => event.orderId);
|
|
51
|
+
contextParts(key);
|
|
52
|
+
if (withConstant) key.set(target => target.category).toValue('orders');
|
|
53
|
+
};
|
|
54
|
+
return new ProjectionBuilderFor<Model>().noAutoMap()
|
|
55
|
+
.from(OrderCreated, from => from.usingCompositeKey<Key>(parentKey).set(model => model.title).to(event => event.title))
|
|
56
|
+
.children<Item>(model => model.items, children => children.noAutoMap().identifiedBy(item => item.id)
|
|
57
|
+
.from(ItemChanged, from => from.usingKey(event => event.itemId).usingParentCompositeKey<Key>(parentKey)
|
|
58
|
+
.set(item => item.id).to(event => event.itemId).set(item => item.name).to(event => event.name)))
|
|
59
|
+
.build(withConstant ? 'oracle-composite-parent-expression-parts' : 'oracle-composite-parent-without-constant', 'OracleReadModel');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRootJoin(): Definition {
|
|
63
|
+
return new ProjectionBuilderFor<JoinModel>().noAutoMap()
|
|
64
|
+
.from(OrderPlaced, from => from.set(model => model.customerId).to(event => event.customerId))
|
|
65
|
+
.join(CustomerNamed, join => join.on(model => model.customerId)
|
|
66
|
+
.usingCompositeKey<Key>(key => contextParts(key.set(target => target.customerId, event => event.customerId))
|
|
67
|
+
.set(target => target.category).toValue('customers'))
|
|
68
|
+
.set(model => model.customerName).to(event => event.name))
|
|
69
|
+
.build('oracle-composite-join-expression-parts', 'OracleReadModel');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildChildJoin(): Definition {
|
|
73
|
+
return new ProjectionBuilderFor<ChildJoinModel>().noAutoMap()
|
|
74
|
+
.from(ItemAdded, from => from.usingCompositeKey<Key>(childKey).set(model => model.title).to(event => event.title))
|
|
75
|
+
.children<CompositeItem>(model => model.items, children => children.noAutoMap().identifiedBy(item => item.id)
|
|
76
|
+
.from(ItemAdded, from => from.usingCompositeKey<Key>(childKey).usingParentCompositeKey<Key>(childKey)
|
|
77
|
+
.set(item => item.name).to(event => event.name))
|
|
78
|
+
.join(ItemNamed, join => join.on(item => item.id).usingCompositeKey<Key>(childKey)
|
|
79
|
+
.set(item => item.name).to(event => event.name)))
|
|
80
|
+
.build('oracle-composite-child-join-expression-parts', 'OracleReadModel');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Compare every event's Key/ParentKey and mappings, including child joins; do not derive
|
|
84
|
+
// builder configuration from the fixture expressions being asserted.
|
|
85
|
+
function expressions(definition: Pick<ChildrenDefinitionLike, 'From' | 'Join' | 'Children'>): object {
|
|
86
|
+
return {
|
|
87
|
+
From: definition.From.map(entry => ({ Event: entry.Key.Id, ...entry.Value })),
|
|
88
|
+
Join: definition.Join.map(entry => ({ Event: entry.Key.Id, Key: entry.Value.Key, On: entry.Value.On, Properties: entry.Value.Properties })),
|
|
89
|
+
Children: Object.fromEntries(Object.entries(definition.Children).map(([name, child]) => [name, expressions(child)]))
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const [name, build] of [
|
|
94
|
+
['composite-key-expression-parts', buildRoot],
|
|
95
|
+
['composite-parent-key-expression-parts', () => buildParent(true)],
|
|
96
|
+
['composite-parent-key-without-constant', () => buildParent(false)],
|
|
97
|
+
['composite-join-key-expression-parts', buildRootJoin],
|
|
98
|
+
['composite-child-join-key-expression-parts', buildChildJoin]
|
|
99
|
+
] as const) {
|
|
100
|
+
describe(`when matching the composite expressions in ${name}`, () => {
|
|
101
|
+
const fixture = JSON.parse(readFileSync(new URL(`../../../testing/projections/fixtures/${name}.json`, import.meta.url), 'utf8')) as { wireDefinition: Definition };
|
|
102
|
+
let definition: Definition;
|
|
103
|
+
beforeEach(() => { definition = build(); });
|
|
104
|
+
it('should emit the instance, parent and join keys executed by the packaged kernel', () => {
|
|
105
|
+
expressions(definition).should.deep.equal(expressions(fixture.wireDefinition));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { CompositeKeyBuilder } from '../CompositeKeyBuilder.js';
|
|
6
|
+
|
|
7
|
+
chai.should();
|
|
8
|
+
|
|
9
|
+
class Key { orderId!: string; }
|
|
10
|
+
class Changed { orderId!: string; }
|
|
11
|
+
|
|
12
|
+
for (const first of ['two-argument', 'completed', 'unfinished'] as const) {
|
|
13
|
+
for (const second of ['two-argument', 'one-argument'] as const) {
|
|
14
|
+
describe(`when setting a duplicate composite part using ${second} after ${first}`, () => {
|
|
15
|
+
let builder: CompositeKeyBuilder<Key, Changed>;
|
|
16
|
+
let duplicate: () => unknown;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
builder = new CompositeKeyBuilder<Key, Changed>();
|
|
20
|
+
if (first === 'two-argument') builder.set(key => key.orderId, event => event.orderId);
|
|
21
|
+
if (first === 'completed') builder.set(key => key.orderId).toValue('original');
|
|
22
|
+
if (first === 'unfinished') builder.set(key => key.orderId);
|
|
23
|
+
duplicate = () => second === 'two-argument'
|
|
24
|
+
? builder.set(key => key.orderId, event => event.orderId)
|
|
25
|
+
: builder.set(key => key.orderId);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should reject the duplicate as soon as the target is selected', () => {
|
|
29
|
+
duplicate.should.throw("Composite key part 'orderId' is already configured.");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('when continuing after a rejected duplicate composite part', () => {
|
|
36
|
+
it('should retain the original part without reserving another part', () => {
|
|
37
|
+
const builder = new CompositeKeyBuilder<Key, Changed>();
|
|
38
|
+
const original = builder.set(key => key.orderId);
|
|
39
|
+
(() => builder.set(key => key.orderId)).should.throw();
|
|
40
|
+
original.toValue('original');
|
|
41
|
+
builder.build().should.equal('$composite(orderId=$value(original))');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
6
|
+
import { eventType } from '../../../events/eventTypeDecorator.js';
|
|
7
|
+
import { CompositeKeyBuilder } from '../CompositeKeyBuilder.js';
|
|
8
|
+
import type { ICompositeKeyBuilder } from '../ICompositeKeyBuilder.js';
|
|
9
|
+
import { ProjectionBuilderFor } from '../ProjectionBuilderFor.js';
|
|
10
|
+
|
|
11
|
+
chai.should();
|
|
12
|
+
|
|
13
|
+
class Changed { orderId!: string; lineNumber!: number; }
|
|
14
|
+
eventType('expression-parts-changed')(Changed);
|
|
15
|
+
class Model { id!: string; }
|
|
16
|
+
class Key { orderId!: string; lineNumber!: number; subject!: string; sourceId!: string; category!: string; }
|
|
17
|
+
|
|
18
|
+
const configure = (key: ICompositeKeyBuilder<Key, Changed>) => key
|
|
19
|
+
.set(target => target.orderId, event => event.orderId)
|
|
20
|
+
.set(target => target.lineNumber).to(event => event.lineNumber)
|
|
21
|
+
.set(target => target.subject).toEventContextProperty('causedBy.subject')
|
|
22
|
+
.set(target => target.sourceId).toEventSourceId()
|
|
23
|
+
.set(target => target.category).toValue('orders');
|
|
24
|
+
const expression = '$composite(orderId=orderId,lineNumber=lineNumber,subject=$eventContext(CausedBy.Subject),sourceId=$eventSourceId,category=$value(orders))';
|
|
25
|
+
|
|
26
|
+
for (const kind of ['root', 'parent', 'join'] as const) {
|
|
27
|
+
describe(`when setting mixed expression parts on a ${kind} composite key`, () => {
|
|
28
|
+
let result: string;
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
const builder = new ProjectionBuilderFor<Model>();
|
|
31
|
+
if (kind === 'root') builder.from(Changed, from => from.usingCompositeKey<Key>(configure));
|
|
32
|
+
if (kind === 'parent') builder.from(Changed, from => from.usingParentCompositeKey<Key>(configure));
|
|
33
|
+
if (kind === 'join') builder.join(Changed, join => join.on(model => model.id).usingCompositeKey<Key>(configure));
|
|
34
|
+
const definition = builder.build('expressionParts', 'Model');
|
|
35
|
+
result = kind === 'join' ? definition.Join[0].Value.Key
|
|
36
|
+
: kind === 'parent' ? definition.From[0].Value.ParentKey : definition.From[0].Value.Key;
|
|
37
|
+
});
|
|
38
|
+
it('should emit the .NET set expressions in declaration order', () => result.should.equal(expression));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [value, expected] of [
|
|
43
|
+
[42, '$value(42)'], [false, '$value(false)'], ['fixed', '$value(fixed)'],
|
|
44
|
+
[null, '$null'], [{ value: 'concept' }, '$value(concept)']
|
|
45
|
+
] as const) {
|
|
46
|
+
describe(`when setting a composite constant part to ${JSON.stringify(value)}`, () => {
|
|
47
|
+
let result: string;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
const builder = new CompositeKeyBuilder<{ category: typeof value }, Changed>();
|
|
50
|
+
builder.set(target => target.category).toValue(value);
|
|
51
|
+
result = builder.build();
|
|
52
|
+
});
|
|
53
|
+
it('should use the shared constant expression syntax', () => result.should.equal(`$composite(category=${expected})`));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('when completing composite parts out of declaration order', () => {
|
|
58
|
+
it('should retain declaration order', () => {
|
|
59
|
+
const builder = new CompositeKeyBuilder<Key, Changed>();
|
|
60
|
+
const first = builder.set(target => target.orderId);
|
|
61
|
+
builder.set(target => target.category).toValue('orders');
|
|
62
|
+
first.to(event => event.orderId);
|
|
63
|
+
builder.build().should.equal('$composite(orderId=orderId,category=$value(orders))');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('when a composite part has no source expression', () => {
|
|
68
|
+
it('should reject building an incomplete key', () => {
|
|
69
|
+
const builder = new CompositeKeyBuilder<Key, Changed>();
|
|
70
|
+
builder.set(target => target.category);
|
|
71
|
+
(() => builder.build()).should.throw("Composite key part 'category' is missing a to expression.");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('when configuring an invalid context or constant part', () => {
|
|
76
|
+
it('should reject a context path the kernel cannot resolve', () => {
|
|
77
|
+
const builder = new CompositeKeyBuilder<Key, Changed>();
|
|
78
|
+
(() => builder.set(target => target.subject).toEventContextProperty('unknown')).should.throw();
|
|
79
|
+
});
|
|
80
|
+
it('should reject a constant the kernel cannot parse', () => {
|
|
81
|
+
const builder = new CompositeKeyBuilder<Key, Changed>();
|
|
82
|
+
(() => builder.set(target => target.category).toValue('a,b')).should.throw('unsupported by the kernel');
|
|
83
|
+
});
|
|
84
|
+
});
|
package/projections/declarative/for_CompositeKeyBuilder/when_typing_constant_parts.standard.spec.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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 { CompositeKeyBuilder, type ICompositeKeyBuilder } from '../index.js';
|
|
6
|
+
|
|
7
|
+
chai.should();
|
|
8
|
+
|
|
9
|
+
class Key {
|
|
10
|
+
number!: number;
|
|
11
|
+
text!: string;
|
|
12
|
+
nullable!: string | null;
|
|
13
|
+
sourceNumber!: number;
|
|
14
|
+
}
|
|
15
|
+
class Changed { number!: number; }
|
|
16
|
+
|
|
17
|
+
// Checked by test:standard-types. Do not execute invalid configurations at runtime.
|
|
18
|
+
function rejectMismatchedConstants(builder: ICompositeKeyBuilder<Key, Changed>): void {
|
|
19
|
+
// @ts-expect-error A number key part cannot receive a string constant.
|
|
20
|
+
builder.set(key => key.number).toValue('orders');
|
|
21
|
+
// @ts-expect-error A string key part cannot receive a number constant.
|
|
22
|
+
builder.set(key => key.text).toValue(42);
|
|
23
|
+
// @ts-expect-error Null requires a nullable key part.
|
|
24
|
+
builder.set(key => key.text).toValue(null);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rejectOnConcreteBuilder(builder: CompositeKeyBuilder<Key, Changed>): void {
|
|
28
|
+
// @ts-expect-error The concrete overload must not fall back to an untyped accessor.
|
|
29
|
+
builder.set(key => key.number).toValue('orders');
|
|
30
|
+
}
|
|
31
|
+
void rejectMismatchedConstants;
|
|
32
|
+
void rejectOnConcreteBuilder;
|
|
33
|
+
|
|
34
|
+
describe('when typing constant composite parts', () => {
|
|
35
|
+
it('should accept selected property types and preserve fluent chaining', () => {
|
|
36
|
+
const builder: ICompositeKeyBuilder<Key, Changed> = new CompositeKeyBuilder<Key, Changed>();
|
|
37
|
+
builder.set(key => key.number).toValue(42)
|
|
38
|
+
.set(key => key.text).toValue('orders')
|
|
39
|
+
.set(key => key.nullable).toValue(null)
|
|
40
|
+
.set(key => key.sourceNumber, event => event.number);
|
|
41
|
+
builder.build().should.equal('$composite(number=$value(42),text=$value(orders),nullable=$null,sourceNumber=number)');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -26,6 +26,7 @@ export type { IJoinBuilder } from './IJoinBuilder.js';
|
|
|
26
26
|
export type { IFromEveryBuilder } from './IFromEveryBuilder.js';
|
|
27
27
|
export type { IReadModelPropertiesBuilder } from './IReadModelPropertiesBuilder.js';
|
|
28
28
|
export type { ICompositeKeyBuilder } from './ICompositeKeyBuilder.js';
|
|
29
|
+
export type { ICompositeKeySetBuilder } from './ICompositeKeySetBuilder.js';
|
|
29
30
|
export type { ISetBuilder } from './ISetBuilder.js';
|
|
30
31
|
export type { IAllSetBuilder } from './IAllSetBuilder.js';
|
|
31
32
|
export type { IAddBuilder } from './IAddBuilder.js';
|