@unifiedcommerce/core 0.1.0 → 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 (174) hide show
  1. package/package.json +1 -2
  2. package/src/adapters/console-email.ts +0 -43
  3. package/src/auth/access.ts +0 -187
  4. package/src/auth/auth-schema.ts +0 -139
  5. package/src/auth/middleware.ts +0 -161
  6. package/src/auth/org.ts +0 -41
  7. package/src/auth/permissions.ts +0 -28
  8. package/src/auth/setup.ts +0 -169
  9. package/src/auth/system-actor.ts +0 -19
  10. package/src/auth/types.ts +0 -10
  11. package/src/config/defaults.ts +0 -82
  12. package/src/config/define-config.ts +0 -53
  13. package/src/config/types.ts +0 -299
  14. package/src/generated/plugin-capabilities.d.ts +0 -20
  15. package/src/generated/plugin-manifest.ts +0 -23
  16. package/src/generated/plugin-repositories.d.ts +0 -20
  17. package/src/hooks/checkout-completion.ts +0 -262
  18. package/src/hooks/checkout.ts +0 -677
  19. package/src/hooks/order-emails.ts +0 -62
  20. package/src/index.ts +0 -214
  21. package/src/interfaces/mcp/agent-prompt.ts +0 -174
  22. package/src/interfaces/mcp/context-enrichment.ts +0 -177
  23. package/src/interfaces/mcp/server.ts +0 -617
  24. package/src/interfaces/mcp/transport.ts +0 -68
  25. package/src/interfaces/rest/customer-portal.ts +0 -299
  26. package/src/interfaces/rest/index.ts +0 -74
  27. package/src/interfaces/rest/router.ts +0 -334
  28. package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
  29. package/src/interfaces/rest/routes/audit.ts +0 -50
  30. package/src/interfaces/rest/routes/carts.ts +0 -89
  31. package/src/interfaces/rest/routes/catalog.ts +0 -493
  32. package/src/interfaces/rest/routes/checkout.ts +0 -283
  33. package/src/interfaces/rest/routes/inventory.ts +0 -70
  34. package/src/interfaces/rest/routes/media.ts +0 -86
  35. package/src/interfaces/rest/routes/orders.ts +0 -78
  36. package/src/interfaces/rest/routes/payments.ts +0 -60
  37. package/src/interfaces/rest/routes/pricing.ts +0 -57
  38. package/src/interfaces/rest/routes/promotions.ts +0 -92
  39. package/src/interfaces/rest/routes/search.ts +0 -71
  40. package/src/interfaces/rest/routes/webhooks.ts +0 -46
  41. package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
  42. package/src/interfaces/rest/schemas/audit.ts +0 -46
  43. package/src/interfaces/rest/schemas/carts.ts +0 -125
  44. package/src/interfaces/rest/schemas/catalog.ts +0 -450
  45. package/src/interfaces/rest/schemas/checkout.ts +0 -66
  46. package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
  47. package/src/interfaces/rest/schemas/inventory.ts +0 -138
  48. package/src/interfaces/rest/schemas/media.ts +0 -75
  49. package/src/interfaces/rest/schemas/orders.ts +0 -104
  50. package/src/interfaces/rest/schemas/pricing.ts +0 -80
  51. package/src/interfaces/rest/schemas/promotions.ts +0 -110
  52. package/src/interfaces/rest/schemas/responses.ts +0 -85
  53. package/src/interfaces/rest/schemas/search.ts +0 -58
  54. package/src/interfaces/rest/schemas/shared.ts +0 -62
  55. package/src/interfaces/rest/schemas/webhooks.ts +0 -68
  56. package/src/interfaces/rest/utils.ts +0 -104
  57. package/src/interfaces/rest/webhook-router.ts +0 -50
  58. package/src/kernel/compensation/executor.ts +0 -61
  59. package/src/kernel/compensation/types.ts +0 -26
  60. package/src/kernel/database/adapter.ts +0 -13
  61. package/src/kernel/database/drizzle-db.ts +0 -56
  62. package/src/kernel/database/migrate.ts +0 -76
  63. package/src/kernel/database/plugin-types.ts +0 -34
  64. package/src/kernel/database/schema.ts +0 -49
  65. package/src/kernel/database/scoped-db.ts +0 -68
  66. package/src/kernel/database/tx-context.ts +0 -46
  67. package/src/kernel/error-mapper.ts +0 -15
  68. package/src/kernel/errors.ts +0 -89
  69. package/src/kernel/factory/repository-factory.ts +0 -242
  70. package/src/kernel/hooks/create-context.ts +0 -43
  71. package/src/kernel/hooks/executor.ts +0 -88
  72. package/src/kernel/hooks/registry.ts +0 -74
  73. package/src/kernel/hooks/types.ts +0 -52
  74. package/src/kernel/http-error.ts +0 -44
  75. package/src/kernel/jobs/adapter.ts +0 -36
  76. package/src/kernel/jobs/drizzle-adapter.ts +0 -58
  77. package/src/kernel/jobs/runner.ts +0 -153
  78. package/src/kernel/jobs/schema.ts +0 -46
  79. package/src/kernel/jobs/types.ts +0 -30
  80. package/src/kernel/local-api.ts +0 -185
  81. package/src/kernel/plugin/manifest.ts +0 -253
  82. package/src/kernel/query/executor.ts +0 -184
  83. package/src/kernel/query/registry.ts +0 -46
  84. package/src/kernel/result.ts +0 -33
  85. package/src/kernel/schema/extra-columns.ts +0 -37
  86. package/src/kernel/service-registry.ts +0 -76
  87. package/src/kernel/service-timing.ts +0 -89
  88. package/src/kernel/state-machine/machine.ts +0 -101
  89. package/src/modules/analytics/drizzle-adapter.ts +0 -426
  90. package/src/modules/analytics/hooks.ts +0 -11
  91. package/src/modules/analytics/models.ts +0 -125
  92. package/src/modules/analytics/repository/index.ts +0 -6
  93. package/src/modules/analytics/service.ts +0 -245
  94. package/src/modules/analytics/types.ts +0 -180
  95. package/src/modules/audit/hooks.ts +0 -78
  96. package/src/modules/audit/schema.ts +0 -33
  97. package/src/modules/audit/service.ts +0 -151
  98. package/src/modules/cart/access.ts +0 -27
  99. package/src/modules/cart/matcher.ts +0 -26
  100. package/src/modules/cart/repository/index.ts +0 -234
  101. package/src/modules/cart/schema.ts +0 -42
  102. package/src/modules/cart/schemas.ts +0 -38
  103. package/src/modules/cart/service.ts +0 -541
  104. package/src/modules/catalog/repository/index.ts +0 -772
  105. package/src/modules/catalog/schema.ts +0 -203
  106. package/src/modules/catalog/schemas.ts +0 -104
  107. package/src/modules/catalog/service.ts +0 -1544
  108. package/src/modules/customers/repository/index.ts +0 -327
  109. package/src/modules/customers/schema.ts +0 -64
  110. package/src/modules/customers/service.ts +0 -171
  111. package/src/modules/fulfillment/repository/index.ts +0 -426
  112. package/src/modules/fulfillment/schema.ts +0 -101
  113. package/src/modules/fulfillment/service.ts +0 -555
  114. package/src/modules/fulfillment/types.ts +0 -59
  115. package/src/modules/inventory/repository/index.ts +0 -509
  116. package/src/modules/inventory/schema.ts +0 -94
  117. package/src/modules/inventory/schemas.ts +0 -38
  118. package/src/modules/inventory/service.ts +0 -490
  119. package/src/modules/media/adapter.ts +0 -17
  120. package/src/modules/media/repository/index.ts +0 -274
  121. package/src/modules/media/schema.ts +0 -41
  122. package/src/modules/media/service.ts +0 -151
  123. package/src/modules/orders/repository/index.ts +0 -287
  124. package/src/modules/orders/schema.ts +0 -66
  125. package/src/modules/orders/service.ts +0 -619
  126. package/src/modules/orders/stale-order-cleanup.ts +0 -76
  127. package/src/modules/organization/service.ts +0 -191
  128. package/src/modules/payments/adapter.ts +0 -47
  129. package/src/modules/payments/repository/index.ts +0 -6
  130. package/src/modules/payments/service.ts +0 -107
  131. package/src/modules/pricing/repository/index.ts +0 -291
  132. package/src/modules/pricing/schema.ts +0 -71
  133. package/src/modules/pricing/schemas.ts +0 -38
  134. package/src/modules/pricing/service.ts +0 -494
  135. package/src/modules/promotions/repository/index.ts +0 -325
  136. package/src/modules/promotions/schema.ts +0 -62
  137. package/src/modules/promotions/schemas.ts +0 -38
  138. package/src/modules/promotions/service.ts +0 -598
  139. package/src/modules/search/adapter.ts +0 -57
  140. package/src/modules/search/hooks.ts +0 -12
  141. package/src/modules/search/repository/index.ts +0 -6
  142. package/src/modules/search/service.ts +0 -315
  143. package/src/modules/shipping/calculator.ts +0 -188
  144. package/src/modules/shipping/repository/index.ts +0 -6
  145. package/src/modules/shipping/service.ts +0 -51
  146. package/src/modules/tax/adapter.ts +0 -60
  147. package/src/modules/tax/repository/index.ts +0 -6
  148. package/src/modules/tax/service.ts +0 -53
  149. package/src/modules/webhooks/hook.ts +0 -34
  150. package/src/modules/webhooks/repository/index.ts +0 -278
  151. package/src/modules/webhooks/schema.ts +0 -56
  152. package/src/modules/webhooks/service.ts +0 -117
  153. package/src/modules/webhooks/signing.ts +0 -6
  154. package/src/modules/webhooks/ssrf-guard.ts +0 -71
  155. package/src/modules/webhooks/tasks.ts +0 -52
  156. package/src/modules/webhooks/worker.ts +0 -134
  157. package/src/runtime/commerce.ts +0 -145
  158. package/src/runtime/kernel.ts +0 -419
  159. package/src/runtime/logger.ts +0 -36
  160. package/src/runtime/server.ts +0 -349
  161. package/src/runtime/shutdown.ts +0 -43
  162. package/src/test-utils/create-pglite-adapter.ts +0 -129
  163. package/src/test-utils/create-plugin-test-app.ts +0 -128
  164. package/src/test-utils/create-repository-test-harness.ts +0 -16
  165. package/src/test-utils/create-test-config.ts +0 -190
  166. package/src/test-utils/create-test-kernel.ts +0 -7
  167. package/src/test-utils/create-test-plugin-context.ts +0 -75
  168. package/src/test-utils/rest-api-test-utils.ts +0 -265
  169. package/src/test-utils/test-actors.ts +0 -62
  170. package/src/test-utils/typed-hooks.ts +0 -54
  171. package/src/types/commerce-types.ts +0 -34
  172. package/src/utils/id.ts +0 -3
  173. package/src/utils/logger.ts +0 -18
  174. 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
- }