archstone 1.2.1 → 1.3.0-rc.2

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.
@@ -1,2 +1,2 @@
1
- import { Either, Optional, UniqueEntityId, ValueObject, WatchedList, left, right } from "../shared/chunk-t21213sm.js";
2
- export { right, left, WatchedList, ValueObject, UniqueEntityId, Optional, Either };
1
+ import { DomainEvent, DomainEvents, Either, EventHandler, Optional, UniqueEntityId, ValueObject, WatchedList, left, right } from "../shared/chunk-rgs8z093.js";
2
+ export { right, left, WatchedList, ValueObject, UniqueEntityId, Optional, EventHandler, Either, DomainEvents, DomainEvent };
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{d as a,e as b,f as c,g as d,h as e}from"../shared/chunk-r63mxet1.js";export{b as right,a as left,e as WatchedList,d as ValueObject,c as UniqueEntityId};
2
+ import{c as a,d as b,e as c,f as d,g as e,h as f}from"../shared/chunk-8hx7pxm3.js";export{b as right,a as left,f as WatchedList,e as ValueObject,d as UniqueEntityId,c as DomainEvents};
@@ -1,3 +1,2 @@
1
- import { Creatable, Deletable, Findable, Repository, Saveable, UseCase, UseCaseError } from "../../shared/chunk-txbqwwkc.js";
2
- import "../../shared/chunk-t21213sm.js";
1
+ import { Creatable, Deletable, Findable, Repository, Saveable, UseCase, UseCaseError } from "../../shared/chunk-rgs8z093.js";
3
2
  export { UseCaseError, UseCase, Saveable, Repository, Findable, Deletable, Creatable };
@@ -1,3 +1,2 @@
1
- import { AggregateRoot, DomainEvent, DomainEvents, Entity, EventHandler } from "../../shared/chunk-axgwjkjg.js";
2
- import "../../shared/chunk-t21213sm.js";
1
+ import { AggregateRoot, DomainEvent, DomainEvents, Entity, EventHandler } from "../../shared/chunk-rgs8z093.js";
3
2
  export { EventHandler, Entity, DomainEvents, DomainEvent, AggregateRoot };
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{a,b,c}from"../../shared/chunk-jw0ckhsy.js";import"../../shared/chunk-r63mxet1.js";export{b as Entity,a as DomainEvents,c as AggregateRoot};
2
+ import{a as b,b as c}from"../../shared/chunk-s2r7zghq.js";import{e as a}from"../../shared/chunk-8hx7pxm3.js";export{b as Entity,a as DomainEvents,c as AggregateRoot};
@@ -1,5 +1,2 @@
1
- import "../shared/chunk-em5cewpk.js";
2
- import { Creatable, Deletable, Findable, Repository, Saveable, UseCase, UseCaseError } from "../shared/chunk-txbqwwkc.js";
3
- import { AggregateRoot, DomainEvent, DomainEvents, Entity, EventHandler } from "../shared/chunk-axgwjkjg.js";
4
- import "../shared/chunk-t21213sm.js";
1
+ import { AggregateRoot, Creatable, Deletable, DomainEvent, DomainEvents, Entity, EventHandler, Findable, Repository, Saveable, UseCase, UseCaseError } from "../shared/chunk-rgs8z093.js";
5
2
  export { UseCaseError, UseCase, Saveable, Repository, Findable, EventHandler, Entity, DomainEvents, DomainEvent, Deletable, Creatable, AggregateRoot };
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import"../shared/chunk-x296z7h1.js";import"../shared/chunk-wsjyhnpq.js";import{a,b,c}from"../shared/chunk-jw0ckhsy.js";import"../shared/chunk-r63mxet1.js";export{b as Entity,a as DomainEvents,c as AggregateRoot};
2
+ import"../shared/chunk-x296z7h1.js";import"../shared/chunk-wsjyhnpq.js";import{a as b,b as c}from"../shared/chunk-s2r7zghq.js";import{e as a}from"../shared/chunk-8hx7pxm3.js";export{b as Entity,a as DomainEvents,c as AggregateRoot};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,2 @@
1
- import "./shared/chunk-em5cewpk.js";
2
- import { Creatable, Deletable, Findable, Repository, Saveable, UseCase, UseCaseError } from "./shared/chunk-txbqwwkc.js";
3
- import { AggregateRoot, DomainEvent, DomainEvents, Entity, EventHandler } from "./shared/chunk-axgwjkjg.js";
4
- import { Either, Optional, UniqueEntityId, ValueObject, WatchedList, left, right } from "./shared/chunk-t21213sm.js";
1
+ import { AggregateRoot, Creatable, Deletable, DomainEvent, DomainEvents, Either, Entity, EventHandler, Findable, Optional, Repository, Saveable, UniqueEntityId, UseCase, UseCaseError, ValueObject, WatchedList, left, right } from "./shared/chunk-rgs8z093.js";
5
2
  export { right, left, WatchedList, ValueObject, UseCaseError, UseCase, UniqueEntityId, Saveable, Repository, Optional, Findable, EventHandler, Entity, Either, DomainEvents, DomainEvent, Deletable, Creatable, AggregateRoot };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import"./shared/chunk-x296z7h1.js";import"./shared/chunk-wsjyhnpq.js";import{a,b,c}from"./shared/chunk-jw0ckhsy.js";import{d as f,e as m,f as p,g as t,h as x}from"./shared/chunk-r63mxet1.js";export{m as right,f as left,x as WatchedList,t as ValueObject,p as UniqueEntityId,b as Entity,a as DomainEvents,c as AggregateRoot};
