@arts-n-crafts/ts 3.18.1 → 3.19.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.
|
@@ -148,8 +148,6 @@ interface Handling<TEvent extends DomainEvent | IntegrationEvent | ExternalEvent
|
|
|
148
148
|
interface EventHandler<TEvent extends DomainEvent | IntegrationEvent | ExternalEvent, TReturnType = Promise<void>> extends Handling<TEvent, TReturnType> {
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
type Module = Record<symbol, unknown>;
|
|
152
|
-
|
|
153
151
|
interface QueryMetadata extends BaseMetadata {
|
|
154
152
|
}
|
|
155
153
|
interface Query<TType = string, TPayload = unknown> extends WithIdentifier {
|
|
@@ -231,7 +229,7 @@ interface RejectionMetadata extends BaseMetadata {
|
|
|
231
229
|
/** Aggregate id when known (for correlation/partitioning). */
|
|
232
230
|
aggregateId?: string;
|
|
233
231
|
}
|
|
234
|
-
interface Rejection<TDetails =
|
|
232
|
+
interface Rejection<TDetails = object> {
|
|
235
233
|
/** Unique id; derive from commandId if possible for dedupe. */
|
|
236
234
|
id: string;
|
|
237
235
|
/** Rejection type, format e.g., "{commandType}Rejected", "{commandType}Failed". Example "CreateUserRejected" */
|
|
@@ -266,8 +264,8 @@ interface Rejection<TDetails = unknown> {
|
|
|
266
264
|
metadata?: RejectionMetadata;
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
interface Decider<TState, TCommand, TEvent extends DomainEvent
|
|
270
|
-
decide(this: void, command: TCommand, currentState: TState):
|
|
267
|
+
interface Decider<TState, TCommand, TEvent extends DomainEvent> {
|
|
268
|
+
decide(this: void, command: TCommand, currentState: TState): TEvent[] | Rejection;
|
|
271
269
|
evolve(this: void, currentState: TState, event: TEvent): TState;
|
|
272
270
|
initialState(this: void, id: string): TState;
|
|
273
271
|
}
|
|
@@ -346,6 +344,10 @@ declare function createQueryNode(type: 'eq' | 'gt' | 'lt', field: string | numbe
|
|
|
346
344
|
declare function createQueryNode(type: 'and' | 'or', field: undefined, value: QueryNode[]): QueryNode;
|
|
347
345
|
declare function createQueryNode(type: 'not', field: undefined, value: QueryNode): QueryNode;
|
|
348
346
|
|
|
347
|
+
declare function convertDomainEventToIntegrationEvent(event: DomainEvent): IntegrationEvent;
|
|
348
|
+
|
|
349
|
+
declare function convertRejectionToIntegrationEvent(rejection: Rejection): IntegrationEvent;
|
|
350
|
+
|
|
349
351
|
declare function createDomainEvent<TPayload = unknown>(type: string, aggregateId: string, aggregateType: string, payload: TPayload, metadata?: Partial<DomainEventMetadata>): DomainEvent<TPayload>;
|
|
350
352
|
|
|
351
353
|
declare function createRejection<TDetails = unknown>(rejectionSpecifics: {
|
|
@@ -364,7 +366,7 @@ declare function isDomainEvent(event: unknown): event is DomainEvent;
|
|
|
364
366
|
|
|
365
367
|
declare function isEvent(event: unknown): event is DomainEvent | IntegrationEvent | ExternalEvent | Rejection;
|
|
366
368
|
|
|
367
|
-
declare function isRejection(
|
|
369
|
+
declare function isRejection(candidate: unknown): candidate is Rejection;
|
|
368
370
|
|
|
369
371
|
interface Registerable$1<TCommand extends Command, TResult = void> {
|
|
370
372
|
register(aTypeOfCommand: TCommand['type'], anHandler: CommandHandler<TCommand>): TResult;
|
|
@@ -497,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
|
|
|
497
499
|
|
|
498
500
|
interface OutboxEntry {
|
|
499
501
|
id: string;
|
|
500
|
-
event:
|
|
502
|
+
event: IntegrationEvent;
|
|
501
503
|
published: boolean;
|
|
502
504
|
retryCount: number;
|
|
503
505
|
lastAttemptAt?: number;
|
|
504
506
|
}
|
|
505
507
|
|
|
506
508
|
interface Queueable<TReturnType = Promise<void>> {
|
|
507
|
-
enqueue(event:
|
|
509
|
+
enqueue(event: DomainEvent | Rejection): TReturnType;
|
|
508
510
|
}
|
|
509
511
|
interface Outbox<TEnqueueReturnType = Promise<void>, TGetPendingReturnType = Promise<OutboxEntry[]>, TMarkAsPublishedReturnType = Promise<void>, TMarkAsFailedReturnType = Promise<void>> extends Queueable<TEnqueueReturnType> {
|
|
510
512
|
getPending(limit?: number): TGetPendingReturnType;
|
|
@@ -548,13 +550,13 @@ declare class GenericOutboxWorker implements OutboxWorker {
|
|
|
548
550
|
declare class InMemoryOutbox implements Outbox {
|
|
549
551
|
protected entries: OutboxEntry[];
|
|
550
552
|
protected idCounter: number;
|
|
551
|
-
enqueue(event: DomainEvent
|
|
553
|
+
enqueue(event: DomainEvent | Rejection): Promise<void>;
|
|
552
554
|
getPending(limit?: number): Promise<OutboxEntry[]>;
|
|
553
555
|
markAsPublished(id: string): Promise<void>;
|
|
554
556
|
markAsFailed(id: string): Promise<void>;
|
|
555
557
|
}
|
|
556
558
|
|
|
557
|
-
declare function createOutboxEntry(event:
|
|
559
|
+
declare function createOutboxEntry(event: IntegrationEvent): OutboxEntry;
|
|
558
560
|
|
|
559
561
|
interface Registerable<TQuery extends Query, TResult = void> {
|
|
560
562
|
register(aTypeOfQuery: TQuery['type'], anHandler: QueryHandler<TQuery>): TResult;
|
|
@@ -571,16 +573,44 @@ declare class SimpleQueryBus<TQuery extends Query, TProjection> implements Query
|
|
|
571
573
|
execute(aQuery: TQuery): Promise<TProjection>;
|
|
572
574
|
}
|
|
573
575
|
|
|
574
|
-
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent
|
|
576
|
+
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent> implements Repository<TEvent, Promise<TState>, Promise<void>> {
|
|
575
577
|
private readonly eventStore;
|
|
576
578
|
readonly streamName: string;
|
|
577
579
|
private readonly evolveFn;
|
|
578
580
|
private readonly initialState;
|
|
579
|
-
constructor(eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, streamName: string, evolveFn: Decider<TState, TCommand, TEvent
|
|
581
|
+
constructor(eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, streamName: string, evolveFn: Decider<TState, TCommand, TEvent>['evolve'], initialState: Decider<TState, TCommand, TEvent>['initialState']);
|
|
580
582
|
load(aggregateId: string): Promise<TState>;
|
|
581
583
|
store(events: TEvent[]): Promise<void>;
|
|
582
584
|
}
|
|
583
585
|
|
|
586
|
+
type GivenInput = DomainEvent[];
|
|
587
|
+
type WhenInput = Command | Query | DomainEvent | IntegrationEvent | ExternalEvent;
|
|
588
|
+
type ThenInput = DomainEvent | Rejection | Array<Record<string, unknown>>;
|
|
589
|
+
declare class ScenarioTest<TState, TEvent extends DomainEvent> {
|
|
590
|
+
private readonly streamName;
|
|
591
|
+
private readonly eventBus;
|
|
592
|
+
private readonly eventStore;
|
|
593
|
+
private readonly commandBus;
|
|
594
|
+
private readonly queryBus;
|
|
595
|
+
private readonly repository;
|
|
596
|
+
private readonly outboxWorker;
|
|
597
|
+
private readonly outbox;
|
|
598
|
+
private givenInput;
|
|
599
|
+
private whenInput;
|
|
600
|
+
constructor(streamName: string, eventBus: EventProducer<DomainEvent | IntegrationEvent | ExternalEvent> & EventConsumer<DomainEvent | IntegrationEvent | ExternalEvent>, eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, commandBus: CommandBus<Command>, queryBus: QueryBus<Query, Promise<Record<string, unknown>[]>>, repository: Repository<DomainEvent, Promise<TState>>, outboxWorker: OutboxWorker, outbox: Outbox);
|
|
601
|
+
given(...events: GivenInput): {
|
|
602
|
+
when(action: WhenInput): ReturnType<ScenarioTest<TState, TEvent>['when']>;
|
|
603
|
+
then(outcome: ThenInput): Promise<void>;
|
|
604
|
+
};
|
|
605
|
+
when(action: WhenInput): {
|
|
606
|
+
then(outcome: ThenInput): Promise<void>;
|
|
607
|
+
};
|
|
608
|
+
then(thenInput: ThenInput): Promise<void>;
|
|
609
|
+
private handleCommand;
|
|
610
|
+
private handleQuery;
|
|
611
|
+
private handleEvent;
|
|
612
|
+
}
|
|
613
|
+
|
|
584
614
|
declare function fail(anExpression: Error): () => never;
|
|
585
615
|
|
|
586
616
|
declare function invariant(condition: boolean, onInvalid: () => never): asserts condition;
|
|
@@ -591,4 +621,4 @@ declare function parseAsError(value: unknown): Error;
|
|
|
591
621
|
|
|
592
622
|
declare function makeStreamKey(streamName: string, aggregateId: string): StreamKey;
|
|
593
623
|
|
|
594
|
-
export { type AggregateRoot, AndSpecification, type BaseMetadata, type Command, type CommandBus, type CommandHandler, type CommandMetadata, CompositeSpecification, type CreateStatement, type Database, type Decider, type DeleteStatement, type Directive, type DomainEvent, type DomainEventMetadata, type EventBasedAggregateRoot, type EventConsumer, type EventHandler, type EventProducer, type EventStore, FieldEquals, FieldGreaterThan, type FilledArray, GenericOutboxWorker, type ISODateTime, InMemoryOutbox, type IntegrationEvent, type IntegrationEventMetadata, type Maybe,
|
|
624
|
+
export { type AggregateRoot, AndSpecification, type BaseMetadata, type Command, type CommandBus, type CommandHandler, type CommandMetadata, CompositeSpecification, type CreateStatement, type Database, type Decider, type DeleteStatement, type Directive, type DomainEvent, type DomainEventMetadata, type EventBasedAggregateRoot, type EventConsumer, type EventHandler, type EventProducer, type EventStore, FieldEquals, FieldGreaterThan, type FilledArray, GenericOutboxWorker, type ISODateTime, InMemoryOutbox, type IntegrationEvent, type IntegrationEventMetadata, type Maybe, NotSpecification, type Nullable, Operation, OrSpecification, type Outbox, type OutboxEntry, type OutboxWorker, type PatchStatement, type Primitive, type PutStatement, type Query, type QueryBus, type QueryHandler, type QueryMetadata, type QueryNode, type Rejection, type RejectionMetadata, type Repository, ScenarioTest, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, type Specification, type StoredEvent, type StreamKey, type UnixTimestampInSeconds, type WithIdentifier, convertDomainEventToIntegrationEvent, convertRejectionToIntegrationEvent, createCommand, createDomainEvent, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, parseAsError };
|
|
@@ -148,8 +148,6 @@ interface Handling<TEvent extends DomainEvent | IntegrationEvent | ExternalEvent
|
|
|
148
148
|
interface EventHandler<TEvent extends DomainEvent | IntegrationEvent | ExternalEvent, TReturnType = Promise<void>> extends Handling<TEvent, TReturnType> {
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
type Module = Record<symbol, unknown>;
|
|
152
|
-
|
|
153
151
|
interface QueryMetadata extends BaseMetadata {
|
|
154
152
|
}
|
|
155
153
|
interface Query<TType = string, TPayload = unknown> extends WithIdentifier {
|
|
@@ -231,7 +229,7 @@ interface RejectionMetadata extends BaseMetadata {
|
|
|
231
229
|
/** Aggregate id when known (for correlation/partitioning). */
|
|
232
230
|
aggregateId?: string;
|
|
233
231
|
}
|
|
234
|
-
interface Rejection<TDetails =
|
|
232
|
+
interface Rejection<TDetails = object> {
|
|
235
233
|
/** Unique id; derive from commandId if possible for dedupe. */
|
|
236
234
|
id: string;
|
|
237
235
|
/** Rejection type, format e.g., "{commandType}Rejected", "{commandType}Failed". Example "CreateUserRejected" */
|
|
@@ -266,8 +264,8 @@ interface Rejection<TDetails = unknown> {
|
|
|
266
264
|
metadata?: RejectionMetadata;
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
interface Decider<TState, TCommand, TEvent extends DomainEvent
|
|
270
|
-
decide(this: void, command: TCommand, currentState: TState):
|
|
267
|
+
interface Decider<TState, TCommand, TEvent extends DomainEvent> {
|
|
268
|
+
decide(this: void, command: TCommand, currentState: TState): TEvent[] | Rejection;
|
|
271
269
|
evolve(this: void, currentState: TState, event: TEvent): TState;
|
|
272
270
|
initialState(this: void, id: string): TState;
|
|
273
271
|
}
|
|
@@ -346,6 +344,10 @@ declare function createQueryNode(type: 'eq' | 'gt' | 'lt', field: string | numbe
|
|
|
346
344
|
declare function createQueryNode(type: 'and' | 'or', field: undefined, value: QueryNode[]): QueryNode;
|
|
347
345
|
declare function createQueryNode(type: 'not', field: undefined, value: QueryNode): QueryNode;
|
|
348
346
|
|
|
347
|
+
declare function convertDomainEventToIntegrationEvent(event: DomainEvent): IntegrationEvent;
|
|
348
|
+
|
|
349
|
+
declare function convertRejectionToIntegrationEvent(rejection: Rejection): IntegrationEvent;
|
|
350
|
+
|
|
349
351
|
declare function createDomainEvent<TPayload = unknown>(type: string, aggregateId: string, aggregateType: string, payload: TPayload, metadata?: Partial<DomainEventMetadata>): DomainEvent<TPayload>;
|
|
350
352
|
|
|
351
353
|
declare function createRejection<TDetails = unknown>(rejectionSpecifics: {
|
|
@@ -364,7 +366,7 @@ declare function isDomainEvent(event: unknown): event is DomainEvent;
|
|
|
364
366
|
|
|
365
367
|
declare function isEvent(event: unknown): event is DomainEvent | IntegrationEvent | ExternalEvent | Rejection;
|
|
366
368
|
|
|
367
|
-
declare function isRejection(
|
|
369
|
+
declare function isRejection(candidate: unknown): candidate is Rejection;
|
|
368
370
|
|
|
369
371
|
interface Registerable$1<TCommand extends Command, TResult = void> {
|
|
370
372
|
register(aTypeOfCommand: TCommand['type'], anHandler: CommandHandler<TCommand>): TResult;
|
|
@@ -497,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
|
|
|
497
499
|
|
|
498
500
|
interface OutboxEntry {
|
|
499
501
|
id: string;
|
|
500
|
-
event:
|
|
502
|
+
event: IntegrationEvent;
|
|
501
503
|
published: boolean;
|
|
502
504
|
retryCount: number;
|
|
503
505
|
lastAttemptAt?: number;
|
|
504
506
|
}
|
|
505
507
|
|
|
506
508
|
interface Queueable<TReturnType = Promise<void>> {
|
|
507
|
-
enqueue(event:
|
|
509
|
+
enqueue(event: DomainEvent | Rejection): TReturnType;
|
|
508
510
|
}
|
|
509
511
|
interface Outbox<TEnqueueReturnType = Promise<void>, TGetPendingReturnType = Promise<OutboxEntry[]>, TMarkAsPublishedReturnType = Promise<void>, TMarkAsFailedReturnType = Promise<void>> extends Queueable<TEnqueueReturnType> {
|
|
510
512
|
getPending(limit?: number): TGetPendingReturnType;
|
|
@@ -548,13 +550,13 @@ declare class GenericOutboxWorker implements OutboxWorker {
|
|
|
548
550
|
declare class InMemoryOutbox implements Outbox {
|
|
549
551
|
protected entries: OutboxEntry[];
|
|
550
552
|
protected idCounter: number;
|
|
551
|
-
enqueue(event: DomainEvent
|
|
553
|
+
enqueue(event: DomainEvent | Rejection): Promise<void>;
|
|
552
554
|
getPending(limit?: number): Promise<OutboxEntry[]>;
|
|
553
555
|
markAsPublished(id: string): Promise<void>;
|
|
554
556
|
markAsFailed(id: string): Promise<void>;
|
|
555
557
|
}
|
|
556
558
|
|
|
557
|
-
declare function createOutboxEntry(event:
|
|
559
|
+
declare function createOutboxEntry(event: IntegrationEvent): OutboxEntry;
|
|
558
560
|
|
|
559
561
|
interface Registerable<TQuery extends Query, TResult = void> {
|
|
560
562
|
register(aTypeOfQuery: TQuery['type'], anHandler: QueryHandler<TQuery>): TResult;
|
|
@@ -571,16 +573,44 @@ declare class SimpleQueryBus<TQuery extends Query, TProjection> implements Query
|
|
|
571
573
|
execute(aQuery: TQuery): Promise<TProjection>;
|
|
572
574
|
}
|
|
573
575
|
|
|
574
|
-
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent
|
|
576
|
+
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent> implements Repository<TEvent, Promise<TState>, Promise<void>> {
|
|
575
577
|
private readonly eventStore;
|
|
576
578
|
readonly streamName: string;
|
|
577
579
|
private readonly evolveFn;
|
|
578
580
|
private readonly initialState;
|
|
579
|
-
constructor(eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, streamName: string, evolveFn: Decider<TState, TCommand, TEvent
|
|
581
|
+
constructor(eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, streamName: string, evolveFn: Decider<TState, TCommand, TEvent>['evolve'], initialState: Decider<TState, TCommand, TEvent>['initialState']);
|
|
580
582
|
load(aggregateId: string): Promise<TState>;
|
|
581
583
|
store(events: TEvent[]): Promise<void>;
|
|
582
584
|
}
|
|
583
585
|
|
|
586
|
+
type GivenInput = DomainEvent[];
|
|
587
|
+
type WhenInput = Command | Query | DomainEvent | IntegrationEvent | ExternalEvent;
|
|
588
|
+
type ThenInput = DomainEvent | Rejection | Array<Record<string, unknown>>;
|
|
589
|
+
declare class ScenarioTest<TState, TEvent extends DomainEvent> {
|
|
590
|
+
private readonly streamName;
|
|
591
|
+
private readonly eventBus;
|
|
592
|
+
private readonly eventStore;
|
|
593
|
+
private readonly commandBus;
|
|
594
|
+
private readonly queryBus;
|
|
595
|
+
private readonly repository;
|
|
596
|
+
private readonly outboxWorker;
|
|
597
|
+
private readonly outbox;
|
|
598
|
+
private givenInput;
|
|
599
|
+
private whenInput;
|
|
600
|
+
constructor(streamName: string, eventBus: EventProducer<DomainEvent | IntegrationEvent | ExternalEvent> & EventConsumer<DomainEvent | IntegrationEvent | ExternalEvent>, eventStore: EventStore<TEvent, Promise<void>, Promise<TEvent[]>>, commandBus: CommandBus<Command>, queryBus: QueryBus<Query, Promise<Record<string, unknown>[]>>, repository: Repository<DomainEvent, Promise<TState>>, outboxWorker: OutboxWorker, outbox: Outbox);
|
|
601
|
+
given(...events: GivenInput): {
|
|
602
|
+
when(action: WhenInput): ReturnType<ScenarioTest<TState, TEvent>['when']>;
|
|
603
|
+
then(outcome: ThenInput): Promise<void>;
|
|
604
|
+
};
|
|
605
|
+
when(action: WhenInput): {
|
|
606
|
+
then(outcome: ThenInput): Promise<void>;
|
|
607
|
+
};
|
|
608
|
+
then(thenInput: ThenInput): Promise<void>;
|
|
609
|
+
private handleCommand;
|
|
610
|
+
private handleQuery;
|
|
611
|
+
private handleEvent;
|
|
612
|
+
}
|
|
613
|
+
|
|
584
614
|
declare function fail(anExpression: Error): () => never;
|
|
585
615
|
|
|
586
616
|
declare function invariant(condition: boolean, onInvalid: () => never): asserts condition;
|
|
@@ -591,4 +621,4 @@ declare function parseAsError(value: unknown): Error;
|
|
|
591
621
|
|
|
592
622
|
declare function makeStreamKey(streamName: string, aggregateId: string): StreamKey;
|
|
593
623
|
|
|
594
|
-
export { type AggregateRoot, AndSpecification, type BaseMetadata, type Command, type CommandBus, type CommandHandler, type CommandMetadata, CompositeSpecification, type CreateStatement, type Database, type Decider, type DeleteStatement, type Directive, type DomainEvent, type DomainEventMetadata, type EventBasedAggregateRoot, type EventConsumer, type EventHandler, type EventProducer, type EventStore, FieldEquals, FieldGreaterThan, type FilledArray, GenericOutboxWorker, type ISODateTime, InMemoryOutbox, type IntegrationEvent, type IntegrationEventMetadata, type Maybe,
|
|
624
|
+
export { type AggregateRoot, AndSpecification, type BaseMetadata, type Command, type CommandBus, type CommandHandler, type CommandMetadata, CompositeSpecification, type CreateStatement, type Database, type Decider, type DeleteStatement, type Directive, type DomainEvent, type DomainEventMetadata, type EventBasedAggregateRoot, type EventConsumer, type EventHandler, type EventProducer, type EventStore, FieldEquals, FieldGreaterThan, type FilledArray, GenericOutboxWorker, type ISODateTime, InMemoryOutbox, type IntegrationEvent, type IntegrationEventMetadata, type Maybe, NotSpecification, type Nullable, Operation, OrSpecification, type Outbox, type OutboxEntry, type OutboxWorker, type PatchStatement, type Primitive, type PutStatement, type Query, type QueryBus, type QueryHandler, type QueryMetadata, type QueryNode, type Rejection, type RejectionMetadata, type Repository, ScenarioTest, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, type Specification, type StoredEvent, type StreamKey, type UnixTimestampInSeconds, type WithIdentifier, convertDomainEventToIntegrationEvent, convertRejectionToIntegrationEvent, createCommand, createDomainEvent, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, parseAsError };
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'crypto';
|
|
|
3
3
|
// src/core/utils/createCommand.ts
|
|
4
4
|
|
|
5
5
|
// src/core/utils/getTimestamp.ts
|
|
6
|
-
var getTimestamp = (date = /* @__PURE__ */ new Date()) =>
|
|
6
|
+
var getTimestamp = (date = /* @__PURE__ */ new Date()) => date.getTime();
|
|
7
7
|
|
|
8
8
|
// src/core/utils/createCommand.ts
|
|
9
9
|
function createCommand(type, aggregateId, aggregateType, payload, metadata = {}) {
|
|
@@ -178,6 +178,42 @@ var FieldGreaterThan = class extends CompositeSpecification {
|
|
|
178
178
|
return createQueryNode("gt", this.field, this.value);
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
+
function createIntegrationEvent(type, payload, metadata) {
|
|
182
|
+
return Object.freeze({
|
|
183
|
+
id: randomUUID(),
|
|
184
|
+
type,
|
|
185
|
+
payload,
|
|
186
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
metadata: {
|
|
188
|
+
...metadata
|
|
189
|
+
},
|
|
190
|
+
kind: "integration"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/domain/utils/convertDomainEventToIntegrationEvent.ts
|
|
195
|
+
function convertDomainEventToIntegrationEvent(event) {
|
|
196
|
+
return createIntegrationEvent(event.type, event.payload, {
|
|
197
|
+
outcome: "accepted",
|
|
198
|
+
aggregateType: event.aggregateType,
|
|
199
|
+
aggregateId: event.aggregateId
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/domain/utils/convertRejectionToIntegrationEvent.ts
|
|
204
|
+
function convertRejectionToIntegrationEvent(rejection) {
|
|
205
|
+
return createIntegrationEvent(
|
|
206
|
+
rejection.commandType,
|
|
207
|
+
{ ...rejection.details, reasonCode: rejection.reasonCode },
|
|
208
|
+
{
|
|
209
|
+
outcome: "rejected",
|
|
210
|
+
aggregateType: rejection.metadata?.aggregateType,
|
|
211
|
+
aggregateId: rejection.metadata?.aggregateId,
|
|
212
|
+
commandType: rejection.commandType,
|
|
213
|
+
commandId: rejection.commandId
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
181
217
|
function createDomainEvent(type, aggregateId, aggregateType, payload, metadata = {}) {
|
|
182
218
|
return Object.freeze({
|
|
183
219
|
id: randomUUID(),
|
|
@@ -225,8 +261,10 @@ function isDomainEvent(event) {
|
|
|
225
261
|
}
|
|
226
262
|
|
|
227
263
|
// src/domain/utils/isRejection.ts
|
|
228
|
-
function isRejection(
|
|
229
|
-
|
|
264
|
+
function isRejection(candidate) {
|
|
265
|
+
if (candidate === null || typeof candidate !== "object")
|
|
266
|
+
return false;
|
|
267
|
+
return "commandId" in candidate && "commandType" in candidate && "reasonCode" in candidate && "timestamp" in candidate;
|
|
230
268
|
}
|
|
231
269
|
|
|
232
270
|
// src/infrastructure/CommandBus/implementations/SimpleCommandBus.ts
|
|
@@ -357,18 +395,6 @@ function createExternalEvent(type, payload, metadata) {
|
|
|
357
395
|
kind: "external"
|
|
358
396
|
});
|
|
359
397
|
}
|
|
360
|
-
function createIntegrationEvent(type, payload, metadata) {
|
|
361
|
-
return Object.freeze({
|
|
362
|
-
id: randomUUID(),
|
|
363
|
-
type,
|
|
364
|
-
payload,
|
|
365
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
366
|
-
metadata: {
|
|
367
|
-
...metadata
|
|
368
|
-
},
|
|
369
|
-
kind: "integration"
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
398
|
|
|
373
399
|
// src/infrastructure/EventBus/utils/isExternalEvent.ts
|
|
374
400
|
function isExternalEvent(event) {
|
|
@@ -527,18 +553,23 @@ var GenericOutboxWorker = class {
|
|
|
527
553
|
}, intervalMs);
|
|
528
554
|
}
|
|
529
555
|
};
|
|
556
|
+
function createOutboxEntry(event) {
|
|
557
|
+
return {
|
|
558
|
+
id: randomUUID(),
|
|
559
|
+
event,
|
|
560
|
+
published: false,
|
|
561
|
+
retryCount: 0,
|
|
562
|
+
lastAttemptAt: void 0
|
|
563
|
+
};
|
|
564
|
+
}
|
|
530
565
|
|
|
531
566
|
// src/infrastructure/Outbox/implementations/InMemoryOutbox.ts
|
|
532
567
|
var InMemoryOutbox = class {
|
|
533
568
|
entries = [];
|
|
534
569
|
idCounter = 0;
|
|
535
570
|
async enqueue(event) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
event,
|
|
539
|
-
published: false,
|
|
540
|
-
retryCount: 0
|
|
541
|
-
});
|
|
571
|
+
const integrationEvent = isRejection(event) ? convertRejectionToIntegrationEvent(event) : convertDomainEventToIntegrationEvent(event);
|
|
572
|
+
this.entries.push(createOutboxEntry(integrationEvent));
|
|
542
573
|
}
|
|
543
574
|
async getPending(limit = 100) {
|
|
544
575
|
return this.entries.filter((e) => !e.published).slice(0, limit);
|
|
@@ -557,15 +588,6 @@ var InMemoryOutbox = class {
|
|
|
557
588
|
}
|
|
558
589
|
}
|
|
559
590
|
};
|
|
560
|
-
function createOutboxEntry(event) {
|
|
561
|
-
return Object.freeze({
|
|
562
|
-
id: randomUUID(),
|
|
563
|
-
event,
|
|
564
|
-
published: false,
|
|
565
|
-
retryCount: 0,
|
|
566
|
-
lastAttemptAt: void 0
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
591
|
|
|
570
592
|
// src/infrastructure/QueryBus/implementations/SimpleQueryBus.ts
|
|
571
593
|
var SimpleQueryBus = class {
|
|
@@ -609,6 +631,122 @@ var SimpleRepository = class {
|
|
|
609
631
|
}
|
|
610
632
|
};
|
|
611
633
|
|
|
612
|
-
|
|
634
|
+
// src/infrastructure/ScenarioTest/ScenarioTest.ts
|
|
635
|
+
var ScenarioTest = class {
|
|
636
|
+
constructor(streamName, eventBus, eventStore, commandBus, queryBus, repository, outboxWorker, outbox) {
|
|
637
|
+
this.streamName = streamName;
|
|
638
|
+
this.eventBus = eventBus;
|
|
639
|
+
this.eventStore = eventStore;
|
|
640
|
+
this.commandBus = commandBus;
|
|
641
|
+
this.queryBus = queryBus;
|
|
642
|
+
this.repository = repository;
|
|
643
|
+
this.outboxWorker = outboxWorker;
|
|
644
|
+
this.outbox = outbox;
|
|
645
|
+
}
|
|
646
|
+
givenInput = [];
|
|
647
|
+
whenInput;
|
|
648
|
+
given(...events) {
|
|
649
|
+
this.givenInput = events;
|
|
650
|
+
return {
|
|
651
|
+
when: this.when.bind(this),
|
|
652
|
+
then: this.then.bind(this)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
when(action) {
|
|
656
|
+
this.whenInput = action;
|
|
657
|
+
return {
|
|
658
|
+
then: this.then.bind(this)
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async then(thenInput) {
|
|
662
|
+
await Promise.all([
|
|
663
|
+
this.repository.store(this.givenInput),
|
|
664
|
+
...this.givenInput.map(async (event) => this.eventBus.consume(this.streamName, event))
|
|
665
|
+
]);
|
|
666
|
+
if (!this.whenInput) {
|
|
667
|
+
throw new Error("In the ScenarioTest, the when-step cannot be empty");
|
|
668
|
+
}
|
|
669
|
+
if (isCommand(this.whenInput)) {
|
|
670
|
+
invariant(
|
|
671
|
+
isDomainEvent(thenInput) || isRejection(thenInput),
|
|
672
|
+
fail(new TypeError('When "command" expects a domain event or rejection in the then-step'))
|
|
673
|
+
);
|
|
674
|
+
await this.handleCommand(this.whenInput, thenInput);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (isQuery(this.whenInput)) {
|
|
678
|
+
invariant(
|
|
679
|
+
Array.isArray(thenInput),
|
|
680
|
+
fail(new TypeError('When "query" expects an array of expected results in the then-step'))
|
|
681
|
+
);
|
|
682
|
+
await this.handleQuery(this.whenInput, thenInput);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
invariant(
|
|
686
|
+
isDomainEvent(thenInput),
|
|
687
|
+
fail(new TypeError('When "domain event" or "integration event" expects a domain event in the then-step'))
|
|
688
|
+
);
|
|
689
|
+
await this.handleEvent(this.whenInput, thenInput);
|
|
690
|
+
}
|
|
691
|
+
async handleCommand(command, outcome) {
|
|
692
|
+
await this.commandBus.execute(command);
|
|
693
|
+
if (isRejection(outcome)) {
|
|
694
|
+
const pending = await this.outbox.getPending();
|
|
695
|
+
const foundRejection = pending.find(
|
|
696
|
+
(entry) => entry.event.metadata.outcome === "rejected" && entry.event.metadata.commandType === outcome.commandType && entry.event.payload.reasonCode === outcome.reasonCode
|
|
697
|
+
);
|
|
698
|
+
invariant(!!foundRejection, fail(new Error("ScenarioTest: rejection was not found in outbox")));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const actualEvents = await this.eventStore.load(this.streamName, outcome.aggregateId);
|
|
702
|
+
const foundEvent = actualEvents.findLast(
|
|
703
|
+
(event) => isDomainEvent(event) && event.aggregateId === outcome.aggregateId && event.type === outcome.type
|
|
704
|
+
);
|
|
705
|
+
invariant(!!foundEvent, fail(new Error("ScenarioTest: event/command was not found")));
|
|
706
|
+
invariant(
|
|
707
|
+
outcome.type === foundEvent.type,
|
|
708
|
+
fail(new Error("ScenarioTest: event/command type was not equal"))
|
|
709
|
+
);
|
|
710
|
+
invariant(
|
|
711
|
+
outcome.aggregateId === foundEvent.aggregateId,
|
|
712
|
+
fail(new Error("ScenarioTest: event/command aggregate id was not equal"))
|
|
713
|
+
);
|
|
714
|
+
invariant(
|
|
715
|
+
isEqual(outcome.payload, foundEvent.payload),
|
|
716
|
+
fail(new Error("ScenarioTest: event/command payload was not equal"))
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
async handleQuery(query, expected) {
|
|
720
|
+
await this.outboxWorker.tick();
|
|
721
|
+
const actual = await this.queryBus.execute(query);
|
|
722
|
+
invariant(
|
|
723
|
+
isEqual(actual, expected),
|
|
724
|
+
fail(new Error("ScenarioTest: a different query result was returned"))
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
async handleEvent(event, outcome) {
|
|
728
|
+
await this.eventBus.publish(this.streamName, event);
|
|
729
|
+
const actualEvents = await this.eventStore.load(this.streamName, outcome.aggregateId);
|
|
730
|
+
const foundEvent = actualEvents.findLast(
|
|
731
|
+
(event2) => isEvent(event2) && event2.aggregateId === outcome.aggregateId && event2.type === outcome.type
|
|
732
|
+
);
|
|
733
|
+
invariant(!!foundEvent, fail(new Error("ScenarioTest: event was not found")));
|
|
734
|
+
invariant(isEvent(foundEvent), fail(new Error("ScenarioTest: event is not of type event")));
|
|
735
|
+
invariant(
|
|
736
|
+
outcome.type === foundEvent.type,
|
|
737
|
+
fail(new Error("ScenarioTest: event type was not equal"))
|
|
738
|
+
);
|
|
739
|
+
invariant(
|
|
740
|
+
outcome.aggregateId === foundEvent.aggregateId,
|
|
741
|
+
fail(new Error("ScenarioTest: event aggregate id was not equal"))
|
|
742
|
+
);
|
|
743
|
+
invariant(
|
|
744
|
+
isEqual(outcome.payload, foundEvent.payload),
|
|
745
|
+
fail(new Error("ScenarioTest: event payload was not equal"))
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
export { AndSpecification, CompositeSpecification, FieldEquals, FieldGreaterThan, GenericOutboxWorker, InMemoryOutbox, NotSpecification, Operation, OrSpecification, ScenarioTest, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, convertDomainEventToIntegrationEvent, convertRejectionToIntegrationEvent, createCommand, createDomainEvent, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, parseAsError };
|
|
613
751
|
//# sourceMappingURL=index.js.map
|
|
614
752
|
//# sourceMappingURL=index.js.map
|