@unifiedcommerce/core 0.0.4 → 0.1.1

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 (179) hide show
  1. package/dist/auth/auth-schema.d.ts +92 -0
  2. package/dist/auth/auth-schema.d.ts.map +1 -1
  3. package/dist/auth/auth-schema.js +7 -0
  4. package/dist/auth/setup.d.ts.map +1 -1
  5. package/dist/auth/setup.js +3 -1
  6. package/package.json +1 -2
  7. package/src/adapters/console-email.ts +0 -43
  8. package/src/auth/access.ts +0 -187
  9. package/src/auth/auth-schema.ts +0 -131
  10. package/src/auth/middleware.ts +0 -161
  11. package/src/auth/org.ts +0 -41
  12. package/src/auth/permissions.ts +0 -28
  13. package/src/auth/setup.ts +0 -165
  14. package/src/auth/system-actor.ts +0 -19
  15. package/src/auth/types.ts +0 -10
  16. package/src/config/defaults.ts +0 -82
  17. package/src/config/define-config.ts +0 -53
  18. package/src/config/types.ts +0 -299
  19. package/src/generated/plugin-capabilities.d.ts +0 -20
  20. package/src/generated/plugin-manifest.ts +0 -23
  21. package/src/generated/plugin-repositories.d.ts +0 -20
  22. package/src/hooks/checkout-completion.ts +0 -262
  23. package/src/hooks/checkout.ts +0 -677
  24. package/src/hooks/order-emails.ts +0 -62
  25. package/src/index.ts +0 -214
  26. package/src/interfaces/mcp/agent-prompt.ts +0 -174
  27. package/src/interfaces/mcp/context-enrichment.ts +0 -177
  28. package/src/interfaces/mcp/server.ts +0 -617
  29. package/src/interfaces/mcp/transport.ts +0 -68
  30. package/src/interfaces/rest/customer-portal.ts +0 -299
  31. package/src/interfaces/rest/index.ts +0 -74
  32. package/src/interfaces/rest/router.ts +0 -334
  33. package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
  34. package/src/interfaces/rest/routes/audit.ts +0 -50
  35. package/src/interfaces/rest/routes/carts.ts +0 -89
  36. package/src/interfaces/rest/routes/catalog.ts +0 -493
  37. package/src/interfaces/rest/routes/checkout.ts +0 -283
  38. package/src/interfaces/rest/routes/inventory.ts +0 -70
  39. package/src/interfaces/rest/routes/media.ts +0 -86
  40. package/src/interfaces/rest/routes/orders.ts +0 -78
  41. package/src/interfaces/rest/routes/payments.ts +0 -60
  42. package/src/interfaces/rest/routes/pricing.ts +0 -57
  43. package/src/interfaces/rest/routes/promotions.ts +0 -92
  44. package/src/interfaces/rest/routes/search.ts +0 -71
  45. package/src/interfaces/rest/routes/webhooks.ts +0 -46
  46. package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
  47. package/src/interfaces/rest/schemas/audit.ts +0 -46
  48. package/src/interfaces/rest/schemas/carts.ts +0 -125
  49. package/src/interfaces/rest/schemas/catalog.ts +0 -450
  50. package/src/interfaces/rest/schemas/checkout.ts +0 -66
  51. package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
  52. package/src/interfaces/rest/schemas/inventory.ts +0 -138
  53. package/src/interfaces/rest/schemas/media.ts +0 -75
  54. package/src/interfaces/rest/schemas/orders.ts +0 -104
  55. package/src/interfaces/rest/schemas/pricing.ts +0 -80
  56. package/src/interfaces/rest/schemas/promotions.ts +0 -110
  57. package/src/interfaces/rest/schemas/responses.ts +0 -85
  58. package/src/interfaces/rest/schemas/search.ts +0 -58
  59. package/src/interfaces/rest/schemas/shared.ts +0 -62
  60. package/src/interfaces/rest/schemas/webhooks.ts +0 -68
  61. package/src/interfaces/rest/utils.ts +0 -104
  62. package/src/interfaces/rest/webhook-router.ts +0 -50
  63. package/src/kernel/compensation/executor.ts +0 -61
  64. package/src/kernel/compensation/types.ts +0 -26
  65. package/src/kernel/database/adapter.ts +0 -13
  66. package/src/kernel/database/drizzle-db.ts +0 -56
  67. package/src/kernel/database/migrate.ts +0 -76
  68. package/src/kernel/database/plugin-types.ts +0 -34
  69. package/src/kernel/database/schema.ts +0 -49
  70. package/src/kernel/database/scoped-db.ts +0 -68
  71. package/src/kernel/database/tx-context.ts +0 -46
  72. package/src/kernel/error-mapper.ts +0 -15
  73. package/src/kernel/errors.ts +0 -89
  74. package/src/kernel/factory/repository-factory.ts +0 -242
  75. package/src/kernel/hooks/create-context.ts +0 -43
  76. package/src/kernel/hooks/executor.ts +0 -88
  77. package/src/kernel/hooks/registry.ts +0 -74
  78. package/src/kernel/hooks/types.ts +0 -52
  79. package/src/kernel/http-error.ts +0 -44
  80. package/src/kernel/jobs/adapter.ts +0 -36
  81. package/src/kernel/jobs/drizzle-adapter.ts +0 -58
  82. package/src/kernel/jobs/runner.ts +0 -153
  83. package/src/kernel/jobs/schema.ts +0 -46
  84. package/src/kernel/jobs/types.ts +0 -30
  85. package/src/kernel/local-api.ts +0 -185
  86. package/src/kernel/plugin/manifest.ts +0 -253
  87. package/src/kernel/query/executor.ts +0 -184
  88. package/src/kernel/query/registry.ts +0 -46
  89. package/src/kernel/result.ts +0 -33
  90. package/src/kernel/schema/extra-columns.ts +0 -37
  91. package/src/kernel/service-registry.ts +0 -76
  92. package/src/kernel/service-timing.ts +0 -89
  93. package/src/kernel/state-machine/machine.ts +0 -101
  94. package/src/modules/analytics/drizzle-adapter.ts +0 -426
  95. package/src/modules/analytics/hooks.ts +0 -11
  96. package/src/modules/analytics/models.ts +0 -125
  97. package/src/modules/analytics/repository/index.ts +0 -6
  98. package/src/modules/analytics/service.ts +0 -245
  99. package/src/modules/analytics/types.ts +0 -180
  100. package/src/modules/audit/hooks.ts +0 -78
  101. package/src/modules/audit/schema.ts +0 -33
  102. package/src/modules/audit/service.ts +0 -151
  103. package/src/modules/cart/access.ts +0 -27
  104. package/src/modules/cart/matcher.ts +0 -26
  105. package/src/modules/cart/repository/index.ts +0 -234
  106. package/src/modules/cart/schema.ts +0 -42
  107. package/src/modules/cart/schemas.ts +0 -38
  108. package/src/modules/cart/service.ts +0 -541
  109. package/src/modules/catalog/repository/index.ts +0 -772
  110. package/src/modules/catalog/schema.ts +0 -203
  111. package/src/modules/catalog/schemas.ts +0 -104
  112. package/src/modules/catalog/service.ts +0 -1544
  113. package/src/modules/customers/repository/index.ts +0 -327
  114. package/src/modules/customers/schema.ts +0 -64
  115. package/src/modules/customers/service.ts +0 -171
  116. package/src/modules/fulfillment/repository/index.ts +0 -426
  117. package/src/modules/fulfillment/schema.ts +0 -101
  118. package/src/modules/fulfillment/service.ts +0 -555
  119. package/src/modules/fulfillment/types.ts +0 -59
  120. package/src/modules/inventory/repository/index.ts +0 -509
  121. package/src/modules/inventory/schema.ts +0 -94
  122. package/src/modules/inventory/schemas.ts +0 -38
  123. package/src/modules/inventory/service.ts +0 -490
  124. package/src/modules/media/adapter.ts +0 -17
  125. package/src/modules/media/repository/index.ts +0 -274
  126. package/src/modules/media/schema.ts +0 -41
  127. package/src/modules/media/service.ts +0 -151
  128. package/src/modules/orders/repository/index.ts +0 -287
  129. package/src/modules/orders/schema.ts +0 -66
  130. package/src/modules/orders/service.ts +0 -619
  131. package/src/modules/orders/stale-order-cleanup.ts +0 -76
  132. package/src/modules/organization/service.ts +0 -191
  133. package/src/modules/payments/adapter.ts +0 -47
  134. package/src/modules/payments/repository/index.ts +0 -6
  135. package/src/modules/payments/service.ts +0 -107
  136. package/src/modules/pricing/repository/index.ts +0 -291
  137. package/src/modules/pricing/schema.ts +0 -71
  138. package/src/modules/pricing/schemas.ts +0 -38
  139. package/src/modules/pricing/service.ts +0 -494
  140. package/src/modules/promotions/repository/index.ts +0 -325
  141. package/src/modules/promotions/schema.ts +0 -62
  142. package/src/modules/promotions/schemas.ts +0 -38
  143. package/src/modules/promotions/service.ts +0 -598
  144. package/src/modules/search/adapter.ts +0 -57
  145. package/src/modules/search/hooks.ts +0 -12
  146. package/src/modules/search/repository/index.ts +0 -6
  147. package/src/modules/search/service.ts +0 -315
  148. package/src/modules/shipping/calculator.ts +0 -188
  149. package/src/modules/shipping/repository/index.ts +0 -6
  150. package/src/modules/shipping/service.ts +0 -51
  151. package/src/modules/tax/adapter.ts +0 -60
  152. package/src/modules/tax/repository/index.ts +0 -6
  153. package/src/modules/tax/service.ts +0 -53
  154. package/src/modules/webhooks/hook.ts +0 -34
  155. package/src/modules/webhooks/repository/index.ts +0 -278
  156. package/src/modules/webhooks/schema.ts +0 -56
  157. package/src/modules/webhooks/service.ts +0 -117
  158. package/src/modules/webhooks/signing.ts +0 -6
  159. package/src/modules/webhooks/ssrf-guard.ts +0 -71
  160. package/src/modules/webhooks/tasks.ts +0 -52
  161. package/src/modules/webhooks/worker.ts +0 -134
  162. package/src/runtime/commerce.ts +0 -145
  163. package/src/runtime/kernel.ts +0 -419
  164. package/src/runtime/logger.ts +0 -36
  165. package/src/runtime/server.ts +0 -349
  166. package/src/runtime/shutdown.ts +0 -43
  167. package/src/test-utils/create-pglite-adapter.ts +0 -129
  168. package/src/test-utils/create-plugin-test-app.ts +0 -128
  169. package/src/test-utils/create-repository-test-harness.ts +0 -16
  170. package/src/test-utils/create-test-config.ts +0 -190
  171. package/src/test-utils/create-test-kernel.ts +0 -7
  172. package/src/test-utils/create-test-plugin-context.ts +0 -75
  173. package/src/test-utils/rest-api-test-utils.ts +0 -265
  174. package/src/test-utils/test-actors.ts +0 -62
  175. package/src/test-utils/typed-hooks.ts +0 -54
  176. package/src/types/commerce-types.ts +0 -34
  177. package/src/utils/id.ts +0 -3
  178. package/src/utils/logger.ts +0 -18
  179. package/src/utils/pagination.ts +0 -22