2
+ import"./shared/chunk-x296z7h1.js";import"./shared/chunk-wsjyhnpq.js";import{a as b,b as c}from"./shared/chunk-s2r7zghq.js";import{c as f,d as m,e as p,f as t,g as x,h as a}from"./shared/chunk-8hx7pxm3.js";export{m as right,f as left,a as WatchedList,x as ValueObject,t as UniqueEntityId,b as Entity,p as DomainEvents,c as AggregateRoot};
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ class H{value;constructor(x){this.value=x}isRight(){return!1}isLeft(){return!0}}class V{value;constructor(x){this.value=x}isRight(){return!0}isLeft(){return!1}}var B=(x)=>new H(x),F=(x)=>new V(x);class W{handlersMap=new Map;markedAggregates=new Set;markAggregateForDispatch(x){if(!this.findMarkedAggregateByID(x.id))this.markedAggregates.add(x)}dispatchEventsForAggregate(x){let E=this.findMarkedAggregateByID(x);if(E)this.dispatchAggregateEvents(E),E.clearEvents(),this.removeAggregateFromMarkedDispatchList(E)}register(x,E){if(!this.handlersMap.has(E))this.handlersMap.set(E,[]);this.handlersMap.get(E)?.push(x)}clearHandlers(){this.handlersMap.clear()}clearMarkedAggregates(){this.markedAggregates.clear()}dispatchAggregateEvents(x){for(let E of x.domainEvents)this.dispatch(E)}removeAggregateFromMarkedDispatchList(x){this.markedAggregates.delete(x)}findMarkedAggregateByID(x){return[...this.markedAggregates].find((E)=>E.id.equals(x))}dispatch(x){let E=this.handlersMap.get(x.constructor.name)??[];for(let O of E)O(x)}}var G=new W;var{randomUUIDv7:J}=globalThis.Bun;class f{value;constructor(x){this.value=x??J()}toValue(){return this.value}toString(){return this.value}equals(x){return x.toValue()===this.value}}class z{props;constructor(x){this.props=x}equals(x){return JSON.stringify(this.props)===JSON.stringify(x.props)}}class A{currentItems;initial;new;removed;constructor(x){this.currentItems=x??[],this.initial=x??[],this.new=[],this.removed=[]}getItems(){return this.currentItems}getNewItems(){return this.new}getRemovedItems(){return this.removed}exists(x){return this.isCurrentItem(x)}add(x){if(this.isRemovedItem(x))this.removeFromRemoved(x);if(!(this.isNewItem(x)||this.wasAddedInitially(x)))this.new.push(x);if(!this.isCurrentItem(x))this.currentItems.push(x)}remove(x){if(this.removeFromCurrent(x),this.isNewItem(x)){this.removeFromNew(x);return}if(!this.isRemovedItem(x))this.removed.push(x)}update(x){this.new=x.filter((E)=>!this.getItems().some((O)=>this.compareItems(E,O))),this.removed=this.getItems().filter((E)=>!x.some((O)=>this.compareItems(E,O))),this.currentItems=x}isCurrentItem(x){return this.currentItems.some((E)=>this.compareItems(x,E))}isNewItem(x){return this.new.some((E)=>this.compareItems(x,E))}isRemovedItem(x){return this.removed.some((E)=>this.compareItems(x,E))}removeFromNew(x){this.new=this.new.filter((E)=>!this.compareItems(E,x))}removeFromCurrent(x){this.currentItems=this.currentItems.filter((E)=>!this.compareItems(x,E))}removeFromRemoved(x){this.removed=this.removed.filter((E)=>!this.compareItems(x,E))}wasAddedInitially(x){return this.initial.some((E)=>this.compareItems(x,E))}}export{B as c,F as d,G as e,f,z as g,A as h};
@@ -0,0 +1,637 @@
1
+ /**
2
+ * Contract for entities that can be created.
3
+ *
4
+ * @template T - The entity type
5
+ */
6
+ interface Creatable<T> {
7
+ /**
8
+ * Persists a new entity for the first time.
9
+ *
10
+ * @param entity - The entity to create
11
+ */
12
+ create(entity: T): Promise<void>;
13
+ }
14
+ /**
15
+ * Contract for entities that can be deleted.
16
+ *
17
+ * @template T - The entity type
18
+ */
19
+ interface Deletable<T> {
20
+ /**
21
+ * Deletes an entity from the repository.
22
+ *
23
+ * @param entity - The entity to delete
24
+ */
25
+ delete(entity: T): Promise<void>;
26
+ }
27
+ /**
28
+ * Contract for entities that can be retrieved by their unique identifier.
29
+ *
30
+ * @template T - The entity type
31
+ */
32
+ interface Findable<T> {
33
+ /**
34
+ * Finds an entity by its unique identifier.
35
+ *
36
+ * @param id - The unique identifier of the entity
37
+ * @returns The entity if found, `null` otherwise
38
+ */
39
+ findById(id: string): Promise<T | null>;
40
+ }
41
+ /**
42
+ * Contract for entities that can be persisted.
43
+ *
44
+ * @template T - The entity type
45
+ */
46
+ interface Saveable<T> {
47
+ /**
48
+ * Persists an existing entity.
49
+ *
50
+ * @param entity - The entity to save
51
+ */
52
+ save(entity: T): Promise<void>;
53
+ }
54
+ /**
55
+ * Full CRUD repository contract, composing all segregated interfaces.
56
+ *
57
+ * Use this when the repository needs to support all operations. For more
58
+ * constrained repositories, prefer composing only the interfaces you need.
59
+ *
60
+ * @template T - The entity type
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // full CRUD
65
+ * interface UserRepository extends Repository<User> {
66
+ * findByEmail(email: string): Promise<User | null>
67
+ * }
68
+ *
69
+ * // read-only
70
+ * interface ReportRepository extends Findable<Report> {
71
+ * findByPeriod(start: Date, end: Date): Promise<Report[]>
72
+ * }
73
+ *
74
+ * // append-only
75
+ * interface AuditRepository extends Creatable<AuditLog> {}
76
+ * ```
77
+ */
78
+ interface Repository<T> extends Findable<T>, Saveable<T>, Creatable<T>, Deletable<T> {}
79
+ /**
80
+ * Base contract for all use case errors.
81
+ *
82
+ * Implement this interface to define semantic, domain-aware errors
83
+ * that can be returned as the left side of an {@link Either}.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * class UserNotFoundError implements UseCaseError {
88
+ * message = "User not found."
89
+ * }
90
+ *
91
+ * type FindUserResult = Either<UserNotFoundError, User>
92
+ * ```
93
+ */
94
+ interface UseCaseError {
95
+ message: string;
96
+ }
97
+ /**
98
+ * Represents the left side of an {@link Either} — conventionally used for
99
+ * failure or error values.
100
+ *
101
+ * @template L - The type of the failure value
102
+ * @template R - The type of the success value
103
+ */
104
+ declare class Left<
105
+ L,
106
+ R
107
+ > {
108
+ readonly value: L;
109
+ constructor(value: L);
110
+ isRight(): this is Right<L, R>;
111
+ isLeft(): this is Left<L, R>;
112
+ }
113
+ /**
114
+ * Represents the right side of an {@link Either} — conventionally used for
115
+ * success values.
116
+ *
117
+ * @template L - The type of the failure value
118
+ * @template R - The type of the success value
119
+ */
120
+ declare class Right<
121
+ L,
122
+ R
123
+ > {
124
+ readonly value: R;
125
+ constructor(value: R);
126
+ isRight(): this is Right<L, R>;
127
+ isLeft(): this is Left<L, R>;
128
+ }
129
+ /**
130
+ * A discriminated union that represents a value of one of two possible types.
131
+ *
132
+ * Commonly used as a type-safe alternative to throwing exceptions — the left
133
+ * side carries an error, and the right side carries a success value.
134
+ *
135
+ * Use the {@link left} and {@link right} helper functions to construct values,
136
+ * and `isLeft()` / `isRight()` to narrow the type.
137
+ *
138
+ * @template L - The type of the failure value
139
+ * @template R - The type of the success value
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * type FindUserResult = Either<NotFoundError, User>
144
+ *
145
+ * function findUser(id: string): FindUserResult {
146
+ * const user = db.find(id)
147
+ * if (!user) return left(new NotFoundError("User", id))
148
+ * return right(user)
149
+ * }
150
+ *
151
+ * const result = findUser("123")
152
+ *
153
+ * if (result.isLeft()) {
154
+ * console.error(result.value) // NotFoundError
155
+ * } else {
156
+ * console.log(result.value) // User
157
+ * }
158
+ * ```
159
+ */
160
+ type Either<
161
+ L,
162
+ R
163
+ > = Left<L, R> | Right<L, R>;
164
+ /**
165
+ * Constructs a {@link Left} value, representing a failure.
166
+ *
167
+ * @param value - The error or failure value
168
+ */
169
+ declare const left: <
170
+ L,
171
+ R
172
+ >(value: L) => Either<L, R>;
173
+ /**
174
+ * Constructs a {@link Right} value, representing a success.
175
+ *
176
+ * @param value - The success value
177
+ */
178
+ declare const right: <
179
+ L,
180
+ R
181
+ >(value: R) => Either<L, R>;
182
+ /**
183
+ * Base contract for all domain events.
184
+ *
185
+ * A domain event represents something meaningful that happened in the domain.
186
+ * Events are raised by aggregate roots and dispatched after successful persistence,
187
+ * enabling decoupled side effects such as notifications, projections, and integrations.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * class UserCreatedEvent implements DomainEvent {
192
+ * occurredAt = new Date()
193
+ *
194
+ * constructor(private readonly user: User) {}
195
+ *
196
+ * getAggregateId(): UniqueEntityId {
197
+ * return this.user.id
198
+ * }
199
+ * }
200
+ * ```
201
+ */
202
+ interface DomainEvent {
203
+ /**
204
+ * Returns the identifier of the aggregate root that raised this event.
205
+ */
206
+ getAggregateId(): UniqueEntityId;
207
+ /**
208
+ * The date and time when the event occurred.
209
+ */
210
+ occurredAt: Date;
211
+ }
212
+ /**
213
+ * Base class for all domain entities.
214
+ *
215
+ * An entity is an object defined by its identity rather than its attributes.
216
+ * Two entities are considered equal if they share the same `id`, regardless
217
+ * of their other properties.
218
+ *
219
+ * All entities must implement a static `create` factory method to encapsulate
220
+ * construction logic and keep the constructor protected.
221
+ *
222
+ * @template Props - The shape of the entity's properties
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * interface UserProps {
227
+ * id: UniqueEntityId
228
+ * name: string
229
+ * email: string
230
+ * createdAt: Date
231
+ * }
232
+ *
233
+ * class User extends Entity<UserProps> {
234
+ * get name() { return this.props.name }
235
+ * get email() { return this.props.email }
236
+ *
237
+ * static create(props: Optional<UserProps, "id" | "createdAt">): User {
238
+ * return new User(
239
+ * { ...props, createdAt: props.createdAt ?? new Date() },
240
+ * props.id ?? new UniqueEntityId(),
241
+ * )
242
+ * }
243
+ * }
244
+ * ```
245
+ */
246
+ declare abstract class Entity<Props> {
247
+ private readonly _id;
248
+ protected props: Props;
249
+ get id(): UniqueEntityId;
250
+ protected constructor(props: Props, id?: UniqueEntityId);
251
+ /**
252
+ * Compares this entity with another by identity.
253
+ *
254
+ * @param entity - The entity to compare against
255
+ * @returns `true` if both entities share the same `id`
256
+ */
257
+ equals(entity: Entity<unknown>): boolean;
258
+ }
259
+ /**
260
+ * Base class for all aggregate roots in the domain.
261
+ *
262
+ * An aggregate root is the entry point to an aggregate — a cluster of domain
263
+ * objects that are treated as a single unit. It is responsible for enforcing
264
+ * invariants and coordinating domain events within its boundary.
265
+ *
266
+ * Domain events are collected internally and dispatched after the aggregate
267
+ * is persisted, ensuring side effects only occur after a successful transaction.
268
+ *
269
+ * @template Props - The shape of the aggregate's properties
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * class User extends AggregateRoot<UserProps> {
274
+ * static create(props: Optional<UserProps, "id" | "createdAt">): User {
275
+ * const user = new User(
276
+ * { ...props, createdAt: props.createdAt ?? new Date() },
277
+ * props.id ?? new UniqueEntityId(),
278
+ * )
279
+ *
280
+ * user.addDomainEvent(new UserCreatedEvent(user))
281
+ *
282
+ * return user
283
+ * }
284
+ * }
285
+ * ```
286
+ */
287
+ declare abstract class AggregateRoot<Props> extends Entity<Props> {
288
+ private readonly _domainEvents;
289
+ /**
290
+ * Returns all domain events that have been raised by this aggregate
291
+ * and are pending dispatch.
292
+ */
293
+ get domainEvents(): DomainEvent[];
294
+ /**
295
+ * Registers a domain event to be dispatched after the aggregate is persisted.
296
+ * Automatically marks this aggregate for dispatch in the {@link DomainEvents} registry.
297
+ *
298
+ * @param domainEvent - The domain event to register
299
+ */
300
+ protected addDomainEvent(domainEvent: DomainEvent): void;
301
+ /**
302
+ * Clears all pending domain events from this aggregate.
303
+ * Should be called by the infrastructure layer after events are dispatched.
304
+ */
305
+ clearEvents(): void;
306
+ }
307
+ /**
308
+ * Callback function invoked when a domain event is dispatched.
309
+ */
310
+ type DomainEventCallback = (event: DomainEvent) => void;
311
+ /**
312
+ * Central registry and dispatcher for domain events.
313
+ *
314
+ * Aggregates register themselves for dispatch after raising events.
315
+ * The infrastructure layer is responsible for calling {@link dispatchEventsForAggregate}
316
+ * after successfully persisting an aggregate, ensuring events are only
317
+ * dispatched after a successful transaction.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * // register a handler
322
+ * DomainEvents.register(
323
+ * (event) => sendWelcomeEmail(event as UserCreatedEvent),
324
+ * UserCreatedEvent.name,
325
+ * )
326
+ *
327
+ * // dispatch after persistence
328
+ * await userRepository.create(user)
329
+ * DomainEvents.dispatchEventsForAggregate(user.id)
330
+ * ```
331
+ */
332
+ declare class DomainEventsImplementation {
333
+ private readonly handlersMap;
334
+ private readonly markedAggregates;
335
+ /**
336
+ * Marks an aggregate root to have its events dispatched.
337
+ * Called automatically by {@link AggregateRoot.addDomainEvent}.
338
+ *
339
+ * @param aggregate - The aggregate to mark for dispatch
340
+ */
341
+ markAggregateForDispatch(aggregate: AggregateRoot<unknown>): void;
342
+ /**
343
+ * Dispatches all pending events for the aggregate with the given id,
344
+ * clears its events, and removes it from the dispatch list.
345
+ *
346
+ * Should be called by the infrastructure layer after persisting the aggregate.
347
+ *
348
+ * @param id - The identifier of the aggregate to dispatch events for
349
+ */
350
+ dispatchEventsForAggregate(id: UniqueEntityId): void;
351
+ /**
352
+ * Registers an event handler for a given event class name.
353
+ *
354
+ * @param callback - The function to invoke when the event is dispatched
355
+ * @param eventClassName - The name of the event class to listen for
356
+ */
357
+ register(callback: DomainEventCallback, eventClassName: string): void;
358
+ /**
359
+ * Removes all registered event handlers.
360
+ * Useful for test isolation.
361
+ */
362
+ clearHandlers(): void;
363
+ /**
364
+ * Removes all aggregates from the dispatch list.
365
+ * Useful for test isolation.
366
+ */
367
+ clearMarkedAggregates(): void;
368
+ private dispatchAggregateEvents;
369
+ private removeAggregateFromMarkedDispatchList;
370
+ private findMarkedAggregateByID;
371
+ private dispatch;
372
+ }
373
+ /**
374
+ * Singleton instance of the domain events registry.
375
+ * Use this to register handlers and dispatch events across the application.
376
+ */
377
+ declare const DomainEvents: DomainEventsImplementation;
378
+ /**
379
+ * Base contract for all event handlers.
380
+ *
381
+ * An event handler is responsible for subscribing to domain events
382
+ * and executing side effects in response — such as sending emails,
383
+ * updating read models, or triggering external integrations.
384
+ *
385
+ * All handlers must implement {@link setupSubscriptions} to register
386
+ * their callbacks in the {@link DomainEvents} registry.
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * class OnUserCreated implements EventHandler {
391
+ * constructor(private readonly mailer: Mailer) {}
392
+ *
393
+ * setupSubscriptions(): void {
394
+ * DomainEvents.register(
395
+ * (event) => this.sendWelcomeEmail(event as UserCreatedEvent),
396
+ * UserCreatedEvent.name,
397
+ * )
398
+ * }
399
+ *
400
+ * private async sendWelcomeEmail(event: UserCreatedEvent): Promise<void> {
401
+ * await this.mailer.send(event.user.email)
402
+ * }
403
+ * }
404
+ * ```
405
+ */
406
+ interface EventHandler {
407
+ /**
408
+ * Registers all event subscriptions for this handler.
409
+ * Should be called once during application bootstrap.
410
+ */
411
+ setupSubscriptions(): void;
412
+ }
413
+ /**
414
+ * Make some property optional on type
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * type Post {
419
+ * id: string;
420
+ * name: string;
421
+ * email: string;
422
+ * }
423
+ *
424
+ * Optional<Post, 'id' | 'email'>
425
+ * ```
426
+ */
427
+ type Optional<
428
+ T,
429
+ K extends keyof T
430
+ > = Pick<Partial<T>, K> & Omit<T, K>;
431
+ /**
432
+ * Represents a unique identifier for a domain entity.
433
+ *
434
+ * Wraps a UUID v7 string, which is time-sortable and ideal for database
435
+ * indexing. A new identifier is generated automatically if no value is provided.
436
+ *
437
+ * @example
438
+ * ```ts
439
+ * // auto-generated
440
+ * const id = new UniqueEntityId()
441
+ *
442
+ * // from existing value (e.g. reconstructing from database)
443
+ * const id = new UniqueEntityId("0195d810-5b3e-7000-8e3e-1a2b3c4d5e6f")
444
+ * ```
445
+ */
446
+ declare class UniqueEntityId {
447
+ private readonly value;
448
+ constructor(value?: string);
449
+ /**
450
+ * Returns the identifier as a primitive string.
451
+ * Useful for serialization and database persistence.
452
+ */
453
+ toValue(): string;
454
+ /**
455
+ * Returns the string representation of the identifier.
456
+ */
457
+ toString(): string;
458
+ /**
459
+ * Compares this identifier with another by value equality.
460
+ *
461
+ * @param id - The identifier to compare against
462
+ * @returns `true` if both identifiers have the same value
463
+ */
464
+ equals(id: UniqueEntityId): boolean;
465
+ }
466
+ /**
467
+ * Base class for all value objects in the domain.
468
+ *
469
+ * A value object is an object defined entirely by its attributes — it has no
470
+ * identity. Two value objects are considered equal if all their properties
471
+ * are deeply equal, regardless of reference.
472
+ *
473
+ * Value objects should be immutable. Avoid mutating `props` directly;
474
+ * instead, create a new instance with the updated values.
475
+ *
476
+ * All value objects must implement a static `create` factory method to
477
+ * encapsulate construction and validation logic, keeping the constructor
478
+ * protected from external instantiation.
479
+ *
480
+ * @template Props - The shape of the value object's properties
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * interface EmailProps {
485
+ * value: string
486
+ * }
487
+ *
488
+ * class Email extends ValueObject<EmailProps> {
489
+ * get value() { return this.props.value }
490
+ *
491
+ * static create(email: string): Email {
492
+ * if (!email.includes("@")) {
493
+ * throw new ValidationError("Invalid email address.")
494
+ * }
495
+ *
496
+ * return new Email({ value: email })
497
+ * }
498
+ * }
499
+ * ```
500
+ */
501
+ declare abstract class ValueObject<Props> {
502
+ protected props: Props;
503
+ protected constructor(props: Props);
504
+ /**
505
+ * Compares this value object with another by deep equality of their properties.
506
+ *
507
+ * @param other - The value object to compare against
508
+ * @returns `true` if both value objects have deeply equal properties
509
+ */
510
+ equals(other: ValueObject<Props>): boolean;
511
+ }
512
+ /**
513
+ * Tracks additions and removals of items in a collection, enabling
514
+ * efficient persistence of only what changed.
515
+ *
516
+ * Commonly used inside aggregate roots to manage one-to-many relationships
517
+ * without rewriting the entire collection on every save — only new and
518
+ * removed items are persisted.
519
+ *
520
+ * Subclasses must implement {@link compareItems} to define equality between items.
521
+ *
522
+ * @template T - The type of items in the list
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * class TagList extends WatchedList<Tag> {
527
+ * compareItems(a: Tag, b: Tag): boolean {
528
+ * return a.id.equals(b.id)
529
+ * }
530
+ *
531
+ * static create(tags: Tag[]): TagList {
532
+ * return new TagList(tags)
533
+ * }
534
+ * }
535
+ *
536
+ * const tags = TagList.create([existingTag])
537
+ *
538
+ * tags.add(newTag) // tracked as new
539
+ * tags.remove(oldTag) // tracked as removed
540
+ *
541
+ * tags.getNewItems() // [newTag]
542
+ * tags.getRemovedItems() // [oldTag]
543
+ * ```
544
+ */
545
+ declare abstract class WatchedList<T> {
546
+ protected currentItems: T[];
547
+ protected initial: T[];
548
+ protected new: T[];
549
+ protected removed: T[];
550
+ constructor(initialItems?: T[]);
551
+ /**
552
+ * Returns all current items in the list.
553
+ */
554
+ getItems(): T[];
555
+ /**
556
+ * Returns items that were added since the list was created.
557
+ */
558
+ getNewItems(): T[];
559
+ /**
560
+ * Returns items that were removed since the list was created.
561
+ */
562
+ getRemovedItems(): T[];
563
+ /**
564
+ * Returns whether the given item exists in the current list.
565
+ *
566
+ * @param item - The item to check
567
+ */
568
+ exists(item: T): boolean;
569
+ /**
570
+ * Adds an item to the list, tracking it as new if it wasn't in the initial set.
571
+ * If the item was previously removed, it is restored.
572
+ *
573
+ * @param item - The item to add
574
+ */
575
+ add(item: T): void;
576
+ /**
577
+ * Removes an item from the list, tracking it as removed if it was in the initial set.
578
+ *
579
+ * @param item - The item to remove
580
+ */
581
+ remove(item: T): void;
582
+ /**
583
+ * Replaces the entire list with a new set of items, automatically
584
+ * computing what was added and what was removed.
585
+ *
586
+ * @param items - The new set of items
587
+ */
588
+ update(items: T[]): void;
589
+ private isCurrentItem;
590
+ private isNewItem;
591
+ private isRemovedItem;
592
+ private removeFromNew;
593
+ private removeFromCurrent;
594
+ private removeFromRemoved;
595
+ private wasAddedInitially;
596
+ }
597
+ /**
598
+ * Represents the expected output shape of any use case.
599
+ * Always an {@link Either} — left for errors, right for success.
600
+ */
601
+ type UseCaseOutput = Either<UseCaseError | never, unknown | null>;
602
+ /**
603
+ * Base contract for all application use cases.
604
+ *
605
+ * A use case orchestrates domain logic for a single application operation.
606
+ * It receives a typed input, interacts with the domain, and returns an
607
+ * {@link Either} — never throwing exceptions directly.
608
+ *
609
+ * The left side carries a {@link UseCaseError} on failure.
610
+ * The right side carries the success value.
611
+ *
612
+ * @template Input - The shape of the data required to execute the use case
613
+ * @template Output - The expected {@link Either} output, constrained to {@link UseCaseOutput}
614
+ *
615
+ * @example
616
+ * ```ts
617
+ * type Input = { userId: string }
618
+ * type Output = Either<UserNotFoundError, User>
619
+ *
620
+ * class GetUserUseCase implements UseCase<Input, Output> {
621
+ * constructor(private userRepository: UserRepository) {}
622
+ *
623
+ * async execute({ userId }: Input): Promise<Output> {
624
+ * const user = await this.userRepository.findById(userId)
625
+ * if (!user) return left(new UserNotFoundError(userId))
626
+ * return right(user)
627
+ * }
628
+ * }
629
+ * ```
630
+ */
631
+ interface UseCase<
632
+ Input,
633
+ Output extends UseCaseOutput
634
+ > {
635
+ execute(input: Input): Promise<Output>;
636
+ }
637
+ export { Either, left, right, DomainEvent, Creatable, Deletable, Findable, Saveable, Repository, UseCaseError, UseCase, Entity, AggregateRoot, DomainEvents, EventHandler, Optional, UniqueEntityId, ValueObject, WatchedList };
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ import{e as D,f as H}from"./chunk-8hx7pxm3.js";class A{_id;props;get id(){return this._id}constructor(x,f){this._id=f??new H,this.props=x}equals(x){if(x===this)return!0;return x.id.equals(this._id)}}class R extends A{_domainEvents=new Set;get domainEvents(){return Array.from(this._domainEvents)}addDomainEvent(x){this._domainEvents.add(x),D.markAggregateForDispatch(this)}clearEvents(){this._domainEvents.clear()}}export{A as a,R as b};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "archstone",
3
3
  "description": "TypeScript architecture foundation for backend services based on Domain-Driven Design and Clean Architecture",
