@unifiedcommerce/core 0.1.0 → 0.2.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 (245) hide show
  1. package/dist/auth/setup.d.ts.map +1 -1
  2. package/dist/auth/setup.js +8 -3
  3. package/dist/config/types.d.ts +3 -1
  4. package/dist/config/types.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/interfaces/mcp/server.d.ts +3 -5
  9. package/dist/interfaces/mcp/server.d.ts.map +1 -1
  10. package/dist/interfaces/mcp/server.js +25 -510
  11. package/dist/interfaces/mcp/tool-builder.d.ts +120 -0
  12. package/dist/interfaces/mcp/tool-builder.d.ts.map +1 -0
  13. package/dist/interfaces/mcp/tool-builder.js +224 -0
  14. package/dist/interfaces/mcp/tools/analytics.d.ts +42 -0
  15. package/dist/interfaces/mcp/tools/analytics.d.ts.map +1 -0
  16. package/dist/interfaces/mcp/tools/analytics.js +70 -0
  17. package/dist/interfaces/mcp/tools/cart.d.ts +14 -0
  18. package/dist/interfaces/mcp/tools/cart.d.ts.map +1 -0
  19. package/dist/interfaces/mcp/tools/cart.js +47 -0
  20. package/dist/interfaces/mcp/tools/catalog.d.ts +53 -0
  21. package/dist/interfaces/mcp/tools/catalog.d.ts.map +1 -0
  22. package/dist/interfaces/mcp/tools/catalog.js +284 -0
  23. package/dist/interfaces/mcp/tools/index.d.ts +3 -0
  24. package/dist/interfaces/mcp/tools/index.d.ts.map +1 -0
  25. package/dist/interfaces/mcp/tools/index.js +20 -0
  26. package/dist/interfaces/mcp/tools/inventory.d.ts +27 -0
  27. package/dist/interfaces/mcp/tools/inventory.d.ts.map +1 -0
  28. package/dist/interfaces/mcp/tools/inventory.js +143 -0
  29. package/dist/interfaces/mcp/tools/orders.d.ts +18 -0
  30. package/dist/interfaces/mcp/tools/orders.d.ts.map +1 -0
  31. package/dist/interfaces/mcp/tools/orders.js +82 -0
  32. package/dist/interfaces/mcp/tools/pricing.d.ts +29 -0
  33. package/dist/interfaces/mcp/tools/pricing.d.ts.map +1 -0
  34. package/dist/interfaces/mcp/tools/pricing.js +90 -0
  35. package/dist/interfaces/mcp/tools/promotions.d.ts +44 -0
  36. package/dist/interfaces/mcp/tools/promotions.d.ts.map +1 -0
  37. package/dist/interfaces/mcp/tools/promotions.js +109 -0
  38. package/dist/interfaces/mcp/tools/registry.d.ts +32 -0
  39. package/dist/interfaces/mcp/tools/registry.d.ts.map +1 -0
  40. package/dist/interfaces/mcp/tools/registry.js +55 -0
  41. package/dist/interfaces/mcp/tools/search.d.ts +14 -0
  42. package/dist/interfaces/mcp/tools/search.d.ts.map +1 -0
  43. package/dist/interfaces/mcp/tools/search.js +39 -0
  44. package/dist/interfaces/mcp/tools/webhooks.d.ts +15 -0
  45. package/dist/interfaces/mcp/tools/webhooks.d.ts.map +1 -0
  46. package/dist/interfaces/mcp/tools/webhooks.js +48 -0
  47. package/dist/interfaces/mcp/transport.d.ts +17 -2
  48. package/dist/interfaces/mcp/transport.d.ts.map +1 -1
  49. package/dist/interfaces/mcp/transport.js +91 -44
  50. package/dist/interfaces/rest/router.d.ts.map +1 -1
  51. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  52. package/dist/interfaces/rest/routes/checkout.js +1 -1
  53. package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
  54. package/dist/interfaces/rest/routes/promotions.js +3 -2
  55. package/dist/kernel/database/adapter.d.ts +8 -0
  56. package/dist/kernel/database/adapter.d.ts.map +1 -1
  57. package/dist/kernel/factory/repository-factory.d.ts.map +1 -1
  58. package/dist/kernel/factory/repository-factory.js +3 -1
  59. package/dist/kernel/local-api.d.ts.map +1 -1
  60. package/dist/kernel/local-api.js +2 -0
  61. package/dist/kernel/plugin/manifest.d.ts +3 -3
  62. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  63. package/dist/kernel/plugin/manifest.js +36 -7
  64. package/dist/runtime/kernel.d.ts +1 -2
  65. package/dist/runtime/kernel.d.ts.map +1 -1
  66. package/dist/runtime/kernel.js +16 -8
  67. package/dist/runtime/server.d.ts.map +1 -1
  68. package/dist/runtime/server.js +8 -3
  69. package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
  70. package/dist/test-utils/create-pglite-adapter.js +7 -6
  71. package/dist/tsconfig.tsbuildinfo +1 -0
  72. package/package.json +2 -2
  73. package/src/adapters/console-email.ts +0 -43
  74. package/src/auth/access.ts +0 -187
  75. package/src/auth/auth-schema.ts +0 -139
  76. package/src/auth/middleware.ts +0 -161
  77. package/src/auth/org.ts +0 -41
  78. package/src/auth/permissions.ts +0 -28
  79. package/src/auth/setup.ts +0 -169
  80. package/src/auth/system-actor.ts +0 -19
  81. package/src/auth/types.ts +0 -10
  82. package/src/config/defaults.ts +0 -82
  83. package/src/config/define-config.ts +0 -53
  84. package/src/config/types.ts +0 -299
  85. package/src/generated/plugin-capabilities.d.ts +0 -20
  86. package/src/generated/plugin-manifest.ts +0 -23
  87. package/src/generated/plugin-repositories.d.ts +0 -20
  88. package/src/hooks/checkout-completion.ts +0 -262
  89. package/src/hooks/checkout.ts +0 -677
  90. package/src/hooks/order-emails.ts +0 -62
  91. package/src/index.ts +0 -214
  92. package/src/interfaces/mcp/agent-prompt.ts +0 -174
  93. package/src/interfaces/mcp/context-enrichment.ts +0 -177
  94. package/src/interfaces/mcp/server.ts +0 -617
  95. package/src/interfaces/mcp/transport.ts +0 -68
  96. package/src/interfaces/rest/customer-portal.ts +0 -299
  97. package/src/interfaces/rest/index.ts +0 -74
  98. package/src/interfaces/rest/router.ts +0 -334
  99. package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
  100. package/src/interfaces/rest/routes/audit.ts +0 -50
  101. package/src/interfaces/rest/routes/carts.ts +0 -89
  102. package/src/interfaces/rest/routes/catalog.ts +0 -493
  103. package/src/interfaces/rest/routes/checkout.ts +0 -283
  104. package/src/interfaces/rest/routes/inventory.ts +0 -70
  105. package/src/interfaces/rest/routes/media.ts +0 -86
  106. package/src/interfaces/rest/routes/orders.ts +0 -78
  107. package/src/interfaces/rest/routes/payments.ts +0 -60
  108. package/src/interfaces/rest/routes/pricing.ts +0 -57
  109. package/src/interfaces/rest/routes/promotions.ts +0 -92
  110. package/src/interfaces/rest/routes/search.ts +0 -71
  111. package/src/interfaces/rest/routes/webhooks.ts +0 -46
  112. package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
  113. package/src/interfaces/rest/schemas/audit.ts +0 -46
  114. package/src/interfaces/rest/schemas/carts.ts +0 -125
  115. package/src/interfaces/rest/schemas/catalog.ts +0 -450
  116. package/src/interfaces/rest/schemas/checkout.ts +0 -66
  117. package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
  118. package/src/interfaces/rest/schemas/inventory.ts +0 -138
  119. package/src/interfaces/rest/schemas/media.ts +0 -75
  120. package/src/interfaces/rest/schemas/orders.ts +0 -104
  121. package/src/interfaces/rest/schemas/pricing.ts +0 -80
  122. package/src/interfaces/rest/schemas/promotions.ts +0 -110
  123. package/src/interfaces/rest/schemas/responses.ts +0 -85
  124. package/src/interfaces/rest/schemas/search.ts +0 -58
  125. package/src/interfaces/rest/schemas/shared.ts +0 -62
  126. package/src/interfaces/rest/schemas/webhooks.ts +0 -68
  127. package/src/interfaces/rest/utils.ts +0 -104
  128. package/src/interfaces/rest/webhook-router.ts +0 -50
  129. package/src/kernel/compensation/executor.ts +0 -61
  130. package/src/kernel/compensation/types.ts +0 -26
  131. package/src/kernel/database/adapter.ts +0 -13
  132. package/src/kernel/database/drizzle-db.ts +0 -56
  133. package/src/kernel/database/migrate.ts +0 -76
  134. package/src/kernel/database/plugin-types.ts +0 -34
  135. package/src/kernel/database/schema.ts +0 -49
  136. package/src/kernel/database/scoped-db.ts +0 -68
  137. package/src/kernel/database/tx-context.ts +0 -46
  138. package/src/kernel/error-mapper.ts +0 -15
  139. package/src/kernel/errors.ts +0 -89
  140. package/src/kernel/factory/repository-factory.ts +0 -242
  141. package/src/kernel/hooks/create-context.ts +0 -43
  142. package/src/kernel/hooks/executor.ts +0 -88
  143. package/src/kernel/hooks/registry.ts +0 -74
  144. package/src/kernel/hooks/types.ts +0 -52
  145. package/src/kernel/http-error.ts +0 -44
  146. package/src/kernel/jobs/adapter.ts +0 -36
  147. package/src/kernel/jobs/drizzle-adapter.ts +0 -58
  148. package/src/kernel/jobs/runner.ts +0 -153
  149. package/src/kernel/jobs/schema.ts +0 -46
  150. package/src/kernel/jobs/types.ts +0 -30
  151. package/src/kernel/local-api.ts +0 -185
  152. package/src/kernel/plugin/manifest.ts +0 -253
  153. package/src/kernel/query/executor.ts +0 -184
  154. package/src/kernel/query/registry.ts +0 -46
  155. package/src/kernel/result.ts +0 -33
  156. package/src/kernel/schema/extra-columns.ts +0 -37
  157. package/src/kernel/service-registry.ts +0 -76
  158. package/src/kernel/service-timing.ts +0 -89
  159. package/src/kernel/state-machine/machine.ts +0 -101
  160. package/src/modules/analytics/drizzle-adapter.ts +0 -426
  161. package/src/modules/analytics/hooks.ts +0 -11
  162. package/src/modules/analytics/models.ts +0 -125
  163. package/src/modules/analytics/repository/index.ts +0 -6
  164. package/src/modules/analytics/service.ts +0 -245
  165. package/src/modules/analytics/types.ts +0 -180
  166. package/src/modules/audit/hooks.ts +0 -78
  167. package/src/modules/audit/schema.ts +0 -33
  168. package/src/modules/audit/service.ts +0 -151
  169. package/src/modules/cart/access.ts +0 -27
  170. package/src/modules/cart/matcher.ts +0 -26
  171. package/src/modules/cart/repository/index.ts +0 -234
  172. package/src/modules/cart/schema.ts +0 -42
  173. package/src/modules/cart/schemas.ts +0 -38
  174. package/src/modules/cart/service.ts +0 -541
  175. package/src/modules/catalog/repository/index.ts +0 -772
  176. package/src/modules/catalog/schema.ts +0 -203
  177. package/src/modules/catalog/schemas.ts +0 -104
  178. package/src/modules/catalog/service.ts +0 -1544
  179. package/src/modules/customers/repository/index.ts +0 -327
  180. package/src/modules/customers/schema.ts +0 -64
  181. package/src/modules/customers/service.ts +0 -171
  182. package/src/modules/fulfillment/repository/index.ts +0 -426
  183. package/src/modules/fulfillment/schema.ts +0 -101
  184. package/src/modules/fulfillment/service.ts +0 -555
  185. package/src/modules/fulfillment/types.ts +0 -59
  186. package/src/modules/inventory/repository/index.ts +0 -509
  187. package/src/modules/inventory/schema.ts +0 -94
  188. package/src/modules/inventory/schemas.ts +0 -38
  189. package/src/modules/inventory/service.ts +0 -490
  190. package/src/modules/media/adapter.ts +0 -17
  191. package/src/modules/media/repository/index.ts +0 -274
  192. package/src/modules/media/schema.ts +0 -41
  193. package/src/modules/media/service.ts +0 -151
  194. package/src/modules/orders/repository/index.ts +0 -287
  195. package/src/modules/orders/schema.ts +0 -66
  196. package/src/modules/orders/service.ts +0 -619
  197. package/src/modules/orders/stale-order-cleanup.ts +0 -76
  198. package/src/modules/organization/service.ts +0 -191
  199. package/src/modules/payments/adapter.ts +0 -47
  200. package/src/modules/payments/repository/index.ts +0 -6
  201. package/src/modules/payments/service.ts +0 -107
  202. package/src/modules/pricing/repository/index.ts +0 -291
  203. package/src/modules/pricing/schema.ts +0 -71
  204. package/src/modules/pricing/schemas.ts +0 -38
  205. package/src/modules/pricing/service.ts +0 -494
  206. package/src/modules/promotions/repository/index.ts +0 -325
  207. package/src/modules/promotions/schema.ts +0 -62
  208. package/src/modules/promotions/schemas.ts +0 -38
  209. package/src/modules/promotions/service.ts +0 -598
  210. package/src/modules/search/adapter.ts +0 -57
  211. package/src/modules/search/hooks.ts +0 -12
  212. package/src/modules/search/repository/index.ts +0 -6
  213. package/src/modules/search/service.ts +0 -315
  214. package/src/modules/shipping/calculator.ts +0 -188
  215. package/src/modules/shipping/repository/index.ts +0 -6
  216. package/src/modules/shipping/service.ts +0 -51
  217. package/src/modules/tax/adapter.ts +0 -60
  218. package/src/modules/tax/repository/index.ts +0 -6
  219. package/src/modules/tax/service.ts +0 -53
  220. package/src/modules/webhooks/hook.ts +0 -34
  221. package/src/modules/webhooks/repository/index.ts +0 -278
  222. package/src/modules/webhooks/schema.ts +0 -56
  223. package/src/modules/webhooks/service.ts +0 -117
  224. package/src/modules/webhooks/signing.ts +0 -6
  225. package/src/modules/webhooks/ssrf-guard.ts +0 -71
  226. package/src/modules/webhooks/tasks.ts +0 -52
  227. package/src/modules/webhooks/worker.ts +0 -134
  228. package/src/runtime/commerce.ts +0 -145
  229. package/src/runtime/kernel.ts +0 -419
  230. package/src/runtime/logger.ts +0 -36
  231. package/src/runtime/server.ts +0 -349
  232. package/src/runtime/shutdown.ts +0 -43
  233. package/src/test-utils/create-pglite-adapter.ts +0 -129
  234. package/src/test-utils/create-plugin-test-app.ts +0 -128
  235. package/src/test-utils/create-repository-test-harness.ts +0 -16
  236. package/src/test-utils/create-test-config.ts +0 -190
  237. package/src/test-utils/create-test-kernel.ts +0 -7
  238. package/src/test-utils/create-test-plugin-context.ts +0 -75
  239. package/src/test-utils/rest-api-test-utils.ts +0 -265
  240. package/src/test-utils/test-actors.ts +0 -62
  241. package/src/test-utils/typed-hooks.ts +0 -54
  242. package/src/types/commerce-types.ts +0 -34
  243. package/src/utils/id.ts +0 -3
  244. package/src/utils/logger.ts +0 -18
  245. package/src/utils/pagination.ts +0 -22