@@ -1,283 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { Kernel } from "../../../runtime/kernel.js";
3
- import { checkoutRoute } from "../schemas/checkout.js";
4
- import {
5
- applyPromotionCodes,
6
- authorizePayment,
7
- calculateShipping,
8
- calculateTax,
9
- checkInventoryAvailability,
10
- completeCheckout,
11
- recordAnalyticsEvent,
12
- resolveCurrentPrices,
13
- validateCartNotEmpty,
14
- validatePaymentMethod,
15
- type CheckoutData,
16
- type OrderResult,
17
- } from "../../../hooks/checkout.js";
18
- import { runAfterHooks, runBeforeHooks } from "../../../kernel/hooks/executor.js";
19
- import { createHookContext } from "../../../kernel/hooks/create-context.js";
20
- import type { AfterHook, BeforeHook, ServiceContainer } from "../../../kernel/hooks/types.js";
21
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
22
- import { isCommerceError } from "../../../kernel/errors.js";
23
- import { makeId } from "../../../utils/id.js";
24
- import type { ShippingAddress } from "../../../modules/shipping/calculator.js";
25
-
26
- export function checkoutRoutes(kernel: Kernel) {
27
- const router = new OpenAPIHono<AppEnv>();
28
-
29
- // @ts-expect-error -- openapi() enforces strict response typing but our handler
30
- // returns union responses (201 | 400 | 422). The route definition documents the
31
- // contract; the defaultHook handles Zod validation; the handler returns dynamic status.
32
- router.openapi(checkoutRoute, async (c) => {
33
- const body = c.req.valid("json");
34
-
35
- const actor = c.get("actor");
36
- const checkoutData: CheckoutData = {
37
- checkoutId: makeId(),
38
- cartId: body.cartId,
39
- currency: body.currency ?? "USD",
40
- paymentMethodId: body.paymentMethodId,
41
- lineItems: [],
42
- subtotal: 0,
43
- discountTotal: 0,
44
- taxTotal: 0,
45
- shippingTotal: 0,
46
- total: 0,
47
- ...(body.customerId !== undefined ? { customerId: body.customerId } : {}),
48
- ...(body.customerGroupIds !== undefined
49
- ? { customerGroupIds: body.customerGroupIds }
50
- : {}),
51
- ...(body.promotionCodes !== undefined
52
- ? { promotionCodes: body.promotionCodes }
53
- : {}),
54
- ...(body.shippingAddress != null
55
- ? {
56
- shippingAddress: {
57
- line1: body.shippingAddress.line1,
58
- city: body.shippingAddress.city,
59
- postalCode: body.shippingAddress.postalCode,
60
- country: body.shippingAddress.country,
61
- ...(body.shippingAddress.line2 != null ? { line2: body.shippingAddress.line2 } : {}),
62
- ...(body.shippingAddress.state != null ? { state: body.shippingAddress.state } : {}),
63
- },
64
- }
65
- : {}),
66
- };
67
-
68
- // ── Phase 1: Validate & Calculate (inside DB transaction — fast SQL only) ──
69
- const validationHooks: BeforeHook<CheckoutData>[] = [
70
- validateCartNotEmpty,
71
- resolveCurrentPrices,
72
- checkInventoryAvailability,
73
- applyPromotionCodes,
74
- calculateTax,
75
- calculateShipping,
76
- ...(kernel.hooks.resolve("checkout.beforePayment") as BeforeHook<CheckoutData>[]),
77
- validatePaymentMethod,
78
- ];
79
-
80
- // ── Phase 2: Payment Authorization (outside transaction — external API call) ──
81
- const paymentHooks: BeforeHook<CheckoutData>[] = [
82
- authorizePayment,
83
- ...(kernel.hooks.resolve("checkout.beforeCreate") as BeforeHook<CheckoutData>[]),
84
- ];
85
-
86
- const afterHooks: AfterHook<OrderResult>[] = [
87
- completeCheckout,
88
- recordAnalyticsEvent,
89
- ...(kernel.hooks.resolve("checkout.afterCreate") as AfterHook<OrderResult>[]),
90
- ];
91
-
92
- const context = createHookContext({
93
- actor,
94
- logger: kernel.logger,
95
- services: kernel.services as ServiceContainer,
96
- context: { moduleName: "checkout" },
97
- origin: "rest",
98
- kernel: kernel as unknown as { database: { db: import("../../../kernel/database/plugin-types.js").PluginDb } },
99
- });
100
-
101
- try {
102
- // Phase 1: DB transaction for validation — releases connection immediately after
103
- const validated = await kernel.database.transaction(async (_tx) => {
104
- context.tx = _tx;
105
- return runBeforeHooks(
106
- validationHooks,
107
- checkoutData,
108
- "create",
109
- context,
110
- );
111
- });
112
-
113
- // Phase 2: Payment authorization — NO DB connection held while calling Stripe/etc.
114
- // If Stripe takes 5s, the DB connection pool is not affected.
115
- context.tx = null;
116
- const processed = await runBeforeHooks(
117
- paymentHooks,
118
- validated,
119
- "create",
120
- context,
121
- );
122
-
123
- // Resolve customer profile UUID from customerId (may be a profile UUID or a Better Auth user_id)
124
- let customerUuid: string | undefined = undefined;
125
- if (processed.customerId) {
126
- const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
127
- if (uuidRe.test(processed.customerId)) {
128
- // Looks like a profile UUID — try direct lookup (no auto-create)
129
- const byIdResult = await kernel.services.customers.getById(
130
- processed.customerId,
131
- actor,
132
- );
133
- if (byIdResult.ok) {
134
- customerUuid = byIdResult.value.id;
135
- }
136
- }
137
- if (!customerUuid) {
138
- // Fall back to user_id lookup (auto-creates customer profile if needed)
139
- const byUserIdResult = await kernel.services.customers.getByUserId(
140
- processed.customerId,
141
- actor,
142
- );
143
- if (byUserIdResult.ok) {
144
- customerUuid = byUserIdResult.value.id;
145
- }
146
- }
147
- // If both lookups fail, we still allow guest checkout (customerUuid remains undefined)
148
- }
149
-
150
- const orderPayload = {
151
- currency: processed.currency,
152
- subtotal: processed.subtotal,
153
- taxTotal: processed.taxTotal,
154
- shippingTotal: processed.shippingTotal,
155
- discountTotal: processed.discountTotal,
156
- grandTotal: processed.total,
157
- paymentIntentId: processed.paymentIntentId,
158
- paymentMethodId: processed.paymentMethodId,
159
- metadata: {
160
- // H2 fix: Merge hook-injected metadata (e.g., BNPL fee) before core fields
161
- ...(typeof processed.metadata === "object" && processed.metadata !== null
162
- ? processed.metadata
163
- : {}),
164
- cartId: processed.cartId,
165
- paymentIntentId: processed.paymentIntentId,
166
- checkoutId: processed.checkoutId,
167
- promotionCodes: processed.promotionCodes,
168
- appliedPromotions: processed.appliedPromotions,
169
- shippingAddress: processed.shippingAddress,
170
- },
171
- lineItems: processed.lineItems.map((lineItem) => {
172
- const payload = {
173
- entityId: lineItem.entityId,
174
- entityType: lineItem.entityType ?? "product",
175
- title: lineItem.title ?? lineItem.entityId,
176
- quantity: lineItem.quantity,
177
- unitPrice: lineItem.resolvedUnitPrice ?? 0,
178
- totalPrice: lineItem.resolvedTotal ?? 0,
179
- };
180
- return lineItem.variantId !== undefined
181
- ? { ...payload, variantId: lineItem.variantId }
182
- : payload;
183
- }),
184
- ...(customerUuid !== undefined
185
- ? { customerId: customerUuid }
186
- : {}),
187
- };
188
-
189
- const order = await kernel.services.orders.create(orderPayload, actor);
190
-
191
- if (!order.ok) {
192
- return c.json(
193
- mapErrorToResponse(order.error),
194
- mapErrorToStatus(order.error),
195
- );
196
- }
197
-
198
- if (order.ok && (processed.appliedPromotions?.length ?? 0) > 0) {
199
- await kernel.services.promotions.recordUsage({
200
- promotions: processed.appliedPromotions ?? [],
201
- orderId: order.value.id,
202
- ...(customerUuid !== undefined
203
- ? { customerId: customerUuid }
204
- : {}),
205
- });
206
- }
207
-
208
- if (order.ok) {
209
- await kernel.services.tax.reportTransaction({
210
- transactionId: order.value.id,
211
- transactionDate: new Date(),
212
- currency: processed.currency,
213
- amount:
214
- processed.subtotal -
215
- processed.discountTotal +
216
- processed.shippingTotal,
217
- shipping: processed.shippingTotal,
218
- salesTax: processed.taxTotal,
219
- lineItems: processed.lineItems.map((lineItem, index) => ({
220
- id: lineItem.id ?? `${order.value.id}-${index + 1}`,
221
- entityId: lineItem.entityId,
222
- description: lineItem.title ?? lineItem.entityId,
223
- quantity: lineItem.quantity,
224
- unitPrice: lineItem.resolvedUnitPrice ?? 0,
225
- ...(lineItem.discountAmount !== undefined
226
- ? { discount: lineItem.discountAmount }
227
- : {}),
228
- })),
229
- ...(customerUuid !== undefined
230
- ? { customerId: customerUuid }
231
- : {}),
232
- ...(processed.shippingAddress !== undefined
233
- ? { toAddress: processed.shippingAddress }
234
- : {}),
235
- });
236
- }
237
-
238
- // Stash paymentMethodId for completeCheckout compensation chain
239
- context.context.paymentMethodId = processed.paymentMethodId;
240
-
241
- const afterReport = await runAfterHooks(
242
- afterHooks,
243
- null,
244
- order.value,
245
- "create",
246
- context,
247
- );
248
-
249
- await kernel.services.cart.markAsCheckedOut(body.cartId, actor);
250
-
251
- return c.json(
252
- {
253
- data: {
254
- ...order.value,
255
- // Stripe Elements requires clientSecret to collect card details on the frontend
256
- ...(processed.paymentClientSecret
257
- ? { paymentClientSecret: processed.paymentClientSecret }
258
- : {}),
259
- },
260
- meta: afterReport.hasErrors
261
- ? { hookErrors: afterReport.errors }
262
- : undefined,
263
- },
264
- 201,
265
- );
266
- } catch (error) {
267
- const message = isCommerceError(error)
268
- ? error.message
269
- : "Checkout failed.";
270
- return c.json(
271
- {
272
- error: {
273
- code: "CHECKOUT_FAILED",
274
- message,
275
- },
276
- },
277
- 422,
278
- );
279
- }
280
- });
281
-
282
- return router;
283
- }
@@ -1,70 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { Kernel } from "../../../runtime/kernel.js";
3
- import {
4
- inventoryAdjustRoute,
5
- inventoryReserveRoute,
6
- inventoryReleaseRoute,
7
- createWarehouseRoute,
8
- inventoryCheckRoute,
9
- listWarehousesRoute,
10
- } from "../schemas/inventory.js";
11
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
12
-
13
- export function inventoryRoutes(kernel: Kernel) {
14
- const router = new OpenAPIHono<AppEnv>();
15
-
16
- // @ts-expect-error -- openapi handler union return type
17
- router.openapi(inventoryCheckRoute, async (c) => {
18
- const entityIds = (c.req.query("entityIds") ?? "")
19
- .split(",")
20
- .map((item) => item.trim())
21
- .filter(Boolean);
22
-
23
- const result = await kernel.services.inventory.checkMultiple(entityIds);
24
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
25
- return c.json({ data: result.value });
26
- });
27
-
28
- // @ts-expect-error -- openapi handler union return type
29
- router.openapi(inventoryAdjustRoute, async (c) => {
30
- const body = c.req.valid("json");
31
- const result = await kernel.services.inventory.adjust(body, c.get("actor"));
32
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
33
- return c.json({ data: result.value });
34
- });
35
-
36
- // @ts-expect-error -- openapi handler union return type
37
- router.openapi(inventoryReserveRoute, async (c) => {
38
- const body = c.req.valid("json");
39
- const result = await kernel.services.inventory.reserve(body, c.get("actor"));
40
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
41
- return c.json({ data: { reserved: true } });
42
- });
43
-
44
- // @ts-expect-error -- openapi handler union return type
45
- router.openapi(inventoryReleaseRoute, async (c) => {
46
- const body = c.req.valid("json");
47
- const result = await kernel.services.inventory.release(body, c.get("actor"));
48
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
49
- return c.json({ data: { released: true } });
50
- });
51
-
52
- // @ts-expect-error -- openapi handler union return type
53
- router.openapi(createWarehouseRoute, async (c) => {
54
- const body = c.req.valid("json") as Parameters<typeof kernel.services.inventory.createWarehouse>[0];
55
- const actor = c.get("actor");
56
- const result = await kernel.services.inventory.createWarehouse(body, actor);
57
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
58
- return c.json({ data: result.value }, 201);
59
- });
60
-
61
- // @ts-expect-error -- openapi handler union return type
62
- router.openapi(listWarehousesRoute, async (c) => {
63
- const actor = c.get("actor");
64
- const result = await kernel.services.inventory.listWarehouses(actor);
65
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
66
- return c.json({ data: result.value });
67
- });
68
-
69
- return router;
70
- }
@@ -1,86 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { Kernel } from "../../../runtime/kernel.js";
3
- import type { AttachMediaInput } from "../../../modules/media/service.js";
4
- import { attachMediaRoute, getMediaRoute, deleteMediaRoute } from "../schemas/media.js";
5
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
6
-
7
- export function mediaRoutes(kernel: Kernel) {
8
- const router = new OpenAPIHono<AppEnv>();
9
-
10
- // Upload requires authentication + media:write permission
11
- router.use("/upload", requirePerm("media:write"));
12
-
13
- router.post("/upload", async (c) => {
14
- const body = await c.req.parseBody();
15
- const file = body.file as File;
16
-
17
- if (!file) {
18
- return c.json({ error: { code: "VALIDATION_FAILED", message: "file is required" } }, 422);
19
- }
20
-
21
- const buffer = await file.arrayBuffer();
22
- const actor = c.get("actor");
23
- const result = await kernel.services.media.upload({
24
- filename: file.name,
25
- contentType: file.type,
26
- data: buffer,
27
- alt: String(body.alt ?? ""),
28
- }, actor);
29
-
30
- if (!result.ok) {
31
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
32
- }
33
-
34
- return c.json({ data: result.value }, 201);
35
- });
36
-
37
- router.openapi(getMediaRoute, async (c) => {
38
- const signed = c.req.query("signed") === "true";
39
-
40
- // Signed URLs require authentication
41
- if (signed && !c.get("actor")) {
42
- return c.json(
43
- { error: { code: "UNAUTHORIZED", message: "Authentication required for signed URLs." } },
44
- 401,
45
- );
46
- }
47
-
48
- const result = signed
49
- ? await kernel.services.media.getSignedUrl(c.req.param("id"))
50
- : await kernel.services.media.getUrl(c.req.param("id"));
51
-
52
- if (!result.ok) {
53
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
54
- }
55
-
56
- return c.redirect(result.value, 302);
57
- });
58
-
59
- // @ts-expect-error -- openapi handler union return type
60
- router.openapi(deleteMediaRoute, async (c) => {
61
- const actor = c.get("actor");
62
- if (!actor || (!actor.permissions.includes("media:write") && !actor.permissions.includes("*:*"))) {
63
- return c.json({ error: { code: "FORBIDDEN", message: "media:write permission required." } }, 403);
64
- }
65
- const result = await kernel.services.media.delete(c.req.param("id"));
66
- if (!result.ok) {
67
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
68
- }
69
- return c.json({ data: { deleted: true } });
70
- });
71
-
72
- // Attach requires media:write
73
- router.use("/attach", requirePerm("media:write"));
74
-
75
- // @ts-expect-error -- openapi handler union return type
76
- router.openapi(attachMediaRoute, async (c) => {
77
- const body = c.req.valid("json") as AttachMediaInput;
78
- const result = await kernel.services.media.attachToEntity(body);
79
- if (!result.ok) {
80
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
81
- }
82
- return c.json({ data: { attached: true } }, 201);
83
- });
84
-
85
- return router;
86
- }
@@ -1,78 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { Kernel } from "../../../runtime/kernel.js";
3
- import { changeOrderStatusRoute, listOrdersRoute, getOrderRoute, getOrderFulfillmentsRoute } from "../schemas/orders.js";
4
- import { type AppEnv, isUUID, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
5
-
6
- export function orderRoutes(kernel: Kernel) {
7
- const router = new OpenAPIHono<AppEnv>();
8
-
9
- // @ts-expect-error -- openapi handler union return type
10
- router.openapi(listOrdersRoute, async (c) => {
11
- const pagination = parsePagination(c.req.query());
12
- const status = c.req.query("status");
13
- const result = await kernel.services.orders.list(
14
- {
15
- page: pagination.page,
16
- limit: pagination.limit,
17
- ...(status !== undefined ? { status } : {}),
18
- },
19
- c.get("actor"),
20
- );
21
-
22
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
23
- return c.json({
24
- data: result.value.items,
25
- meta: {
26
- pagination: result.value.pagination,
27
- },
28
- });
29
- });
30
-
31
- // @ts-expect-error -- openapi handler union return type
32
- router.openapi(getOrderRoute, async (c) => {
33
- const idOrNumber = c.req.param("idOrNumber");
34
- const result = isUUID(idOrNumber)
35
- ? await kernel.services.orders.getById(idOrNumber, c.get("actor"))
36
- : await kernel.services.orders.getByNumber(idOrNumber, c.get("actor"));
37
-
38
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
39
- return c.json({ data: result.value });
40
- });
41
-
42
- // @ts-expect-error -- openapi() enforces strict response typing but our handler
43
- // returns union responses (200 | 400 | 404). The route definition documents the
44
- // contract; the handler returns dynamic status.
45
- router.openapi(changeOrderStatusRoute, async (c) => {
46
- const body = c.req.valid("json");
47
- const result = await kernel.services.orders.changeStatus(
48
- {
49
- orderId: c.req.param("id"),
50
- newStatus: body.status,
51
- ...(body.reason !== undefined ? { reason: body.reason } : {}),
52
- },
53
- c.get("actor"),
54
- );
55
-
56
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
57
- return c.json({ data: result.value });
58
- });
59
-
60
- // @ts-expect-error -- openapi handler union return type
61
- router.openapi(getOrderFulfillmentsRoute, async (c) => {
62
- const orderId = c.req.param("id");
63
- const actor = c.get("actor");
64
-
65
- // Verify the order exists and the actor has access before returning fulfillments
66
- const orderResult = isUUID(orderId)
67
- ? await kernel.services.orders.getById(orderId, actor)
68
- : await kernel.services.orders.getByNumber(orderId, actor);
69
-
70
- if (!orderResult.ok) return c.json(mapErrorToResponse(orderResult.error), mapErrorToStatus(orderResult.error));
71
-
72
- const result = await kernel.services.fulfillment.getByOrderId(orderResult.value.id);
73
- if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
74
- return c.json({ data: result.value });
75
- });
76
-
77
- return router;
78
- }
@@ -1,60 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
3
- import type { Kernel } from "../../../runtime/kernel.js";
4
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
5
- import { processedWebhookEvents } from "../../../modules/webhooks/schema.js";
6
-
7
- type Db = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
8
-
9
- export function paymentRoutes(kernel: Kernel) {
10
- const router = new OpenAPIHono<AppEnv>();
11
-
12
- router.post("/webhook", async (c) => {
13
- const result = await kernel.services.payments.verifyWebhook(c.req.raw);
14
- if (!result.ok) {
15
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
16
- }
17
-
18
- const event = result.value;
19
- const db = kernel.database.db as Db;
20
-
21
- // Atomic idempotency: INSERT ... ON CONFLICT DO NOTHING ... RETURNING
22
- // If the row already exists, RETURNING yields zero rows → duplicate.
23
- // If the row is new, RETURNING yields one row → process the event.
24
- // No TOCTOU race: the UNIQUE constraint on event_id is the single source of truth.
25
- const [inserted] = await db
26
- .insert(processedWebhookEvents)
27
- .values({
28
- eventId: event.id,
29
- provider: "stripe",
30
- eventType: event.type,
31
- })
32
- .onConflictDoNothing()
33
- .returning({ id: processedWebhookEvents.id });
34
-
35
- if (!inserted) {
36
- // Row already existed — this is a duplicate delivery
37
- return c.json({ data: { received: true, duplicate: true } });
38
- }
39
-
40
- // Process the event (first time only)
41
- if (event.type === "payment_intent.succeeded") {
42
- const data = event.data as Record<string, unknown> | undefined;
43
- const metadata = data?.metadata as Record<string, unknown> | undefined;
44
- if (typeof metadata?.orderId === "string") {
45
- await kernel.services.orders.changeStatus(
46
- {
47
- orderId: metadata.orderId,
48
- newStatus: "confirmed",
49
- reason: "stripe_webhook_payment_intent_succeeded",
50
- },
51
- null,
52
- );
53
- }
54
- }
55
-
56
- return c.json({ data: { received: true } });
57
- });
58
-
59
- return router;
60
- }
@@ -1,57 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { Kernel } from "../../../runtime/kernel.js";
3
- import { setBasePriceRoute, createModifierRoute, listPricesRoute } from "../schemas/pricing.js";
4
- import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
5
-
6
- export function pricingRoutes(kernel: Kernel) {
7
- const router = new OpenAPIHono<AppEnv>();
8
-
9
- router.use("/prices", requirePerm("pricing:manage"));
10
-
11
- // @ts-expect-error -- openapi() enforces strict response typing but our handler
12
- // returns union responses (201 | 400 | 422). The route definition documents the
13
- // contract; the handler returns dynamic status.
14
- router.openapi(setBasePriceRoute, async (c) => {
15
- const actor = c.get("actor");
16
- const result = await kernel.services.pricing.setBasePrice(c.req.valid("json"), actor);
17
- if (!result.ok) {
18
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
19
- }
20
- return c.json({ data: result.value }, 201);
21
- });
22
-
23
- // @ts-expect-error -- openapi handler union return type
24
- router.openapi(listPricesRoute, async (c) => {
25
- const entityId = c.req.query("entityId");
26
- const variantId = c.req.query("variantId");
27
- const currency = c.req.query("currency");
28
- const customerGroupId = c.req.query("customerGroupId");
29
-
30
- const result = await kernel.services.pricing.listPrices({
31
- ...(entityId !== undefined ? { entityId } : {}),
32
- ...(variantId !== undefined ? { variantId } : {}),
33
- ...(currency !== undefined ? { currency } : {}),
34
- ...(customerGroupId !== undefined ? { customerGroupId } : {}),
35
- });
36
- if (!result.ok) {
37
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
38
- }
39
- return c.json({ data: result.value });
40
- });
41
-
42
- router.use("/modifiers", requirePerm("pricing:manage"));
43
-
44
- // @ts-expect-error -- openapi() enforces strict response typing but our handler
45
- // returns union responses (201 | 400 | 422). The route definition documents the
46
- // contract; the handler returns dynamic status.
47
- router.openapi(createModifierRoute, async (c) => {
48
- const actor = c.get("actor");
49
- const result = await kernel.services.pricing.createModifier(c.req.valid("json"), actor);
50
- if (!result.ok) {
51
- return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
52
- }
53
- return c.json({ data: result.value }, 201);
54
- });
55
-
56
- return router;
57
- }