4
- "version": "1.2.1",
4
+ "version": "1.3.0-rc.2",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "files": [
@@ -76,6 +76,7 @@
76
76
  "test": "bun test",
77
77
  "check": "ultracite check",
78
78
  "fix": "ultracite fix",
79
+ "release": "bun publish --access public",
79
80
  "prepublishOnly": "bun run build",
80
81
  "prepare": "bunx husky"
81
82
  },
@@ -1,233 +0,0 @@
1
- import { UniqueEntityId } from "./chunk-t21213sm.js";
2
- /**
3
- * Base contract for all domain events.
4
- *
5
- * A domain event represents something meaningful that happened in the domain.
6
- * Events are raised by aggregate roots and dispatched after successful persistence,
7
- * enabling decoupled side effects such as notifications, projections, and integrations.
8
- *
9
- * @example
10
- * ```ts
11
- * class UserCreatedEvent implements DomainEvent {
12
- * occurredAt = new Date()
13
- *
14
- * constructor(private readonly user: User) {}
15
- *
16
- * getAggregateId(): UniqueEntityId {
17
- * return this.user.id
18
- * }
19
- * }
20
- * ```
21
- */
22
- interface DomainEvent {
23
- /**
24
- * Returns the identifier of the aggregate root that raised this event.
25
- */
26
- getAggregateId(): UniqueEntityId;
27
- /**
28
- * The date and time when the event occurred.
29
- */
30
- occurredAt: Date;
31
- }
32
- /**
33
- * Callback function invoked when a domain event is dispatched.
34
- */
35
- type DomainEventCallback = (event: DomainEvent) => void;
36
- /**
37
- * Central registry and dispatcher for domain events.
38
- *
39
- * Aggregates register themselves for dispatch after raising events.
40
- * The infrastructure layer is responsible for calling {@link dispatchEventsForAggregate}
41
- * after successfully persisting an aggregate, ensuring events are only
42
- * dispatched after a successful transaction.
43
- *
44
- * @example
45
- * ```ts
46
- * // register a handler
47
- * DomainEvents.register(
48
- * (event) => sendWelcomeEmail(event as UserCreatedEvent),
49
- * UserCreatedEvent.name,
50
- * )
51
- *
52
- * // dispatch after persistence
53
- * await userRepository.create(user)
54
- * DomainEvents.dispatchEventsForAggregate(user.id)
55
- * ```
56
- */
57
- declare class DomainEventsImplementation {
58
- private readonly handlersMap;
59
- private readonly markedAggregates;
60
- /**
61
- * Marks an aggregate root to have its events dispatched.
62
- * Called automatically by {@link AggregateRoot.addDomainEvent}.
63
- *
64
- * @param aggregate - The aggregate to mark for dispatch
65
- */
66
- markAggregateForDispatch(aggregate: AggregateRoot<unknown>): void;
67
- /**
68
- * Dispatches all pending events for the aggregate with the given id,
69
- * clears its events, and removes it from the dispatch list.
70
- *
71
- * Should be called by the infrastructure layer after persisting the aggregate.
72
- *
73
- * @param id - The identifier of the aggregate to dispatch events for
74
- */
75
- dispatchEventsForAggregate(id: UniqueEntityId): void;
76
- /**
77
- * Registers an event handler for a given event class name.
78
- *
79
- * @param callback - The function to invoke when the event is dispatched
80
- * @param eventClassName - The name of the event class to listen for
81
- */
82
- register(callback: DomainEventCallback, eventClassName: string): void;
83
- /**
84
- * Removes all registered event handlers.
85
- * Useful for test isolation.
86
- */
87
- clearHandlers(): void;
88
- /**
89
- * Removes all aggregates from the dispatch list.
90
- * Useful for test isolation.
91
- */
92
- clearMarkedAggregates(): void;
93
- private dispatchAggregateEvents;
94
- private removeAggregateFromMarkedDispatchList;
95
- private findMarkedAggregateByID;
96
- private dispatch;
97
- }
98
- /**
99
- * Singleton instance of the domain events registry.
100
- * Use this to register handlers and dispatch events across the application.
101
- */
102
- declare const DomainEvents: DomainEventsImplementation;
103
- /**
104
- * Base contract for all event handlers.
105
- *
106
- * An event handler is responsible for subscribing to domain events
107
- * and executing side effects in response — such as sending emails,
108
- * updating read models, or triggering external integrations.
109
- *
110
- * All handlers must implement {@link setupSubscriptions} to register
111
- * their callbacks in the {@link DomainEvents} registry.
112
- *
113
- * @example
114
- * ```ts
115
- * class OnUserCreated implements EventHandler {
116
- * constructor(private readonly mailer: Mailer) {}
117
- *
118
- * setupSubscriptions(): void {
119
- * DomainEvents.register(
120
- * (event) => this.sendWelcomeEmail(event as UserCreatedEvent),
121
- * UserCreatedEvent.name,
122
- * )
123
- * }
124
- *
125
- * private async sendWelcomeEmail(event: UserCreatedEvent): Promise<void> {
126
- * await this.mailer.send(event.user.email)
127
- * }
128
- * }
129
- * ```
130
- */
131
- interface EventHandler {
132
- /**
133
- * Registers all event subscriptions for this handler.
134
- * Should be called once during application bootstrap.
135
- */
136
- setupSubscriptions(): void;
137
- }
138
- /**
139
- * Base class for all domain entities.
140
- *
141
- * An entity is an object defined by its identity rather than its attributes.
142
- * Two entities are considered equal if they share the same `id`, regardless
143
- * of their other properties.
144
- *
145
- * All entities must implement a static `create` factory method to encapsulate
146
- * construction logic and keep the constructor protected.
147
- *
148
- * @template Props - The shape of the entity's properties
149
- *
150
- * @example
151
- * ```ts
152
- * interface UserProps {
153
- * id: UniqueEntityId
154
- * name: string
155
- * email: string
156
- * createdAt: Date
157
- * }
158
- *
159
- * class User extends Entity<UserProps> {
160
- * get name() { return this.props.name }
161
- * get email() { return this.props.email }
162
- *
163
- * static create(props: Optional<UserProps, "id" | "createdAt">): User {
164
- * return new User(
165
- * { ...props, createdAt: props.createdAt ?? new Date() },
166
- * props.id ?? new UniqueEntityId(),
167
- * )
168
- * }
169
- * }
170
- * ```
171
- */
172
- declare abstract class Entity<Props> {
173
- private readonly _id;
174
- protected props: Props;
175
- get id(): UniqueEntityId;
176
- protected constructor(props: Props, id?: UniqueEntityId);
177
- /**
178
- * Compares this entity with another by identity.
179
- *
180
- * @param entity - The entity to compare against
181
- * @returns `true` if both entities share the same `id`
182
- */
183
- equals(entity: Entity<unknown>): boolean;
184
- }
185
- /**
186
- * Base class for all aggregate roots in the domain.
187
- *
188
- * An aggregate root is the entry point to an aggregate — a cluster of domain
189
- * objects that are treated as a single unit. It is responsible for enforcing
190
- * invariants and coordinating domain events within its boundary.
191
- *
192
- * Domain events are collected internally and dispatched after the aggregate
193
- * is persisted, ensuring side effects only occur after a successful transaction.
194
- *
195
- * @template Props - The shape of the aggregate's properties
196
- *
197
- * @example
198
- * ```ts
199
- * class User extends AggregateRoot<UserProps> {
200
- * static create(props: Optional<UserProps, "id" | "createdAt">): User {
201
- * const user = new User(
202
- * { ...props, createdAt: props.createdAt ?? new Date() },
203
- * props.id ?? new UniqueEntityId(),
204
- * )
205
- *
206
- * user.addDomainEvent(new UserCreatedEvent(user))
207
- *
208
- * return user
209
- * }
210
- * }
211
- * ```
212
- */
213
- declare abstract class AggregateRoot<Props> extends Entity<Props> {
214
- private readonly _domainEvents;
215
- /**
216
- * Returns all domain events that have been raised by this aggregate
217
- * and are pending dispatch.
218
- */
219
- get domainEvents(): DomainEvent[];
220
- /**
221
- * Registers a domain event to be dispatched after the aggregate is persisted.
222
- * Automatically marks this aggregate for dispatch in the {@link DomainEvents} registry.
223
- *
224
- * @param domainEvent - The domain event to register
225
- */
226
- protected addDomainEvent(domainEvent: DomainEvent): void;
227
- /**
228
- * Clears all pending domain events from this aggregate.
229
- * Should be called by the infrastructure layer after events are dispatched.
230
- */
231
- clearEvents(): void;
232
- }
233
- export { DomainEvent, DomainEvents, EventHandler, Entity, AggregateRoot };
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{f as j}from"./chunk-r63mxet1.js";class R{handlersMap=new Map;markedAggregates=new Set;markAggregateForDispatch(x){if(!this.findMarkedAggregateByID(x.id))this.markedAggregates.add(x)}dispatchEventsForAggregate(x){let f=this.findMarkedAggregateByID(x);if(f)this.dispatchAggregateEvents(f),f.clearEvents(),this.removeAggregateFromMarkedDispatchList(f)}register(x,f){if(!this.handlersMap.has(f))this.handlersMap.set(f,[]);this.handlersMap.get(f)?.push(x)}clearHandlers(){this.handlersMap.clear()}clearMarkedAggregates(){this.markedAggregates.clear()}dispatchAggregateEvents(x){for(let f of x.domainEvents)this.dispatch(f)}removeAggregateFromMarkedDispatchList(x){this.markedAggregates.delete(x)}findMarkedAggregateByID(x){return[...this.markedAggregates].find((f)=>f.id.equals(x))}dispatch(x){let f=this.handlersMap.get(x.constructor.name)??[];for(let B of f)B(x)}}var H=new R;class A{_id;props;get id(){return this._id}constructor(x,f){this._id=f??new j,this.props=x}equals(x){if(x===this)return!0;return x.id.equals(this._id)}}class z extends A{_domainEvents=new Set;get domainEvents(){return Array.from(this._domainEvents)}addDomainEvent(x){this._domainEvents.add(x),H.markAggregateForDispatch(this)}clearEvents(){this._domainEvents.clear()}}export{H as a,A as b,z as c};
@@ -1,2 +0,0 @@
1
- // @bun
2
- class V{value;constructor(x){this.value=x}isRight(){return!1}isLeft(){return!0}}class W{value;constructor(x){this.value=x}isRight(){return!0}isLeft(){return!1}}var F=(x)=>new V(x),G=(x)=>new W(x);var{randomUUIDv7:H}=globalThis.Bun;class z{value;constructor(x){this.value=x??H()}toValue(){return this.value}toString(){return this.value}equals(x){return x.toValue()===this.value}}class A{props;constructor(x){this.props=x}equals(x){return JSON.stringify(this.props)===JSON.stringify(x.props)}}class B{currentItems;initial;new;removed;constructor(x){this.currentItems=x??[],this.initial=x??[],this.new=[],this.removed=[]}getItems(){return this.currentItems}getNewItems(){return this.new}getRemovedItems(){return this.removed}exists(x){return this.isCurrentItem(x)}add(x){if(this.isRemovedItem(x))this.removeFromRemoved(x);if(!(this.isNewItem(x)||this.wasAddedInitially(x)))this.new.push(x);if(!this.isCurrentItem(x))this.currentItems.push(x)}remove(x){if(this.removeFromCurrent(x),this.isNewItem(x)){this.removeFromNew(x);return}if(!this.isRemovedItem(x))this.removed.push(x)}update(x){this.new=x.filter((E)=>!this.getItems().some((O)=>this.compareItems(E,O))),this.removed=this.getItems().filter((E)=>!x.some((O)=>this.compareItems(E,O))),this.currentItems=x}isCurrentItem(x){return this.currentItems.some((E)=>this.compareItems(x,E))}isNewItem(x){return this.new.some((E)=>this.compareItems(x,E))}isRemovedItem(x){return this.removed.some((E)=>this.compareItems(x,E))}removeFromNew(x){this.new=this.new.filter((E)=>!this.compareItems(E,x))}removeFromCurrent(x){this.currentItems=this.currentItems.filter((E)=>!this.compareItems(x,E))}removeFromRemoved(x){this.removed=this.removed.filter((E)=>!this.compareItems(x,E))}wasAddedInitially(x){return this.initial.some((E)=>this.compareItems(x,E))}}export{F as d,G as e,z as f,A as g,B as h};
@@ -1,270 +0,0 @@
1
- /**
2
- * Represents the left side of an {@link Either} — conventionally used for
3
- * failure or error values.
4
- *
5
- * @template L - The type of the failure value
6
- * @template R - The type of the success value
7
- */
8
- declare class Left<
9
- L,
10
- R
11
- > {
12
- readonly value: L;
13
- constructor(value: L);
14
- isRight(): this is Right<L, R>;
15
- isLeft(): this is Left<L, R>;
16
- }
17
- /**
18
- * Represents the right side of an {@link Either} — conventionally used for
19
- * success values.
20
- *
21
- * @template L - The type of the failure value
22
- * @template R - The type of the success value
23
- */
24
- declare class Right<
25
- L,
26
- R
27
- > {
28
- readonly value: R;
29
- constructor(value: R);
30
- isRight(): this is Right<L, R>;
31
- isLeft(): this is Left<L, R>;
32
- }
33
- /**
34
- * A discriminated union that represents a value of one of two possible types.
35
- *
36
- * Commonly used as a type-safe alternative to throwing exceptions — the left
37
- * side carries an error, and the right side carries a success value.
38
- *
39
- * Use the {@link left} and {@link right} helper functions to construct values,
40
- * and `isLeft()` / `isRight()` to narrow the type.
41
- *
42
- * @template L - The type of the failure value
43
- * @template R - The type of the success value
44
- *
45
- * @example
46
- * ```ts
47
- * type FindUserResult = Either<NotFoundError, User>
48
- *
49
- * function findUser(id: string): FindUserResult {
50
- * const user = db.find(id)
51
- * if (!user) return left(new NotFoundError("User", id))
52
- * return right(user)
53
- * }
54
- *
55
- * const result = findUser("123")
56
- *
57
- * if (result.isLeft()) {
58
- * console.error(result.value) // NotFoundError
59
- * } else {
60
- * console.log(result.value) // User
61
- * }
62
- * ```
63
- */
64
- type Either<
65
- L,
66
- R
67
- > = Left<L, R> | Right<L, R>;
68
- /**
69
- * Constructs a {@link Left} value, representing a failure.
70
- *
71
- * @param value - The error or failure value
72
- */
73
- declare const left: <
74
- L,
75
- R
76
- >(value: L) => Either<L, R>;
77
- /**
78
- * Constructs a {@link Right} value, representing a success.
79
- *
80
- * @param value - The success value
81
- */
82
- declare const right: <
83
- L,
84
- R
85
- >(value: R) => Either<L, R>;
86
- /**
87
- * Make some property optional on type
88
- *
89
- * @example
90
- * ```typescript
91
- * type Post {
92
- * id: string;
93
- * name: string;
94
- * email: string;
95
- * }
96
- *
97
- * Optional<Post, 'id' | 'email'>
98
- * ```
99
- */
100
- type Optional<
101
- T,
102
- K extends keyof T
103
- > = Pick<Partial<T>, K> & Omit<T, K>;
104
- /**
105
- * Represents a unique identifier for a domain entity.
106
- *
107
- * Wraps a UUID v7 string, which is time-sortable and ideal for database
108
- * indexing. A new identifier is generated automatically if no value is provided.
109
- *
110
- * @example
111
- * ```ts
112
- * // auto-generated
113
- * const id = new UniqueEntityId()
114
- *
115
- * // from existing value (e.g. reconstructing from database)
116
- * const id = new UniqueEntityId("0195d810-5b3e-7000-8e3e-1a2b3c4d5e6f")
117
- * ```
118
- */
119
- declare class UniqueEntityId {
120
- private readonly value;
121
- constructor(value?: string);
122
- /**
123
- * Returns the identifier as a primitive string.
124
- * Useful for serialization and database persistence.
125
- */
126
- toValue(): string;
127
- /**
128
- * Returns the string representation of the identifier.
129
- */
130
- toString(): string;
131
- /**
132
- * Compares this identifier with another by value equality.
133
- *
134
- * @param id - The identifier to compare against
135
- * @returns `true` if both identifiers have the same value
136
- */
137
- equals(id: UniqueEntityId): boolean;
138
- }
139
- /**
140
- * Base class for all value objects in the domain.
141
- *
142
- * A value object is an object defined entirely by its attributes — it has no
143
- * identity. Two value objects are considered equal if all their properties
144
- * are deeply equal, regardless of reference.
145
- *
146
- * Value objects should be immutable. Avoid mutating `props` directly;
147
- * instead, create a new instance with the updated values.
148
- *
149
- * All value objects must implement a static `create` factory method to
150
- * encapsulate construction and validation logic, keeping the constructor
151
- * protected from external instantiation.
152
- *
153
- * @template Props - The shape of the value object's properties
154
- *
155
- * @example
156
- * ```ts
157
- * interface EmailProps {
158
- * value: string
159
- * }
160
- *
161
- * class Email extends ValueObject<EmailProps> {
162
- * get value() { return this.props.value }
163
- *
164
- * static create(email: string): Email {
165
- * if (!email.includes("@")) {
166
- * throw new ValidationError("Invalid email address.")
167
- * }
168
- *
169
- * return new Email({ value: email })
170
- * }
171
- * }
172
- * ```
173
- */
174
- declare abstract class ValueObject<Props> {
175
- protected props: Props;
176
- protected constructor(props: Props);
177
- /**
178
- * Compares this value object with another by deep equality of their properties.
179
- *
180
- * @param other - The value object to compare against
181
- * @returns `true` if both value objects have deeply equal properties
182
- */
183
- equals(other: ValueObject<Props>): boolean;
184
- }
185
- /**
186
- * Tracks additions and removals of items in a collection, enabling
187
- * efficient persistence of only what changed.
188
- *
189
- * Commonly used inside aggregate roots to manage one-to-many relationships
190
- * without rewriting the entire collection on every save — only new and
191
- * removed items are persisted.
192
- *
193
- * Subclasses must implement {@link compareItems} to define equality between items.
194
- *
195
- * @template T - The type of items in the list
196
- *
197
- * @example
198
- * ```ts
199
- * class TagList extends WatchedList<Tag> {
200
- * compareItems(a: Tag, b: Tag): boolean {
201
- * return a.id.equals(b.id)
202
- * }
203
- *
204
- * static create(tags: Tag[]): TagList {
205
- * return new TagList(tags)
206
- * }
207
- * }
208
- *
209
- * const tags = TagList.create([existingTag])
210
- *
211
- * tags.add(newTag) // tracked as new
212
- * tags.remove(oldTag) // tracked as removed
213
- *
214
- * tags.getNewItems() // [newTag]
215
- * tags.getRemovedItems() // [oldTag]
216
- * ```
217
- */
218
- declare abstract class WatchedList<T> {
219
- protected currentItems: T[];
220
- protected initial: T[];
221
- protected new: T[];
222
- protected removed: T[];
223
- constructor(initialItems?: T[]);
224
- /**
225
- * Returns all current items in the list.
226
- */
227
- getItems(): T[];
228
- /**
229
- * Returns items that were added since the list was created.
230
- */
231
- getNewItems(): T[];
232
- /**
233
- * Returns items that were removed since the list was created.
234
- */
235
- getRemovedItems(): T[];
236
- /**
237
- * Returns whether the given item exists in the current list.
238
- *
239
- * @param item - The item to check
240
- */
241
- exists(item: T): boolean;
242
- /**
243
- * Adds an item to the list, tracking it as new if it wasn't in the initial set.
244
- * If the item was previously removed, it is restored.
245
- *
246
- * @param item - The item to add
247
- */
248
- add(item: T): void;
249
- /**
250
- * Removes an item from the list, tracking it as removed if it was in the initial set.
251
- *
252
- * @param item - The item to remove
253
- */
254
- remove(item: T): void;
255
- /**
256
- * Replaces the entire list with a new set of items, automatically
257
- * computing what was added and what was removed.
258
- *
259
- * @param items - The new set of items
260
- */
261
- update(items: T[]): void;
262
- private isCurrentItem;
263
- private isNewItem;
264
- private isRemovedItem;
265
- private removeFromNew;
266
- private removeFromCurrent;
267
- private removeFromRemoved;
268
- private wasAddedInitially;
269
- }
270
- export { Either, left, right, Optional, UniqueEntityId, ValueObject, WatchedList };
@@ -1,138 +0,0 @@
1
- import { Either } from "./chunk-t21213sm.js";
2
- /**
3
- * Contract for entities that can be created.
4
- *
5
- * @template T - The entity type
6
- */
7
- interface Creatable<T> {
8
- /**
9
- * Persists a new entity for the first time.
10
- *
11
- * @param entity - The entity to create
12
- */
13
- create(entity: T): Promise<void>;
14
- }
15
- /**
16
- * Contract for entities that can be deleted.
17
- *
18
- * @template T - The entity type
19
- */
20
- interface Deletable<T> {
21
- /**
22
- * Deletes an entity from the repository.
23
- *
24
- * @param entity - The entity to delete
25
- */
26
- delete(entity: T): Promise<void>;
27
- }
28
- /**
29
- * Contract for entities that can be retrieved by their unique identifier.
30
- *
31
- * @template T - The entity type
32
- */
33
- interface Findable<T> {
34
- /**
35
- * Finds an entity by its unique identifier.
36
- *
37
- * @param id - The unique identifier of the entity
38
- * @returns The entity if found, `null` otherwise
39
- */
40
- findById(id: string): Promise<T | null>;
41
- }
42
- /**
43
- * Contract for entities that can be persisted.
44
- *
45
- * @template T - The entity type
46
- */
47
- interface Saveable<T> {
48
- /**
49
- * Persists an existing entity.
50
- *
51
- * @param entity - The entity to save
52
- */
53
- save(entity: T): Promise<void>;
54
- }
55
- /**
56
- * Full CRUD repository contract, composing all segregated interfaces.
57
- *
58
- * Use this when the repository needs to support all operations. For more
59
- * constrained repositories, prefer composing only the interfaces you need.
60
- *
61
- * @template T - The entity type
62
- *
63
- * @example
64
- * ```ts
65
- * // full CRUD
66
- * interface UserRepository extends Repository<User> {
67
- * findByEmail(email: string): Promise<User | null>
68
- * }
69
- *
70
- * // read-only
71
- * interface ReportRepository extends Findable<Report> {
72
- * findByPeriod(start: Date, end: Date): Promise<Report[]>
73
- * }
74
- *
75
- * // append-only
76
- * interface AuditRepository extends Creatable<AuditLog> {}
77
- * ```
78
- */
79
- interface Repository<T> extends Findable<T>, Saveable<T>, Creatable<T>, Deletable<T> {}
80
- /**
81
- * Base contract for all use case errors.
82
- *
83
- * Implement this interface to define semantic, domain-aware errors
84
- * that can be returned as the left side of an {@link Either}.
85
- *
86
- * @example
87
- * ```ts
88
- * class UserNotFoundError implements UseCaseError {
89
- * message = "User not found."
90
- * }
91
- *
92
- * type FindUserResult = Either<UserNotFoundError, User>
93
- * ```
94
- */
95
- interface UseCaseError {
96
- message: string;
97
- }
98
- /**
99
- * Represents the expected output shape of any use case.
100
- * Always an {@link Either} — left for errors, right for success.
101
- */
102
- type UseCaseOutput = Either<UseCaseError | never, unknown | null>;
103
- /**
104
- * Base contract for all application use cases.
105
- *
106
- * A use case orchestrates domain logic for a single application operation.
107
- * It receives a typed input, interacts with the domain, and returns an
108
- * {@link Either} — never throwing exceptions directly.
109
- *
110
- * The left side carries a {@link UseCaseError} on failure.
111
- * The right side carries the success value.
112
- *
113
- * @template Input - The shape of the data required to execute the use case
114
- * @template Output - The expected {@link Either} output, constrained to {@link UseCaseOutput}
115
- *
116
- * @example
117
- * ```ts
118
- * type Input = { userId: string }
119
- * type Output = Either<UserNotFoundError, User>
120
- *
121
- * class GetUserUseCase implements UseCase<Input, Output> {
122
- * constructor(private userRepository: UserRepository) {}
123
- *
124
- * async execute({ userId }: Input): Promise<Output> {
125
- * const user = await this.userRepository.findById(userId)
126
- * if (!user) return left(new UserNotFoundError(userId))
127
- * return right(user)
128
- * }
129
- * }
130
- * ```
131
- */
132
- interface UseCase<
133
- Input,
134
- Output extends UseCaseOutput
135
- > {
136
- execute(input: Input): Promise<Output>;
137
- }
138
- export { Creatable, Deletable, Findable, Saveable, Repository, UseCaseError, UseCase };