@shirudo/ddd-kit 0.9.8 → 0.11.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.
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Composable TypeScript toolkit for tactical Domain-Driven Design.
4
4
 
5
+ > **⚠️ BETA WARNING**
6
+ >
7
+ > This library is currently in beta. The API is subject to change until a stable 1.0.0 release. Breaking changes may occur in minor versions during the beta phase. Please pin your dependencies to specific versions.
8
+
5
9
  ## Badges
6
10
 
7
11
  ![npm version](https://img.shields.io/npm/v/@shirudo/ddd-kit)
@@ -37,9 +41,9 @@ pnpm add @shirudo/ddd-kit
37
41
  Here's a minimal example showing how to create and use a Value Object:
38
42
 
39
43
  ```typescript
40
- import { vo, type ValueObject } from "@shirudo/ddd-kit";
44
+ import { vo, type VO } from "@shirudo/ddd-kit";
41
45
 
42
- type EmailAddress = ValueObject<{
46
+ type EmailAddress = VO<{
43
47
  value: string;
44
48
  }>;
45
49
 
@@ -125,10 +129,10 @@ The `Result<T, E>` type provides functional error handling without exceptions. I
125
129
  ### Creating a Value Object
126
130
 
127
131
  ```typescript
128
- import { vo, voEquals, voEqualsExcept, voWithValidation, type ValueObject } from "@shirudo/ddd-kit";
132
+ import { vo, voEquals, voEqualsExcept, voWithValidation, type VO } from "@shirudo/ddd-kit";
129
133
 
130
- // Simple value object
131
- type Money = ValueObject<{
134
+ // Simple value object (Functional Style)
135
+ type Money = VO<{
132
136
  amount: number;
133
137
  currency: string;
134
138
  }>;
@@ -136,6 +140,22 @@ type Money = ValueObject<{
136
140
  const price = vo({ amount: 99.99, currency: "USD" });
137
141
  // price is deeply immutable - nested objects and arrays are also frozen
138
142
 
143
+ // Class-based Value Object (OOP Style)
144
+ import { ValueObject } from "@shirudo/ddd-kit";
145
+
146
+ class Address extends ValueObject<{ street: string; city: string }> {
147
+ constructor(props: { street: string; city: string }) {
148
+ super(props);
149
+ }
150
+
151
+ get street(): string {
152
+ return this.props.street;
153
+ }
154
+ }
155
+
156
+ const address = new Address({ street: "Main St", city: "New York" });
157
+ // address.props is immutable
158
+
139
159
  // Value object with validation (returns Result)
140
160
  const result = voWithValidation(
141
161
  { amount: 100, currency: "USD" },
@@ -458,7 +478,7 @@ import {
458
478
  type CreateOrderCommand = Command & {
459
479
  type: "CreateOrder";
460
480
  customerId: string;
461
- items: Array<{ productId: string; quantity: number }>;
481
+ items: Array<{ productId: string; quantity: number; price: number }>;
462
482
  };
463
483
 
464
484
  // Create a command handler
@@ -471,7 +491,14 @@ const createOrderHandler: CommandHandler<CreateOrderCommand, string> = async (
471
491
  }
472
492
 
473
493
  // Perform business logic
474
- const order = Order.create(cmd.customerId, cmd.items);
494
+ const orderId = `order-${Date.now()}` as OrderId;
495
+ const order = Order.create(orderId, cmd.customerId);
496
+
497
+ // Add items to the order
498
+ for (const item of cmd.items) {
499
+ order.addItem(item.productId, item.quantity, item.price);
500
+ }
501
+
475
502
  await repository.save(order);
476
503
 
477
504
  return ok(order.id);
@@ -481,7 +508,7 @@ const createOrderHandler: CommandHandler<CreateOrderCommand, string> = async (
481
508
  const result = await createOrderHandler({
482
509
  type: "CreateOrder",
483
510
  customerId: "customer-123",
484
- items: [{ productId: "product-1", quantity: 2 }],
511
+ items: [{ productId: "product-1", quantity: 2, price: 10.0 }],
485
512
  });
486
513
 
487
514
  if (result.ok) {
@@ -498,7 +525,7 @@ commandBus.register("CreateOrder", createOrderHandler);
498
525
  const busResult = await commandBus.execute({
499
526
  type: "CreateOrder",
500
527
  customerId: "customer-123",
501
- items: [{ productId: "product-1", quantity: 2 }],
528
+ items: [{ productId: "product-1", quantity: 2, price: 10.0 }],
502
529
  });
503
530
  ```
504
531
 
@@ -568,7 +595,14 @@ const createOrderHandler: CommandHandler<CreateOrderCommand, string> = async (
568
595
  return await withCommit(
569
596
  { outbox, bus, uow },
570
597
  async () => {
571
- const order = Order.create(cmd.customerId, cmd.items);
598
+ const orderId = `order-${Date.now()}` as OrderId;
599
+ const order = Order.create(orderId, cmd.customerId);
600
+
601
+ // Add items to the order
602
+ for (const item of cmd.items) {
603
+ order.addItem(item.productId, item.quantity, item.price);
604
+ }
605
+
572
606
  await repository.save(order);
573
607
 
574
608
  return {
@@ -606,7 +640,7 @@ import {
606
640
  type CreateOrderCommand = Command & {
607
641
  type: "CreateOrder";
608
642
  customerId: string;
609
- items: OrderItem[];
643
+ items: Array<{ productId: string; quantity: number; price: number }>;
610
644
  };
611
645
 
612
646
  type GetOrderQuery = Query & {
@@ -618,7 +652,14 @@ type GetOrderQuery = Query & {
618
652
  const createOrderHandler: CommandHandler<CreateOrderCommand, OrderId> = async (
619
653
  cmd
620
654
  ) => {
621
- const order = Order.create(cmd.customerId, cmd.items);
655
+ const orderId = `order-${Date.now()}` as OrderId;
656
+ const order = Order.create(orderId, cmd.customerId);
657
+
658
+ // Add items to the order
659
+ for (const item of cmd.items) {
660
+ order.addItem(item.productId, item.quantity, item.price);
661
+ }
662
+
622
663
  await repository.save(order);
623
664
  return ok(order.id);
624
665
  };
@@ -0,0 +1,57 @@
1
+ type Key = string | symbol;
2
+ type PathSegment = string | number | symbol;
3
+ interface DeepOmitOptions {
4
+ /**
5
+ * Keys to ignore everywhere in the object tree.
6
+ * Only applies to object properties, not Map/Set/TypedArray contents.
7
+ */
8
+ readonly ignoreKeys?: readonly Key[];
9
+ /**
10
+ * Fine-grained control: Key + path (without current key).
11
+ * Example path: ["user", "meta", 0, "data"]
12
+ */
13
+ readonly ignoreKeyPredicate?: (key: Key, path: readonly PathSegment[]) => boolean;
14
+ }
15
+ /**
16
+ * Creates a deep copy of `value` with certain keys removed according to the provided rules.
17
+ *
18
+ * This function recursively traverses the object tree and removes keys that match
19
+ * the criteria specified in `options`. Built-in types (Date, Map, Set, TypedArrays, etc.)
20
+ * are treated atomically and not modified.
21
+ *
22
+ * @param value - The value to create a deep copy from
23
+ * @param options - Options specifying which keys to ignore
24
+ * @returns A deep copy of `value` with specified keys removed
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const obj = { a: 1, b: { c: 2, d: 3 } };
29
+ * const result = deepOmit(obj, { ignoreKeys: ['d'] });
30
+ * // result: { a: 1, b: { c: 2 } }
31
+ * ```
32
+ */
33
+ declare function deepOmit<T>(value: T, options: DeepOmitOptions): T;
34
+
35
+ type DeepEqualExceptOptions = DeepOmitOptions;
36
+ /**
37
+ * Performs a deep equality comparison between two values after omitting specified keys.
38
+ *
39
+ * This function first removes the specified keys from both values using `deepOmit`,
40
+ * then performs a deep equality check using `deepEqual`.
41
+ *
42
+ * @param a - The first value to compare
43
+ * @param b - The second value to compare
44
+ * @param options - Options specifying which keys to omit before comparison
45
+ * @returns `true` if the values are deeply equal after omitting specified keys, `false` otherwise
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const obj1 = { id: 1, name: "Alice", updatedAt: "2024-01-01" };
50
+ * const obj2 = { id: 2, name: "Alice", updatedAt: "2024-01-02" };
51
+ *
52
+ * deepEqualExcept(obj1, obj2, { ignoreKeys: ["id", "updatedAt"] }); // true
53
+ * ```
54
+ */
55
+ declare function deepEqualExcept(a: unknown, b: unknown, options: DeepEqualExceptOptions): boolean;
56
+
57
+ export { type DeepOmitOptions as D, type Key as K, type PathSegment as P, type DeepEqualExceptOptions as a, deepEqualExcept as b, deepOmit as d };