@comity/order 0.9.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/cjs/contracts/address-snapshot.js +3 -0
  4. package/dist/cjs/contracts/contact.js +3 -0
  5. package/dist/cjs/contracts/customer-snapshot.js +3 -0
  6. package/dist/cjs/contracts/item.js +3 -0
  7. package/dist/cjs/contracts/order-repository.js +3 -0
  8. package/dist/cjs/contracts/order.js +3 -0
  9. package/dist/cjs/contracts/payment-snapshot.js +3 -0
  10. package/dist/cjs/domain/order-transitions.js +52 -0
  11. package/dist/cjs/entities/order.js +459 -0
  12. package/dist/cjs/errors/index.js +6 -0
  13. package/dist/cjs/errors/order.js +39 -0
  14. package/dist/cjs/index.js +8 -0
  15. package/dist/cjs/repositories/index.js +6 -0
  16. package/dist/cjs/repositories/memory.js +69 -0
  17. package/dist/cjs/value-objects/order-id.js +70 -0
  18. package/dist/esm/contracts/address-snapshot.js +2 -0
  19. package/dist/esm/contracts/contact.js +2 -0
  20. package/dist/esm/contracts/customer-snapshot.js +2 -0
  21. package/dist/esm/contracts/item.js +2 -0
  22. package/dist/esm/contracts/order-repository.js +2 -0
  23. package/dist/esm/contracts/order.js +2 -0
  24. package/dist/esm/contracts/payment-snapshot.js +2 -0
  25. package/dist/esm/domain/order-transitions.js +48 -0
  26. package/dist/esm/entities/order.js +455 -0
  27. package/dist/esm/errors/index.js +2 -0
  28. package/dist/esm/errors/order.js +35 -0
  29. package/dist/esm/index.js +3 -0
  30. package/dist/esm/repositories/index.js +2 -0
  31. package/dist/esm/repositories/memory.js +65 -0
  32. package/dist/esm/value-objects/order-id.js +66 -0
  33. package/dist/types/contracts/address-snapshot.d.ts +35 -0
  34. package/dist/types/contracts/contact.d.ts +14 -0
  35. package/dist/types/contracts/customer-snapshot.d.ts +24 -0
  36. package/dist/types/contracts/item.d.ts +100 -0
  37. package/dist/types/contracts/order-repository.d.ts +82 -0
  38. package/dist/types/contracts/order.d.ts +108 -0
  39. package/dist/types/contracts/payment-snapshot.d.ts +36 -0
  40. package/dist/types/domain/order-transitions.d.ts +22 -0
  41. package/dist/types/entities/order.d.ts +168 -0
  42. package/dist/types/errors/index.d.ts +2 -0
  43. package/dist/types/errors/order.d.ts +44 -0
  44. package/dist/types/index.d.ts +9 -0
  45. package/dist/types/repositories/index.d.ts +1 -0
  46. package/dist/types/repositories/memory.d.ts +26 -0
  47. package/dist/types/value-objects/order-id.d.ts +50 -0
  48. package/package.json +102 -0
