@arts-n-crafts/ts 3.18.0 → 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;
|
|
@@ -448,10 +450,25 @@ declare class SimpleEventBus<TEvent extends DomainEvent | IntegrationEvent | Ext
|
|
|
448
450
|
publish(stream: string, anEvent: TEvent): Promise<void>;
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
declare function createExternalEvent<TPayload = unknown>(type: string, payload: TPayload, metadata?: Partial<ExternalEventMetadata>): ExternalEvent<TPayload>;
|
|
454
|
+
|
|
451
455
|
declare function createIntegrationEvent<TPayload = unknown>(type: string, payload: TPayload, metadata?: Partial<IntegrationEventMetadata>): IntegrationEvent<TPayload>;
|
|
452
456
|
|
|
457
|
+
declare function isExternalEvent<TPayload>(event: unknown): event is ExternalEvent<TPayload>;
|
|
458
|
+
|
|
453
459
|
declare function isIntegrationEvent<TPayload>(event: unknown): event is IntegrationEvent<TPayload>;
|
|
454
460
|
|
|
461
|
+
declare function mapDomainEventToIntegrationEvent<TPayload>(domainEvent: DomainEvent<TPayload>): IntegrationEvent<TPayload>;
|
|
462
|
+
|
|
463
|
+
declare function mapRejectionToIntegrationEvent<TDetails>(rejection: Rejection<TDetails>): IntegrationEvent<{
|
|
464
|
+
reasonCode: Rejection<TDetails>['reasonCode'];
|
|
465
|
+
reason?: string;
|
|
466
|
+
classification?: Rejection<TDetails>['classification'];
|
|
467
|
+
retryable?: boolean;
|
|
468
|
+
details?: TDetails;
|
|
469
|
+
validationErrors?: Rejection<TDetails>['validationErrors'];
|
|
470
|
+
}>;
|
|
471
|
+
|
|
455
472
|
type StreamKey = `${string}#${string}`;
|
|
456
473
|
|
|
457
474
|
/**
|
|
@@ -482,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
|
|
|
482
499
|
|
|
483
500
|
interface OutboxEntry {
|
|
484
501
|
id: string;
|
|
485
|
-
event:
|
|
502
|
+
event: IntegrationEvent;
|
|
486
503
|
published: boolean;
|
|
487
504
|
retryCount: number;
|
|
488
505
|
lastAttemptAt?: number;
|
|
489
506
|
}
|
|
490
507
|
|
|
491
508
|
interface Queueable<TReturnType = Promise<void>> {
|
|
492
|
-
enqueue(event:
|
|
509
|
+
enqueue(event: DomainEvent | Rejection): TReturnType;
|
|
493
510
|
}
|
|
494
511
|
interface Outbox<TEnqueueReturnType = Promise<void>, TGetPendingReturnType = Promise<OutboxEntry[]>, TMarkAsPublishedReturnType = Promise<void>, TMarkAsFailedReturnType = Promise<void>> extends Queueable<TEnqueueReturnType> {
|
|
495
512
|
getPending(limit?: number): TGetPendingReturnType;
|
|
@@ -533,13 +550,13 @@ declare class GenericOutboxWorker implements OutboxWorker {
|
|
|
533
550
|
declare class InMemoryOutbox implements Outbox {
|
|
534
551
|
protected entries: OutboxEntry[];
|
|
535
552
|
protected idCounter: number;
|
|
536
|
-
enqueue(event: DomainEvent
|
|
553
|
+
enqueue(event: DomainEvent | Rejection): Promise<void>;
|
|
537
554
|
getPending(limit?: number): Promise<OutboxEntry[]>;
|
|
538
555
|
markAsPublished(id: string): Promise<void>;
|
|
539
556
|
markAsFailed(id: string): Promise<void>;
|
|
540
557
|
}
|
|
541
558
|
|
|
542
|
-
declare function createOutboxEntry(event:
|
|
559
|
+
declare function createOutboxEntry(event: IntegrationEvent): OutboxEntry;
|
|
543
560
|
|
|
544
561
|
interface Registerable<TQuery extends Query, TResult = void> {
|
|
545
562
|
register(aTypeOfQuery: TQuery['type'], anHandler: QueryHandler<TQuery>): TResult;
|
|
@@ -556,16 +573,44 @@ declare class SimpleQueryBus<TQuery extends Query, TProjection> implements Query
|
|
|
556
573
|
execute(aQuery: TQuery): Promise<TProjection>;
|
|
557
574
|
}
|
|
558
575
|
|
|
559
|
-
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent
|
|
576
|
+
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent> implements Repository<TEvent, Promise<TState>, Promise<void>> {
|
|
560
577
|
private readonly eventStore;
|
|
561
578
|
readonly streamName: string;
|
|
562
579
|
private readonly evolveFn;
|
|
563
580
|
private readonly initialState;
|
|
564
|
-
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']);
|
|
565
582
|
load(aggregateId: string): Promise<TState>;
|
|
566
583
|
store(events: TEvent[]): Promise<void>;
|
|
567
584
|
}
|
|
568
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
|
+
|
|
569
614
|
declare function fail(anExpression: Error): () => never;
|
|
570
615
|
|
|
571
616
|
declare function invariant(condition: boolean, onInvalid: () => never): asserts condition;
|
|
@@ -576,4 +621,4 @@ declare function parseAsError(value: unknown): Error;
|
|
|
576
621
|
|
|
577
622
|
declare function makeStreamKey(streamName: string, aggregateId: string): StreamKey;
|
|
578
623
|
|
|
579
|
-
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;
|
|
@@ -448,10 +450,25 @@ declare class SimpleEventBus<TEvent extends DomainEvent | IntegrationEvent | Ext
|
|
|
448
450
|
publish(stream: string, anEvent: TEvent): Promise<void>;
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
declare function createExternalEvent<TPayload = unknown>(type: string, payload: TPayload, metadata?: Partial<ExternalEventMetadata>): ExternalEvent<TPayload>;
|
|
454
|
+
|
|
451
455
|
declare function createIntegrationEvent<TPayload = unknown>(type: string, payload: TPayload, metadata?: Partial<IntegrationEventMetadata>): IntegrationEvent<TPayload>;
|
|
452
456
|
|
|
457
|
+
declare function isExternalEvent<TPayload>(event: unknown): event is ExternalEvent<TPayload>;
|
|
458
|
+
|
|
453
459
|
declare function isIntegrationEvent<TPayload>(event: unknown): event is IntegrationEvent<TPayload>;
|
|
454
460
|
|
|
461
|
+
declare function mapDomainEventToIntegrationEvent<TPayload>(domainEvent: DomainEvent<TPayload>): IntegrationEvent<TPayload>;
|
|
462
|
+
|
|
463
|
+
declare function mapRejectionToIntegrationEvent<TDetails>(rejection: Rejection<TDetails>): IntegrationEvent<{
|
|
464
|
+
reasonCode: Rejection<TDetails>['reasonCode'];
|
|
465
|
+
reason?: string;
|
|
466
|
+
classification?: Rejection<TDetails>['classification'];
|
|
467
|
+
retryable?: boolean;
|
|
468
|
+
details?: TDetails;
|
|
469
|
+
validationErrors?: Rejection<TDetails>['validationErrors'];
|
|
470
|
+
}>;
|
|
471
|
+
|
|
455
472
|
type StreamKey = `${string}#${string}`;
|
|
456
473
|
|
|
457
474
|
/**
|
|
@@ -482,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
|
|
|
482
499
|
|
|
483
500
|
interface OutboxEntry {
|
|
484
501
|
id: string;
|
|
485
|
-
event:
|
|
502
|
+
event: IntegrationEvent;
|
|
486
503
|
published: boolean;
|
|
487
504
|
retryCount: number;
|
|
488
505
|
lastAttemptAt?: number;
|
|
489
506
|
}
|
|
490
507
|
|
|
491
508
|
interface Queueable<TReturnType = Promise<void>> {
|
|
492
|
-
enqueue(event:
|
|
509
|
+
enqueue(event: DomainEvent | Rejection): TReturnType;
|
|
493
510
|
}
|
|
494
511
|
interface Outbox<TEnqueueReturnType = Promise<void>, TGetPendingReturnType = Promise<OutboxEntry[]>, TMarkAsPublishedReturnType = Promise<void>, TMarkAsFailedReturnType = Promise<void>> extends Queueable<TEnqueueReturnType> {
|
|
495
512
|
getPending(limit?: number): TGetPendingReturnType;
|
|
@@ -533,13 +550,13 @@ declare class GenericOutboxWorker implements OutboxWorker {
|
|
|
533
550
|
declare class InMemoryOutbox implements Outbox {
|
|
534
551
|
protected entries: OutboxEntry[];
|
|
535
552
|
protected idCounter: number;
|
|
536
|
-
enqueue(event: DomainEvent
|
|
553
|
+
enqueue(event: DomainEvent | Rejection): Promise<void>;
|
|
537
554
|
getPending(limit?: number): Promise<OutboxEntry[]>;
|
|
538
555
|
markAsPublished(id: string): Promise<void>;
|
|
539
556
|
markAsFailed(id: string): Promise<void>;
|
|
540
557
|
}
|
|
541
558
|
|
|
542
|
-
declare function createOutboxEntry(event:
|
|
559
|
+
declare function createOutboxEntry(event: IntegrationEvent): OutboxEntry;
|
|
543
560
|
|
|
544
561
|
interface Registerable<TQuery extends Query, TResult = void> {
|
|
545
562
|
register(aTypeOfQuery: TQuery['type'], anHandler: QueryHandler<TQuery>): TResult;
|
|
@@ -556,16 +573,44 @@ declare class SimpleQueryBus<TQuery extends Query, TProjection> implements Query
|
|
|
556
573
|
execute(aQuery: TQuery): Promise<TProjection>;
|
|
557
574
|
}
|
|
558
575
|
|
|
559
|
-
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent
|
|
576
|
+
declare class SimpleRepository<TState, TCommand, TEvent extends DomainEvent> implements Repository<TEvent, Promise<TState>, Promise<void>> {
|
|
560
577
|
private readonly eventStore;
|
|
561
578
|
readonly streamName: string;
|
|
562
579
|
private readonly evolveFn;
|
|
563
580
|
private readonly initialState;
|
|
564
|
-
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']);
|
|
565
582
|
load(aggregateId: string): Promise<TState>;
|
|
566
583
|
store(events: TEvent[]): Promise<void>;
|
|
567
584
|
}
|
|
568
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
|
+
|
|
569
614
|
declare function fail(anExpression: Error): () => never;
|
|
570
615
|
|
|
571
616
|
declare function invariant(condition: boolean, onInvalid: () => never): asserts condition;
|
|
@@ -576,4 +621,4 @@ declare function parseAsError(value: unknown): Error;
|
|
|
576
621
|
|
|
577
622
|
declare function makeStreamKey(streamName: string, aggregateId: string): StreamKey;
|
|
578
623
|
|
|
579
|
-
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
|
|
@@ -345,7 +383,7 @@ var SimpleEventBus = class {
|
|
|
345
383
|
await this.consume(stream, anEvent);
|
|
346
384
|
}
|
|
347
385
|
};
|
|
348
|
-
function
|
|
386
|
+
function createExternalEvent(type, payload, metadata) {
|
|
349
387
|
return Object.freeze({
|
|
350
388
|
id: randomUUID(),
|
|
351
389
|
type,
|
|
@@ -354,15 +392,63 @@ function createIntegrationEvent(type, payload, metadata) {
|
|
|
354
392
|
metadata: {
|
|
355
393
|
...metadata
|
|
356
394
|
},
|
|
357
|
-
kind: "
|
|
395
|
+
kind: "external"
|
|
358
396
|
});
|
|
359
397
|
}
|
|
360
398
|
|
|
399
|
+
// src/infrastructure/EventBus/utils/isExternalEvent.ts
|
|
400
|
+
function isExternalEvent(event) {
|
|
401
|
+
return isEvent(event) && event.kind === "external";
|
|
402
|
+
}
|
|
403
|
+
|
|
361
404
|
// src/infrastructure/EventBus/utils/isIntegrationEvent.ts
|
|
362
405
|
function isIntegrationEvent(event) {
|
|
363
406
|
return isEvent(event) && event.kind === "integration";
|
|
364
407
|
}
|
|
365
408
|
|
|
409
|
+
// src/infrastructure/EventBus/utils/mapDomainEventToIntegrationEvent.ts
|
|
410
|
+
function mapDomainEventToIntegrationEvent(domainEvent) {
|
|
411
|
+
const metadata = {
|
|
412
|
+
...domainEvent.metadata,
|
|
413
|
+
aggregateType: domainEvent.aggregateType,
|
|
414
|
+
aggregateId: domainEvent.aggregateId,
|
|
415
|
+
outcome: "accepted"
|
|
416
|
+
};
|
|
417
|
+
return {
|
|
418
|
+
id: domainEvent.id,
|
|
419
|
+
type: domainEvent.type,
|
|
420
|
+
payload: domainEvent.payload,
|
|
421
|
+
timestamp: new Date(domainEvent.timestamp).toISOString(),
|
|
422
|
+
metadata,
|
|
423
|
+
kind: "integration"
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/infrastructure/EventBus/utils/mapRejectionToIntegrationEvent.ts
|
|
428
|
+
function mapRejectionToIntegrationEvent(rejection) {
|
|
429
|
+
const metadata = {
|
|
430
|
+
...rejection.metadata,
|
|
431
|
+
outcome: "rejected",
|
|
432
|
+
commandId: rejection.commandId,
|
|
433
|
+
commandType: rejection.commandType
|
|
434
|
+
};
|
|
435
|
+
return {
|
|
436
|
+
id: rejection.id,
|
|
437
|
+
type: rejection.type,
|
|
438
|
+
payload: {
|
|
439
|
+
reasonCode: rejection.reasonCode,
|
|
440
|
+
reason: rejection.reason,
|
|
441
|
+
classification: rejection.classification,
|
|
442
|
+
retryable: rejection.retryable,
|
|
443
|
+
details: rejection.details,
|
|
444
|
+
validationErrors: rejection.validationErrors
|
|
445
|
+
},
|
|
446
|
+
timestamp: new Date(rejection.timestamp).toISOString(),
|
|
447
|
+
metadata,
|
|
448
|
+
kind: "integration"
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
366
452
|
// src/utils/streamKey/makeStreamKey.ts
|
|
367
453
|
function makeStreamKey(streamName, aggregateId) {
|
|
368
454
|
return `${streamName}#${aggregateId}`;
|
|
@@ -467,18 +553,23 @@ var GenericOutboxWorker = class {
|
|
|
467
553
|
}, intervalMs);
|
|
468
554
|
}
|
|
469
555
|
};
|
|
556
|
+
function createOutboxEntry(event) {
|
|
557
|
+
return {
|
|
558
|
+
id: randomUUID(),
|
|
559
|
+
event,
|
|
560
|
+
published: false,
|
|
561
|
+
retryCount: 0,
|
|
562
|
+
lastAttemptAt: void 0
|
|
563
|
+
};
|
|
564
|
+
}
|
|
470
565
|
|
|
471
566
|
// src/infrastructure/Outbox/implementations/InMemoryOutbox.ts
|
|
472
567
|
var InMemoryOutbox = class {
|
|
473
568
|
entries = [];
|
|
474
569
|
idCounter = 0;
|
|
475
570
|
async enqueue(event) {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
event,
|
|
479
|
-
published: false,
|
|
480
|
-
retryCount: 0
|
|
481
|
-
});
|
|
571
|
+
const integrationEvent = isRejection(event) ? convertRejectionToIntegrationEvent(event) : convertDomainEventToIntegrationEvent(event);
|
|
572
|
+
this.entries.push(createOutboxEntry(integrationEvent));
|
|
482
573
|
}
|
|
483
574
|
async getPending(limit = 100) {
|
|
484
575
|
return this.entries.filter((e) => !e.published).slice(0, limit);
|
|
@@ -497,15 +588,6 @@ var InMemoryOutbox = class {
|
|
|
497
588
|
}
|
|
498
589
|
}
|
|
499
590
|
};
|
|
500
|
-
function createOutboxEntry(event) {
|
|
501
|
-
return Object.freeze({
|
|
502
|
-
id: randomUUID(),
|
|
503
|
-
event,
|
|
504
|
-
published: false,
|
|
505
|
-
retryCount: 0,
|
|
506
|
-
lastAttemptAt: void 0
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
591
|
|
|
510
592
|
// src/infrastructure/QueryBus/implementations/SimpleQueryBus.ts
|
|
511
593
|
var SimpleQueryBus = class {
|
|
@@ -549,6 +631,122 @@ var SimpleRepository = class {
|
|
|
549
631
|
}
|
|
550
632
|
};
|
|
551
633
|
|
|
552
|
-
|
|
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 };
|
|
553
751
|
//# sourceMappingURL=index.js.map
|
|
554
752
|
//# sourceMappingURL=index.js.map
|