@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 = 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;
@@ -497,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
497
499
 
498
500
  interface OutboxEntry {
499
501
  id: string;
500
- event: DomainEvent;
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: OutboxEntry['event']): TReturnType;
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<unknown>): Promise<void>;
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: DomainEvent<unknown>): OutboxEntry;
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, 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>> {
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, 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']);
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, 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, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, 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;
@@ -497,14 +499,14 @@ interface EventStore<TEvent, TAppendReturnType = Promise<void>, TLoadReturnType
497
499
 
498
500
  interface OutboxEntry {
499
501
  id: string;
500
- event: DomainEvent;
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: OutboxEntry['event']): TReturnType;
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<unknown>): Promise<void>;
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: DomainEvent<unknown>): OutboxEntry;
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, 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>> {
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, 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']);
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, 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, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, 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
@@ -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
- this.entries.push({
537
- id: (this.idCounter++).toString(),
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
- export { AndSpecification, CompositeSpecification, FieldEquals, FieldGreaterThan, GenericOutboxWorker, InMemoryOutbox, NotSpecification, Operation, OrSpecification, SimpleCommandBus, SimpleDatabase, SimpleEventBus, SimpleEventStore, SimpleQueryBus, SimpleRepository, createCommand, createDomainEvent, createExternalEvent, createIntegrationEvent, createOutboxEntry, createQuery, createQueryNode, createRejection, createStoredEvent, fail, getTimestamp, invariant, isCommand, isDomainEvent, isEqual, isEvent, isExternalEvent, isIntegrationEvent, isQuery, isRejection, makeStreamKey, mapDomainEventToIntegrationEvent, mapRejectionToIntegrationEvent, 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 };
613
751
  //# sourceMappingURL=index.js.map
614
752
  //# sourceMappingURL=index.js.map