@@ -0,0 +1,168 @@
1
+ import type { Price } from "@comity/pricing";
2
+ import type { Result } from "@comity/primitives/result";
3
+ import type { OrderAddressSnapshot } from "../contracts/address-snapshot.js";
4
+ import type { OrderCustomerSnapshot } from "../contracts/customer-snapshot.js";
5
+ import type { OrderItem } from "../contracts/item.js";
6
+ import type { OrderCreate, OrderItemInput, OrderSnapshot, OrderStatus, OrderUpdate } from "../contracts/order.js";
7
+ import type { OrderPaymentSnapshot } from "../contracts/payment-snapshot.js";
8
+ import type { OrderId } from "../value-objects/order-id.js";
9
+ import type { ChannelId } from "@comity/organization";
10
+ import { Instant } from "@comity/primitives/time";
11
+ import { OrderError } from "../errors/order.js";
12
+ /**
13
+ * Represents an order entity in the system.
14
+ *
15
+ * @remark
16
+ * An order is a lifecycle aggregate: it owns its status transitions, its line
17
+ * items, the applied pricing result, and its historical facts (buyer,
18
+ * addresses, payment). It does not validate coupons, does not know promotion
19
+ * rules, and does not orchestrate external services — those concerns belong
20
+ * to `@comity/pricing`, other Core Modules, and application/domain services.
21
+ */
22
+ export declare class Order {
23
+ #private;
24
+ /**
25
+ * @param fields - The fields used to create or hydrate the order.
26
+ * @param id - The unique identifier of the order, if it has been assigned.
27
+ */
28
+ constructor(fields: OrderCreate, id?: OrderId);
29
+ /**
30
+ * @returns The unique identifier of the order, if it has been assigned.
31
+ */
32
+ get id(): OrderId | undefined;
33
+ /**
34
+ * @returns The commercial channel through which the order was placed.
35
+ */
36
+ get channelId(): ChannelId;
37
+ /**
38
+ * @returns The lifecycle status of the order.
39
+ */
40
+ get status(): OrderStatus;
41
+ /**
42
+ * @returns The order items.
43
+ */
44
+ get items(): ReadonlyArray<OrderItem>;
45
+ /**
46
+ * @returns The order price.
47
+ */
48
+ get price(): Price;
49
+ /**
50
+ * @returns The historical buyer fact, if captured.
51
+ */
52
+ get customer(): OrderCustomerSnapshot | undefined;
53
+ /**
54
+ * @returns The historical address facts, if captured.
55
+ */
56
+ get addresses(): ReadonlyArray<OrderAddressSnapshot> | undefined;
57
+ /**
58
+ * @returns The historical payment facts, if attached.
59
+ */
60
+ get payments(): ReadonlyArray<OrderPaymentSnapshot> | undefined;
61
+ /**
62
+ * @returns The custom metadata of the order, if any.
63
+ */
64
+ get meta(): Readonly<Record<string, unknown>> | undefined;
65
+ /**
66
+ * @returns The timestamp when the order was created.
67
+ */
68
+ get createdAt(): Instant;
69
+ /**
70
+ * @returns The timestamp when the order was last updated.
71
+ */
72
+ get updatedAt(): Instant;
73
+ /**
74
+ * Updates the applied pricing result and metadata.
75
+ *
76
+ * Status transitions are performed through the domain methods; items are
77
+ * mutated through `addItem`/`removeItem`/`updateItemQuantity`.
78
+ *
79
+ * @param changes - The changes to apply to the order.
80
+ */
81
+ update(changes: OrderUpdate): void;
82
+ /**
83
+ * Appends a historical payment fact to the order.
84
+ *
85
+ * A payment may be processed after the order is created, so the outcome is
86
+ * recorded through a dedicated method rather than the generic update path.
87
+ * This records a historical fact only: it performs no payment logic, knows
88
+ * no payment module, and does not coordinate, authorize, or capture.
89
+ *
90
+ * @param snapshot - The payment fact to record.
91
+ */
92
+ attachPayment(snapshot: OrderPaymentSnapshot): void;
93
+ /**
94
+ * Replaces the shipping destination of the order.
95
+ *
96
+ * Allowed only while the order is `draft | pending`; in later statuses the
97
+ * shipping destination is immutable (ADR-018). The order must hold exactly
98
+ * one shipping address fact; a missing or ambiguous one is an error rather
99
+ * than a guess. A structurally identical destination is a successful no-op
100
+ * that leaves timestamps untouched. The operation never reaches the generic
101
+ * `update()` path, never touches billing, customer, item, price, or payment
102
+ * facts, and records no history: the aggregate retains only the current
103
+ * shipping destination.
104
+ *
105
+ * @param destination - The replacement shipping destination. The address
106
+ * role is owned by this operation and is always stored as `"shipping"`.
107
+ *
108
+ * @returns A result indicating the success or failure of the change.
109
+ */
110
+ changeShippingDestination(destination: Omit<OrderAddressSnapshot, "role">): Result<void, OrderError>;
111
+ /**
112
+ * Adds an item to the order.
113
+ *
114
+ * @param input - The item data to add.
115
+ *
116
+ * @returns The created item, or an `invalid_quantity` error when the
117
+ * quantity is not a positive integer.
118
+ */
119
+ addItem(input: OrderItemInput): Result<OrderItem, OrderError>;
120
+ /**
121
+ * Removes an item from the order.
122
+ *
123
+ * @param itemId - The order item ID.
124
+ *
125
+ * @returns A result indicating the success or failure of the removal.
126
+ */
127
+ removeItem(itemId: string): Result<void, OrderError>;
128
+ /**
129
+ * Updates the quantity of an order item.
130
+ *
131
+ * @param itemId - The order item ID.
132
+ * @param quantity - The new quantity.
133
+ *
134
+ * @returns A result indicating the success or failure of the update.
135
+ */
136
+ updateItemQuantity(itemId: string, quantity: number): Result<void, OrderError>;
137
+ /**
138
+ * Submits the order: `draft` → `pending`.
139
+ *
140
+ * @returns A result indicating the success or failure of the transition.
141
+ */
142
+ submit(): Result<void, OrderError>;
143
+ /**
144
+ * Confirms the order: `pending` → `confirmed`.
145
+ *
146
+ * @returns A result indicating the success or failure of the transition.
147
+ */
148
+ confirm(): Result<void, OrderError>;
149
+ /**
150
+ * Fulfills the order: `confirmed` → `fulfilled`.
151
+ *
152
+ * @returns A result indicating the success or failure of the transition.
153
+ */
154
+ fulfill(): Result<void, OrderError>;
155
+ /**
156
+ * Cancels the order: `draft | pending | confirmed` → `cancelled`.
157
+ *
158
+ * @returns A result indicating the success or failure of the transition.
159
+ */
160
+ cancel(): Result<void, OrderError>;
161
+ /**
162
+ * Creates a snapshot of the current state of the order.
163
+ * Requires the order to have an assigned identifier.
164
+ *
165
+ * @returns A snapshot representing the current state of the order.
166
+ */
167
+ snapshot(): OrderSnapshot;
168
+ }
@@ -0,0 +1,2 @@
1
+ export type { OrderErrorReason } from "./order.js";
2
+ export { OrderError } from "./order.js";
@@ -0,0 +1,44 @@
1
+ import type { ErrorMeta } from "@comity/primitives/errors";
2
+ import { BaseError } from "@comity/primitives/errors";
3
+ /**
4
+ * Order error reason.
5
+ *
6
+ * Contains only domain conditions owned by the Order aggregate. Not-found is
7
+ * a `null` repository result, never an error; infrastructure and
8
+ * cross-module failures (`repository_error`, `insufficient_stock`,
9
+ * `product_not_available`, coupon validation) belong to other layers/modules.
10
+ */
11
+ export type OrderErrorReason = "invalid_quantity" | "invalid_item" | "invalid_status_transition" | "shipping_destination_immutable" | "ambiguous_shipping_destination";
12
+ /**
13
+ * Order error metadata.
14
+ */
15
+ interface OrderErrorMeta extends ErrorMeta {
16
+ /** Error reason. */
17
+ readonly reason: OrderErrorReason;
18
+ /** Contextual details. */
19
+ readonly details?: Readonly<{
20
+ /** Affected order ID. */
21
+ orderId?: string;
22
+ /** Affected item ID. */
23
+ itemId?: string;
24
+ /** Invalid input field. */
25
+ field?: string;
26
+ /** Source status of an invalid transition. */
27
+ from?: string;
28
+ /** Target status of an invalid transition. */
29
+ to?: string;
30
+ }>;
31
+ }
32
+ /**
33
+ * Order operation error with typed reasons.
34
+ */
35
+ export declare class OrderError extends BaseError<OrderErrorMeta> {
36
+ /** Error code. */
37
+ readonly code: `order:${OrderErrorReason}`;
38
+ /**
39
+ * @param reason - The reason for the order error.
40
+ * @param meta - Additional metadata for the error.
41
+ */
42
+ constructor(reason: OrderErrorReason, meta?: Omit<OrderErrorMeta, "reason">);
43
+ }
44
+ export {};
@@ -0,0 +1,9 @@
1
+ export type { OrderAddressRole, OrderAddressSnapshot } from "./contracts/address-snapshot.js";
2
+ export type { OrderContact } from "./contracts/contact.js";
3
+ export type { OrderCustomerSnapshot } from "./contracts/customer-snapshot.js";
4
+ export type { OrderItem, OrderProductAttribute, OrderProductOption, OrderProductSnapshot, OrderVariantSnapshot, } from "./contracts/item.js";
5
+ export type { OrderRepository, OrderRepositoryContext, OrderSearchCriteria, OrderSearchResult } from "./contracts/order-repository.js";
6
+ export type { OrderCreate, OrderData, OrderItemInput, OrderSnapshot, OrderState, OrderStatus, OrderUpdate, } from "./contracts/order.js";
7
+ export type { OrderPaymentSnapshot, OrderPaymentStatus } from "./contracts/payment-snapshot.js";
8
+ export { Order } from "./entities/order.js";
9
+ export { OrderId } from "./value-objects/order-id.js";
@@ -0,0 +1 @@
1
+ export { MemoryOrderRepository } from "./memory.js";
@@ -0,0 +1,26 @@
1
+ import type { RepositoryError } from "@comity/primitives/errors";
2
+ import type { Result } from "@comity/primitives/result";
3
+ import type { OrderRepository, OrderRepositoryContext, OrderSearchCriteria, OrderSearchResult } from "../contracts/order-repository.js";
4
+ import type { Order } from "../entities/order.js";
5
+ import type { OrderId } from "../value-objects/order-id.js";
6
+ /**
7
+ * In-memory implementation of `OrderRepository` for testing and development purposes.
8
+ *
9
+ * Note: This implementation is not suitable for production use as it does not persist
10
+ * sessions and is not shared across multiple instances of the application.
11
+ */
12
+ export declare class MemoryOrderRepository implements OrderRepository {
13
+ #private;
14
+ /**
15
+ * @inheritdoc
16
+ */
17
+ getById(id: OrderId, ctx: OrderRepositoryContext): Promise<Result<Order | null, RepositoryError>>;
18
+ /**
19
+ * @inheritdoc
20
+ */
21
+ save(order: Order, ctx: OrderRepositoryContext): Promise<Result<void, RepositoryError>>;
22
+ /**
23
+ * @inheritdoc
24
+ */
25
+ search(criteria: OrderSearchCriteria | undefined, ctx: OrderRepositoryContext): Promise<Result<OrderSearchResult, RepositoryError>>;
26
+ }
@@ -0,0 +1,50 @@
1
+ import type { Result } from "@comity/primitives/result";
2
+ import { InvalidIdentifierError } from "@comity/primitives/errors";
3
+ /**
4
+ * OrderId is a value object that represents the unique identifier of an order.
5
+ *
6
+ * @remarks
7
+ * OrderId is globally unique. The current implementation generates UUIDs.
8
+ * The repository uses a composite key of `TenantId + OrderId` as its physical
9
+ * storage namespace, but the OrderId itself is globally unique and not scoped
10
+ * to a tenant.
11
+ */
12
+ export declare class OrderId {
13
+ #private;
14
+ /**
15
+ * Creates an OrderId from an identifier string.
16
+ *
17
+ * The Value Object is always created in a valid state; an empty or
18
+ * whitespace-only value yields an `empty` failure instead of throwing.
19
+ *
20
+ * @param value - The value of the order ID.
21
+ *
22
+ * @returns The OrderId, or an `empty` error when the value is empty or
23
+ * whitespace-only.
24
+ */
25
+ static create(value: string): Result<OrderId, InvalidIdentifierError>;
26
+ /**
27
+ * @param value - The value of the order ID.
28
+ */
29
+ private constructor();
30
+ /**
31
+ * Returns the underlying string value of the order ID.
32
+ *
33
+ * @returns The underlying identifier.
34
+ */
35
+ get value(): string;
36
+ /**
37
+ * Checks if this OrderId is equal to another OrderId.
38
+ *
39
+ * @param other - The other OrderId to compare with.
40
+ *
41
+ * @returns True if the OrderIds are equal, false otherwise.
42
+ */
43
+ equals(other: OrderId): boolean;
44
+ /**
45
+ * Returns a string representation of the order ID.
46
+ *
47
+ * @returns The string representation of the order ID.
48
+ */
49
+ toString(): string;
50
+ }
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@comity/order",
3
+ "version": "0.9.0",
4
+ "description": "Order domain contracts for Comity commerce modules.",
5
+ "type": "module",
6
+ "private": false,
7
+ "author": "Filippo Bovo <hello@filippobovo.com>",
8
+ "license": "MIT",
9
+ "comity": {
10
+ "layer": "core"
11
+ },
12
+ "homepage": "https://github.com/comityjs/framework#readme",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/comityjs/framework.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/comityjs/framework/issues"
19
+ },
20
+ "engines": {
21
+ "node": ">=24.0.0"
22
+ },
23
+ "keywords": [
24
+ "comity",
25
+ "comityjs",
26
+ "cart",
27
+ "ecommerce",
28
+ "order",
29
+ "typescript"
30
+ ],
31
+ "files": [
32
+ "./dist",
33
+ "!./dist/**/*.map"
34
+ ],
35
+ "main": "./dist/cjs/index.js",
36
+ "module": "./dist/esm/index.js",
37
+ "types": "./dist/types/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "import": {
41
+ "types": "./dist/types/index.d.ts",
42
+ "default": "./dist/esm/index.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/types/index.d.ts",
46
+ "default": "./dist/cjs/index.js"
47
+ }
48
+ },
49
+ "./errors": {
50
+ "import": {
51
+ "types": "./dist/types/errors/index.d.ts",
52
+ "default": "./dist/esm/errors/index.js"
53
+ },
54
+ "require": {
55
+ "types": "./dist/types/errors/index.d.ts",
56
+ "default": "./dist/cjs/errors/index.js"
57
+ }
58
+ },
59
+ "./repositories": {
60
+ "import": {
61
+ "types": "./dist/types/repositories/index.d.ts",
62
+ "default": "./dist/esm/repositories/index.js"
63
+ },
64
+ "require": {
65
+ "types": "./dist/types/repositories/index.d.ts",
66
+ "default": "./dist/cjs/repositories/index.js"
67
+ }
68
+ },
69
+ "./package.json": "./package.json"
70
+ },
71
+ "typesVersions": {
72
+ "*": {
73
+ "errors": [
74
+ "./dist/types/errors/index.d.ts"
75
+ ],
76
+ "repositories": [
77
+ "./dist/types/repositories/index.d.ts"
78
+ ]
79
+ }
80
+ },
81
+ "publishConfig": {
82
+ "registry": "https://registry.npmjs.org",
83
+ "access": "public"
84
+ },
85
+ "sideEffects": false,
86
+ "dependencies": {
87
+ "@comity/pricing": "0.9.0",
88
+ "@comity/organization": "0.9.0",
89
+ "@comity/primitives": "0.9.0"
90
+ },
91
+ "devDependencies": {
92
+ "@types/node": "^24.13.4",
93
+ "typescript": "^5.9.3"
94
+ },
95
+ "scripts": {
96
+ "build": "node ../../scripts/build.mjs",
97
+ "dev": "node ../../scripts/build.mjs --watch",
98
+ "test": "vitest run --coverage",
99
+ "type-check": "tsc -p tsconfig.json --noEmit",
100
+ "lint": "eslint --ext .ts src"
101
+ }
102
+ }