@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 = unknown> {
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, TRejection extends Rejection = never> {
270
- decide(this: void, command: TCommand, currentState: TState): (TEvent | TRejection)[];
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(event: unknown): event is Rejection;
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: DomainEvent;
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: OutboxEntry['event']): TReturnType;
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<unknown>): Promise<void>;
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: DomainEvent<unknown>): OutboxEntry;
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, TRejection extends Rejection> implements Repository<TEvent, Promise<TState>, Promise<void>> {
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, TRejection>['evolve'], initialState: Decider<TState, TCommand, TEvent, TRejection>['initialState']);
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, type Module, 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, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, type Specification, type StoredEvent, type StreamKey, type UnixTimestampInSeconds, type WithIdentifier, createCommand, createDomainEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, parseAsError };
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 = unknown> {
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, TRejection extends Rejection = never> {
270
- decide(this: void, command: TCommand, currentState: TState): (TEvent | TRejection)[];
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(event: unknown): event is Rejection;
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: DomainEvent;
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: OutboxEntry['event']): TReturnType;
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<unknown>): Promise<void>;
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: DomainEvent<unknown>): OutboxEntry;
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, TRejection extends Rejection> implements Repository<TEvent, Promise<TState>, Promise<void>> {
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, TRejection>['evolve'], initialState: Decider<TState, TCommand, TEvent, TRejection>['initialState']);
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, type Module, 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, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, type Specification, type StoredEvent, type StreamKey, type UnixTimestampInSeconds, type WithIdentifier, createCommand, createDomainEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, parseAsError };
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()) => Math.floor(date.getTime() / 1e3);
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(event) {
229
- return isEvent(event) && "commandId" in event && "reasonCode" in event && event.kind === "rejection";
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 createIntegrationEvent(type, payload, metadata) {
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: "integration"
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
- this.entries.push({
477
- id: (this.idCounter++).toString(),
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
- export { AndSpecification, CompositeSpecification, FieldEquals, FieldGreaterThan, GenericOutboxWorker, InMemoryOutbox, NotSpecification, Operation, OrSpecification, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, createCommand, createDomainEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, parseAsError };
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