@@ -1,134 +0,0 @@
1
- import { signWebhookPayload } from "./signing.js";
2
- import { isPrivateIp } from "./ssrf-guard.js";
3
- import type { WebhooksRepository } from "./repository/index.js";
4
-
5
- /**
6
- * SSRF prevention: reject webhook URLs targeting private/internal hosts.
7
- * Blocks RFC 1918, loopback, link-local, and common internal domains.
8
- */
9
- const PRIVATE_HOST_PATTERNS = [
10
- /^127\./, // loopback
11
- /^10\./, // RFC 1918 class A
12
- /^172\.(1[6-9]|2[0-9]|3[01])\./, // RFC 1918 class B
13
- /^192\.168\./, // RFC 1918 class C
14
- /^169\.254\./, // link-local / AWS IMDS
15
- /^0\.0\.0\.0/, // unspecified
16
- /^localhost$/i,
17
- /\.local$/i,
18
- /\.internal$/i,
19
- ];
20
-
21
- function validateWebhookUrl(url: string): void {
22
- const parsed = new URL(url);
23
-
24
- if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
25
- throw new Error("Webhook URLs must use HTTPS in production.");
26
- }
27
-
28
- const hostname = parsed.hostname;
29
- for (const pattern of PRIVATE_HOST_PATTERNS) {
30
- if (pattern.test(hostname)) {
31
- throw new Error(`Webhook URLs cannot target private hosts: ${hostname}`);
32
- }
33
- }
34
- }
35
-
36
- /**
37
- * Resolve the webhook URL hostname via DNS and verify the resolved IP is not
38
- * private. This closes the DNS rebinding gap where a hostname initially
39
- * resolves to a public IP but later rebinds to an internal address.
40
- */
41
- async function validateResolvedIp(url: string): Promise<void> {
42
- const { lookup } = await import("node:dns/promises");
43
- const parsed = new URL(url);
44
- const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
45
-
46
- // Skip DNS resolution for raw IP addresses — they are already checked
47
- // by validateWebhookUrl via PRIVATE_HOST_PATTERNS.
48
- const isIpLiteral = /^[\d.]+$/.test(hostname) || hostname.includes(":");
49
- if (isIpLiteral) return;
50
-
51
- try {
52
- const { address } = await lookup(hostname);
53
- if (isPrivateIp(address)) {
54
- throw new Error(
55
- `Webhook URL hostname "${hostname}" resolved to private IP ${address}`,
56
- );
57
- }
58
- } catch (err) {
59
- if (err instanceof Error && err.message.includes("resolved to private")) {
60
- throw err;
61
- }
62
- throw new Error(`Failed to resolve webhook URL hostname "${hostname}": ${err}`);
63
- }
64
- }
65
-
66
- interface WorkerDeps {
67
- repository: WebhooksRepository;
68
- fetchImpl?: typeof fetch;
69
- }
70
-
71
- export class WebhookDeliveryWorker {
72
- private fetchImpl: typeof fetch;
73
-
74
- constructor(private deps: WorkerDeps) {
75
- this.fetchImpl = deps.fetchImpl ?? fetch;
76
- }
77
-
78
- async deliver(args: {
79
- endpoint: { id: string; url: string; secret: string };
80
- eventName: string;
81
- payload: unknown;
82
- }): Promise<void> {
83
- // SSRF prevention: validate URL string patterns + resolved DNS IP
84
- validateWebhookUrl(args.endpoint.url);
85
- await validateResolvedIp(args.endpoint.url);
86
-
87
- let attempt = 0;
88
- const maxAttempts = 3;
89
-
90
- while (attempt < maxAttempts) {
91
- attempt += 1;
92
- const signature = signWebhookPayload(args.endpoint.secret, args.payload);
93
-
94
- try {
95
- const response = await this.fetchImpl(args.endpoint.url, {
96
- method: "POST",
97
- headers: {
98
- "content-type": "application/json",
99
- "x-commerce-signature": signature,
100
- "x-commerce-event": args.eventName,
101
- },
102
- body: JSON.stringify(args.payload),
103
- signal: AbortSignal.timeout(10_000),
104
- });
105
-
106
- await this.deps.repository.createDelivery({
107
- endpointId: args.endpoint.id,
108
- eventName: args.eventName,
109
- payload: args.payload,
110
- statusCode: response.status,
111
- attemptCount: attempt,
112
- ...(response.ok ? { deliveredAt: new Date() } : {}),
113
- ...(!response.ok ? { failedAt: new Date() } : {}),
114
- ...(!response.ok && attempt < maxAttempts
115
- ? { nextRetryAt: new Date(Date.now() + 2 ** attempt * 1000) }
116
- : {}),
117
- });
118
-
119
- if (response.ok) return;
120
- } catch {
121
- await this.deps.repository.createDelivery({
122
- endpointId: args.endpoint.id,
123
- eventName: args.eventName,
124
- payload: args.payload,
125
- attemptCount: attempt,
126
- failedAt: new Date(),
127
- ...(attempt < maxAttempts
128
- ? { nextRetryAt: new Date(Date.now() + 2 ** attempt * 1000) }
129
- : {}),
130
- });
131
- }
132
- }
133
- }
134
- }
@@ -1,145 +0,0 @@
1
- import type { Actor } from "../auth/types.js";
2
- import type { CommerceConfig } from "../config/types.js";
3
- import type { Kernel } from "./kernel.js";
4
- import { createKernel } from "./kernel.js";
5
- import { ensureDefaultOrg } from "../auth/org.js";
6
- import { createAuth, type AuthInstance } from "../auth/setup.js";
7
- import { createLogger, type Logger } from "./logger.js";
8
- import { createLocalAPI, type CommerceLocalAPI, type LocalAPIOptions } from "../kernel/local-api.js";
9
-
10
- /**
11
- * The commerce instance returned by `createCommerce()`.
12
- *
13
- * This is the headless, framework-agnostic entry point.
14
- * No HTTP server, no Hono — just typed services and a local API.
15
- *
16
- * ## Usage with Next.js App Router:
17
- *
18
- * ```typescript
19
- * // lib/commerce.ts
20
- * import { createCommerce } from "@unifiedcommerce/core";
21
- * import config from "../commerce.config.js";
22
- *
23
- * export const commerce = await createCommerce(config);
24
- *
25
- * // app/products/page.tsx (Server Component)
26
- * import { commerce } from "@/lib/commerce";
27
- *
28
- * export default async function ProductsPage() {
29
- * const products = await commerce.api.catalog.list({ limit: 20 });
30
- * if (!products.ok) return <div>Error</div>;
31
- * return <ProductGrid items={products.value.items} />;
32
- * }
33
- * ```
34
- *
35
- * ## Usage with TanStack Start:
36
- *
37
- * ```typescript
38
- * // app/routes/products.tsx
39
- * import { createServerFn } from "@tanstack/start";
40
- * import { commerce } from "../lib/commerce.js";
41
- *
42
- * const getProducts = createServerFn("GET", async () => {
43
- * return commerce.api.catalog.list({ limit: 20 });
44
- * });
45
- * ```
46
- *
47
- * ## Usage with SvelteKit:
48
- *
49
- * ```typescript
50
- * // src/lib/server/commerce.ts
51
- * import { createCommerce } from "@unifiedcommerce/core";
52
- * import config from "./commerce.config.js";
53
- * export const commerce = await createCommerce(config);
54
- *
55
- * // src/routes/products/+page.server.ts
56
- * import { commerce } from "$lib/server/commerce";
57
- * export async function load() {
58
- * const products = await commerce.api.catalog.list({ limit: 20 });
59
- * return { products: products.ok ? products.value : { items: [] } };
60
- * }
61
- * ```
62
- */
63
- export interface CommerceInstance {
64
- /** Proxy-based local API — auto-injects actor/tx to every service call */
65
- api: CommerceLocalAPI;
66
-
67
- /** Raw kernel for advanced usage (hooks, database, config) */
68
- kernel: Kernel;
69
-
70
- /** Drizzle database instance for direct queries */
71
- db: unknown;
72
-
73
- /** Auth instance (Better Auth) for session management */
74
- auth: AuthInstance;
75
-
76
- /** Logger */
77
- logger: Logger;
78
-
79
- /**
80
- * Create a scoped API for a specific user/actor.
81
- * Use this in authenticated routes to scope data access.
82
- *
83
- * ```typescript
84
- * // In a Next.js server action:
85
- * const userApi = commerce.withActor({
86
- * type: "user", userId: session.userId, ...
87
- * });
88
- * const orders = await userApi.orders.list({ limit: 10 });
89
- * // Only returns orders for this user's org
90
- * ```
91
- */
92
- withActor(actor: Actor): CommerceLocalAPI;
93
-
94
- /**
95
- * Create a scoped API within a database transaction.
96
- *
97
- * ```typescript
98
- * await commerce.kernel.database.transaction(async (tx) => {
99
- * const txApi = commerce.withTransaction(tx, actor);
100
- * await txApi.inventory.adjust({ entityId, adjustment: -1, reason: "sold" });
101
- * await txApi.orders.create({ ... });
102
- * });
103
- * ```
104
- */
105
- withTransaction(tx: unknown, actor?: Actor | null): CommerceLocalAPI;
106
- }
107
-
108
- /**
109
- * Create a headless commerce instance.
110
- *
111
- * This is the primary entry point for using UnifiedCommerce without an HTTP server.
112
- * It initializes the kernel, database, auth, and returns a local API that works
113
- * exactly like the REST API but without HTTP overhead.
114
- *
115
- * The Hono server (`createServer`) is optional — use it only when you need
116
- * a standalone HTTP API. For Next.js, TanStack Start, SvelteKit, Nuxt, etc.,
117
- * use `createCommerce()` directly.
118
- */
119
- export async function createCommerce(
120
- config: CommerceConfig,
121
- ): Promise<CommerceInstance> {
122
- const kernel = createKernel(config);
123
- await ensureDefaultOrg(kernel.database.db);
124
- const auth = createAuth(kernel.database, config);
125
- const logger = createLogger(config);
126
-
127
- // Default API: no actor (public access), no transaction
128
- const api = createLocalAPI(kernel);
129
-
130
- return {
131
- api,
132
- kernel,
133
- db: kernel.database.db,
134
- auth,
135
- logger,
136
-
137
- withActor(actor: Actor): CommerceLocalAPI {
138
- return createLocalAPI(kernel, { actor });
139
- },
140
-
141
- withTransaction(tx: unknown, actor?: Actor | null): CommerceLocalAPI {
142
- return createLocalAPI(kernel, { actor: actor ?? null, tx });
143
- },
144
- };
145
- }
@@ -1,419 +0,0 @@
1
- import type {
2
- CommerceConfig,
3
- MCPResource,
4
- MCPTool,
5
- } from "../config/types.js";
6
- import { HookRegistry, type HookHandler } from "../kernel/hooks/registry.js";
7
- import {
8
- createDatabaseConnection,
9
- type DatabaseAdapter,
10
- } from "../kernel/database/adapter.js";
11
- import type { PluginDb } from "../kernel/database/plugin-types.js";
12
-
13
- import { CatalogServiceImpl } from "../modules/catalog/service.js";
14
- import { CatalogRepository } from "../modules/catalog/repository/index.js";
15
- import { InventoryRepository } from "../modules/inventory/repository/index.js";
16
- import { CartRepository } from "../modules/cart/repository/index.js";
17
- import { OrdersRepository } from "../modules/orders/repository/index.js";
18
- import { CustomersRepository } from "../modules/customers/repository/index.js";
19
- import { PricingRepository } from "../modules/pricing/repository/index.js";
20
- import { PromotionsRepository } from "../modules/promotions/repository/index.js";
21
- import { FulfillmentRepository } from "../modules/fulfillment/repository/index.js";
22
- import { WebhooksRepository } from "../modules/webhooks/repository/index.js";
23
- import { MediaRepository } from "../modules/media/repository/index.js";
24
- import type { DrizzleDatabase } from "../kernel/database/drizzle-db.js";
25
- import { InventoryService } from "../modules/inventory/service.js";
26
- import { MediaService } from "../modules/media/service.js";
27
- import { CartService } from "../modules/cart/service.js";
28
- import { OrderService } from "../modules/orders/service.js";
29
- import { PaymentsService } from "../modules/payments/service.js";
30
- import { FulfillmentService } from "../modules/fulfillment/service.js";
31
- import { CustomerService } from "../modules/customers/service.js";
32
- import { WebhookService } from "../modules/webhooks/service.js";
33
- import { AnalyticsService } from "../modules/analytics/service.js";
34
- import { DrizzleAnalyticsAdapter } from "../modules/analytics/drizzle-adapter.js";
35
- import { BUILTIN_ANALYTICS_MODELS } from "../modules/analytics/models.js";
36
- import { PricingService } from "../modules/pricing/service.js";
37
- import { PromotionService } from "../modules/promotions/service.js";
38
- import { TaxService } from "../modules/tax/service.js";
39
- import { ShippingService } from "../modules/shipping/service.js";
40
- import { SearchService } from "../modules/search/service.js";
41
- import { WebhookDeliveryWorker } from "../modules/webhooks/worker.js";
42
- import {
43
- createAuditService,
44
- type AuditService,
45
- } from "../modules/audit/service.js";
46
- import { OrganizationService } from "../modules/organization/service.js";
47
- import { createLogger } from "../utils/logger.js";
48
- import { withTiming } from "../kernel/service-timing.js";
49
- import { extendOrderStateMachine } from "../kernel/state-machine/machine.js";
50
- import { DEFAULT_ORG_ID } from "../auth/org.js";
51
- import { deliverWebhooks } from "../modules/webhooks/hook.js";
52
- // Analytics event recording hooks removed (RFC-006): source tables ARE the events.
53
- // The DrizzleAnalyticsAdapter queries orders/inventory directly via SQL.
54
- import { syncToSearchIndex } from "../modules/search/hooks.js";
55
- import { auditHooks } from "../modules/audit/hooks.js";
56
- import { sendOrderStatusEmail } from "../hooks/order-emails.js";
57
- import { DrizzleJobsAdapter } from "../kernel/jobs/drizzle-adapter.js";
58
-
59
- export interface WebhookDeliveryPayload {
60
- endpoint: { id: string; url: string; secret: string };
61
- eventName: string;
62
- payload: unknown;
63
- }
64
-
65
- export interface Kernel {
66
- config: CommerceConfig;
67
- hooks: HookRegistry;
68
- database: DatabaseAdapter;
69
- services: {
70
- catalog: CatalogServiceImpl;
71
- inventory: InventoryService;
72
- media: MediaService;
73
- cart: CartService;
74
- orders: OrderService;
75
- payments: PaymentsService;
76
- fulfillment: FulfillmentService;
77
- customers: CustomerService;
78
- webhooks: WebhookService & {
79
- enqueueDelivery(payload: WebhookDeliveryPayload): Promise<void>;
80
- };
81
- analytics: AnalyticsService;
82
- pricing: PricingService;
83
- promotions: PromotionService;
84
- tax: TaxService;
85
- shipping: ShippingService;
86
- search: SearchService;
87
- audit: AuditService;
88
- email: CommerceConfig["email"];
89
- organization: OrganizationService;
90
- };
91
- mcpTools: MCPTool[];
92
- mcpResources: MCPResource[];
93
- logger: ReturnType<typeof createLogger>;
94
- getMCPActor(): {
95
- type: "api_key";
96
- userId: string;
97
- email: null;
98
- name: string;
99
- vendorId: null;
100
- organizationId: string;
101
- role: string;
102
- permissions: string[];
103
- };
104
- }
105
-
106
- const requiredServiceKeys = [
107
- "catalog",
108
- "inventory",
109
- "media",
110
- "cart",
111
- "orders",
112
- "payments",
113
- "fulfillment",
114
- "customers",
115
- "webhooks",
116
- "analytics",
117
- "pricing",
118
- "promotions",
119
- "tax",
120
- "shipping",
121
- "search",
122
- "audit",
123
- ] as const satisfies Array<keyof Kernel["services"]>;
124
-
125
- function assertServicesReady(
126
- services: Partial<Kernel["services"]>,
127
- ): asserts services is Kernel["services"] {
128
- for (const key of requiredServiceKeys) {
129
- if (services[key] === undefined) {
130
- throw new Error(`Kernel service "${String(key)}" was not initialized.`);
131
- }
132
- }
133
- }
134
-
135
- function registerConfiguredHooks(
136
- config: CommerceConfig,
137
- hooks: HookRegistry,
138
- ): void {
139
- for (const [entityType, entityConfig] of Object.entries(
140
- config.entities ?? {},
141
- )) {
142
- const entityHooks = entityConfig.hooks ?? {};
143
- for (const [hookName, handlers] of Object.entries(entityHooks)) {
144
- hooks.registerConfigHooks(
145
- `catalog.${entityType}.${hookName}`,
146
- handlers ?? [],
147
- );
148
- }
149
- }
150
-
151
- for (const [moduleName, moduleConfig] of [
152
- ["cart", config.cart],
153
- ["checkout", config.checkout],
154
- ["orders", config.orders],
155
- ["inventory", config.inventory],
156
- ] as const) {
157
- const hooksObject = moduleConfig?.hooks;
158
- if (!hooksObject) continue;
159
- for (const [hookName, handlers] of Object.entries(hooksObject)) {
160
- const normalizedHandlers: HookHandler[] = Array.isArray(handlers)
161
- ? (handlers as unknown as HookHandler[])
162
- : [];
163
- hooks.registerConfigHooks(
164
- `${moduleName}.${hookName}`,
165
- normalizedHandlers,
166
- );
167
- }
168
- }
169
-
170
- // Webhook delivery (async via job queue) — 14 event types
171
- hooks.append("orders.afterCreate", deliverWebhooks);
172
- hooks.append("orders.afterStatusChange", deliverWebhooks);
173
- hooks.append("orders.afterStatusChange", sendOrderStatusEmail as (...args: unknown[]) => unknown);
174
- hooks.append("catalog.afterCreate", deliverWebhooks);
175
- hooks.append("catalog.afterUpdate", deliverWebhooks);
176
- hooks.append("catalog.afterDelete", deliverWebhooks);
177
- hooks.append("inventory.afterAdjust", deliverWebhooks);
178
- hooks.append("customers.afterCreate", deliverWebhooks);
179
- hooks.append("customers.afterUpdate", deliverWebhooks);
180
- hooks.append("pricing.afterCreate", deliverWebhooks);
181
- hooks.append("pricing.afterUpdate", deliverWebhooks);
182
- hooks.append("promotions.afterCreate", deliverWebhooks);
183
- hooks.append("promotions.afterUpdate", deliverWebhooks);
184
- hooks.append("fulfillment.afterCreate", deliverWebhooks);
185
- hooks.append("cart.afterAddItem", deliverWebhooks);
186
-
187
- // Analytics: no event recording hooks needed (RFC-006).
188
- // The DrizzleAnalyticsAdapter queries source tables directly via SQL.
189
-
190
- // Search index sync
191
- hooks.append("catalog.afterCreate", syncToSearchIndex);
192
- hooks.append("catalog.afterUpdate", syncToSearchIndex);
193
-
194
- // Auto-audit — records every create/update/delete across all modules
195
- for (const [key, handler] of Object.entries(auditHooks)) {
196
- hooks.append(key, handler);
197
- }
198
- }
199
-
200
- export function createKernel(config: CommerceConfig): Kernel {
201
- const hooks = new HookRegistry();
202
- const logger = createLogger("kernel");
203
- hooks.setLogger(logger as unknown as { error: (obj: Record<string, unknown>, msg: string) => void });
204
-
205
- if (!config.storage) {
206
- throw new Error(
207
- "Storage adapter is required. Configure `storage` in defineConfig (for example: localStorageAdapter for development, or s3StorageAdapter/r2StorageAdapter for object storage).",
208
- );
209
- }
210
-
211
- const database = createDatabaseConnection({
212
- adapter: config.databaseAdapter ?? {
213
- provider: config.database.provider,
214
- db: {},
215
- async transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T> {
216
- return fn({});
217
- },
218
- },
219
- });
220
- const mcpTools: MCPTool[] = [];
221
- const mcpResources: MCPResource[] = [];
222
-
223
- const services: Partial<Kernel["services"]> = {
224
- email: config.email,
225
- organization: new OrganizationService(database.db),
226
- };
227
-
228
- const serviceContainer = services as Record<string, unknown>;
229
-
230
- // Expose database on service container so plugin hooks can access it
231
- serviceContainer.database = database;
232
-
233
- const db = database.db as DrizzleDatabase;
234
-
235
- // Expose jobs adapter on service container so plugins can enqueue background work
236
- const jobsAdapter = new DrizzleJobsAdapter(db, new Map());
237
- serviceContainer.jobs = jobsAdapter;
238
-
239
- const pricingRepository = new PricingRepository(db);
240
- const promotionsRepository = new PromotionsRepository(db);
241
-
242
- services.tax = new TaxService({ adapter: config.tax?.adapter });
243
- services.payments = new PaymentsService(config.payments);
244
-
245
- const customersRepository = new CustomersRepository(db);
246
- services.customers = new CustomerService({
247
- repository: customersRepository,
248
- });
249
-
250
- const webhooksRepository = new WebhooksRepository(db);
251
- const webhookWorker = new WebhookDeliveryWorker({
252
- repository: webhooksRepository,
253
- });
254
- services.webhooks = Object.assign(
255
- new WebhookService({
256
- repository: webhooksRepository,
257
- }),
258
- {
259
- async enqueueDelivery(payload: WebhookDeliveryPayload) {
260
- await webhookWorker.deliver(payload);
261
- },
262
- },
263
- );
264
-
265
- const inventoryRepository = new InventoryRepository(db);
266
- services.inventory = new InventoryService({
267
- repository: inventoryRepository,
268
- hooks,
269
- config,
270
- services: serviceContainer,
271
- database,
272
- });
273
-
274
- const catalogRepository = new CatalogRepository(db);
275
- services.catalog = new CatalogServiceImpl({
276
- repository: catalogRepository,
277
- hooks,
278
- config,
279
- services: serviceContainer,
280
- });
281
-
282
- services.search = new SearchService({
283
- catalogRepository: catalogRepository,
284
- ...(config.search?.adapter ? { adapter: config.search.adapter } : {}),
285
- ...(config.search?.defaultFacets
286
- ? { defaultFacets: config.search.defaultFacets }
287
- : {}),
288
- });
289
-
290
- const cartRepository = new CartRepository(db);
291
- services.cart = new CartService({
292
- repository: cartRepository,
293
- catalogRepository: catalogRepository,
294
- hooks,
295
- config,
296
- services: serviceContainer,
297
- });
298
-
299
- const ordersRepository = new OrdersRepository(db);
300
- services.orders = new OrderService({
301
- repository: ordersRepository,
302
- hooks,
303
- services: serviceContainer,
304
- kernel: { database: database as { db: PluginDb } },
305
- ...(config.orders?.customTransitions
306
- ? { stateMachine: extendOrderStateMachine(config.orders.customTransitions) }
307
- : {}),
308
- });
309
-
310
- const fulfillmentRepository = new FulfillmentRepository(db);
311
- services.fulfillment = new FulfillmentService({
312
- repository: fulfillmentRepository,
313
- ordersRepository: ordersRepository,
314
- inventoryService: services.inventory,
315
- });
316
-
317
- services.pricing = new PricingService({
318
- repository: pricingRepository,
319
- catalogRepository: catalogRepository,
320
- });
321
-
322
- services.promotions = new PromotionService({
323
- repository: promotionsRepository,
324
- catalogRepository: catalogRepository,
325
- ordersRepository: ordersRepository,
326
- });
327
-
328
- // AnalyticsService — always uses DrizzleAnalyticsAdapter (direct SQL).
329
- // Plugins add their own models via the analyticsModels manifest slot.
330
- const analyticsAdapter = new DrizzleAnalyticsAdapter(db);
331
- for (const model of BUILTIN_ANALYTICS_MODELS) {
332
- analyticsAdapter.registerModel(model);
333
- }
334
- services.analytics = new AnalyticsService({
335
- adapter: analyticsAdapter,
336
- config,
337
- });
338
-
339
- services.shipping = new ShippingService({
340
- config,
341
- catalogRepository: catalogRepository,
342
- });
343
-
344
- const mediaRepository = new MediaRepository(db);
345
- services.media = new MediaService({
346
- repository: mediaRepository,
347
- catalogRepository: catalogRepository,
348
- storage: config.storage,
349
- });
350
-
351
- services.audit = createAuditService(db);
352
-
353
- assertServicesReady(services);
354
-
355
- // Service method observability: wrap each service in a timing proxy
356
- // that logs slow calls (>100ms) and failed calls with duration.
357
- // Disabled in test environment to avoid noisy logs.
358
- if (process.env.NODE_ENV !== "test") {
359
- const timedLogger = logger as unknown as { info: (o: Record<string, unknown>, m: string) => void; error: (o: Record<string, unknown>, m: string) => void };
360
- const serviceKeys = Object.keys(services) as Array<keyof typeof services>;
361
- for (const key of serviceKeys) {
362
- const svc = services[key];
363
- if (svc && typeof svc === "object" && key !== "email") {
364
- (services as Record<string, unknown>)[key] = withTiming(
365
- svc as object,
366
- key,
367
- timedLogger,
368
- );
369
- }
370
- }
371
- }
372
-
373
- registerConfiguredHooks(config, hooks);
374
-
375
- const kernel: Kernel = {
376
- config,
377
- hooks,
378
- database,
379
- services,
380
- mcpTools,
381
- mcpResources,
382
- logger,
383
- getMCPActor() {
384
- return {
385
- type: "api_key",
386
- userId: "mcp-agent",
387
- email: null,
388
- name: "MCP Agent",
389
- vendorId: null,
390
- organizationId: DEFAULT_ORG_ID,
391
- role: "ai_agent",
392
- permissions: config.auth?.roles?.ai_agent?.permissions ?? [
393
- "catalog:read",
394
- "catalog:create",
395
- "inventory:read",
396
- "inventory:adjust",
397
- "orders:read",
398
- "cart:create",
399
- "cart:update",
400
- "mcp:access",
401
- ],
402
- };
403
- },
404
- };
405
-
406
- // Register plugin hooks from config.hooks flat map
407
- for (const [key, handlers] of Object.entries(config.hooks ?? {})) {
408
- for (const handler of handlers) {
409
- hooks.append(key, handler as HookHandler);
410
- }
411
- }
412
-
413
- // Register plugin analytics models from config.analytics.models
414
- for (const model of config.analytics?.models ?? []) {
415
- services.analytics.registerModel(model);
416
- }
417
-
418
- return kernel;
419
- }