@porulle/core 0.11.0 → 0.14.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 (300) hide show
  1. package/dist/auth/actor.d.ts +10 -0
  2. package/dist/auth/actor.d.ts.map +1 -0
  3. package/dist/auth/actor.js +91 -0
  4. package/dist/auth/auth-schema-guard.d.ts +2444 -0
  5. package/dist/auth/auth-schema-guard.d.ts.map +1 -0
  6. package/dist/auth/auth-schema-guard.js +113 -0
  7. package/dist/auth/auth-schema.d.ts +228 -0
  8. package/dist/auth/auth-schema.d.ts.map +1 -1
  9. package/dist/auth/auth-schema.js +24 -2
  10. package/dist/auth/middleware.d.ts +1 -0
  11. package/dist/auth/middleware.d.ts.map +1 -1
  12. package/dist/auth/middleware.js +16 -92
  13. package/dist/auth/org.d.ts +8 -2
  14. package/dist/auth/org.d.ts.map +1 -1
  15. package/dist/auth/org.js +12 -4
  16. package/dist/auth/organization-guard.d.ts +5 -0
  17. package/dist/auth/organization-guard.d.ts.map +1 -0
  18. package/dist/auth/organization-guard.js +124 -0
  19. package/dist/auth/permissions.d.ts +10 -0
  20. package/dist/auth/permissions.d.ts.map +1 -1
  21. package/dist/auth/permissions.js +31 -7
  22. package/dist/auth/role-authority.d.ts +44 -0
  23. package/dist/auth/role-authority.d.ts.map +1 -0
  24. package/dist/auth/role-authority.js +85 -0
  25. package/dist/auth/setup.d.ts.map +1 -1
  26. package/dist/auth/setup.js +19 -1
  27. package/dist/auth/strict-org-resolution.d.ts.map +1 -1
  28. package/dist/auth/strict-org-resolution.js +9 -1
  29. package/dist/auth/types.d.ts +1 -1
  30. package/dist/auth/types.d.ts.map +1 -1
  31. package/dist/config/defaults.d.ts.map +1 -1
  32. package/dist/config/defaults.js +2 -0
  33. package/dist/config/types.d.ts +32 -4
  34. package/dist/config/types.d.ts.map +1 -1
  35. package/dist/hooks/checkout.d.ts.map +1 -1
  36. package/dist/hooks/checkout.js +10 -9
  37. package/dist/index.d.ts +7 -5
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +5 -3
  40. package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
  41. package/dist/interfaces/rest/audit-middleware.js +2 -0
  42. package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
  43. package/dist/interfaces/rest/customer-portal.js +9 -8
  44. package/dist/interfaces/rest/route-coverage.d.ts +14 -0
  45. package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
  46. package/dist/interfaces/rest/route-coverage.js +81 -0
  47. package/dist/interfaces/rest/router.d.ts +7 -2
  48. package/dist/interfaces/rest/router.d.ts.map +1 -1
  49. package/dist/interfaces/rest/router.js +8 -4
  50. package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
  51. package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
  52. package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +2 -2
  53. package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
  54. package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
  55. package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
  56. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
  57. package/dist/interfaces/rest/routes/admin/staff.js +126 -66
  58. package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
  59. package/dist/interfaces/rest/routes/audit.js +3 -3
  60. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  61. package/dist/interfaces/rest/routes/carts.js +13 -10
  62. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  63. package/dist/interfaces/rest/routes/catalog.js +10 -2
  64. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  65. package/dist/interfaces/rest/routes/checkout.js +30 -35
  66. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
  67. package/dist/interfaces/rest/routes/documents.js +10 -5
  68. package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
  69. package/dist/interfaces/rest/routes/inventory.js +2 -0
  70. package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
  71. package/dist/interfaces/rest/routes/media.js +7 -3
  72. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  73. package/dist/interfaces/rest/routes/orders.js +26 -8
  74. package/dist/interfaces/rest/routes/pricing.js +2 -2
  75. package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
  76. package/dist/interfaces/rest/routes/promotions.js +13 -10
  77. package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
  78. package/dist/interfaces/rest/routes/search.js +2 -1
  79. package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
  80. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  81. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  82. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  83. package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
  84. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  85. package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
  86. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  87. package/dist/interfaces/rest/utils.d.ts +15 -0
  88. package/dist/interfaces/rest/utils.d.ts.map +1 -1
  89. package/dist/interfaces/rest/utils.js +45 -2
  90. package/dist/kernel/compensation/executor.js +2 -2
  91. package/dist/kernel/database/plugin-types.d.ts +11 -0
  92. package/dist/kernel/database/plugin-types.d.ts.map +1 -1
  93. package/dist/kernel/database/scoped-db.d.ts +12 -0
  94. package/dist/kernel/database/scoped-db.d.ts.map +1 -1
  95. package/dist/kernel/database/scoped-db.js +12 -0
  96. package/dist/kernel/database/tx-context.d.ts +8 -0
  97. package/dist/kernel/database/tx-context.d.ts.map +1 -1
  98. package/dist/kernel/database/tx-context.js +13 -0
  99. package/dist/kernel/hooks/create-context.d.ts +2 -0
  100. package/dist/kernel/hooks/create-context.d.ts.map +1 -1
  101. package/dist/kernel/hooks/create-context.js +1 -0
  102. package/dist/kernel/hooks/types.d.ts +2 -0
  103. package/dist/kernel/hooks/types.d.ts.map +1 -1
  104. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  105. package/dist/kernel/plugin/manifest.js +9 -5
  106. package/dist/modules/analytics/service.js +2 -2
  107. package/dist/modules/audit/service.js +3 -3
  108. package/dist/modules/cart/repository/index.d.ts +13 -7
  109. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  110. package/dist/modules/cart/repository/index.js +16 -10
  111. package/dist/modules/cart/schema.d.ts +17 -0
  112. package/dist/modules/cart/schema.d.ts.map +1 -1
  113. package/dist/modules/cart/schema.js +4 -0
  114. package/dist/modules/cart/service.d.ts +22 -9
  115. package/dist/modules/cart/service.d.ts.map +1 -1
  116. package/dist/modules/cart/service.js +57 -33
  117. package/dist/modules/catalog/brand-service.js +7 -7
  118. package/dist/modules/catalog/category-service.js +7 -7
  119. package/dist/modules/catalog/entity-service.d.ts +14 -7
  120. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  121. package/dist/modules/catalog/entity-service.js +109 -31
  122. package/dist/modules/catalog/read-policy.d.ts +9 -0
  123. package/dist/modules/catalog/read-policy.d.ts.map +1 -0
  124. package/dist/modules/catalog/read-policy.js +8 -0
  125. package/dist/modules/catalog/repository/index.d.ts +3 -2
  126. package/dist/modules/catalog/repository/index.d.ts.map +1 -1
  127. package/dist/modules/catalog/repository/index.js +14 -4
  128. package/dist/modules/catalog/service.d.ts +15 -13
  129. package/dist/modules/catalog/service.d.ts.map +1 -1
  130. package/dist/modules/catalog/service.js +25 -18
  131. package/dist/modules/customers/module.d.ts.map +1 -1
  132. package/dist/modules/customers/module.js +1 -0
  133. package/dist/modules/customers/service.d.ts +2 -0
  134. package/dist/modules/customers/service.d.ts.map +1 -1
  135. package/dist/modules/customers/service.js +21 -20
  136. package/dist/modules/documents/module.d.ts.map +1 -1
  137. package/dist/modules/documents/module.js +1 -0
  138. package/dist/modules/documents/service.d.ts +18 -4
  139. package/dist/modules/documents/service.d.ts.map +1 -1
  140. package/dist/modules/documents/service.js +54 -16
  141. package/dist/modules/fulfillment/module.d.ts.map +1 -1
  142. package/dist/modules/fulfillment/module.js +1 -0
  143. package/dist/modules/fulfillment/service.d.ts +2 -0
  144. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  145. package/dist/modules/fulfillment/service.js +15 -6
  146. package/dist/modules/inventory/service.d.ts.map +1 -1
  147. package/dist/modules/inventory/service.js +15 -14
  148. package/dist/modules/media/service.js +6 -6
  149. package/dist/modules/orders/guest-access.d.ts +27 -0
  150. package/dist/modules/orders/guest-access.d.ts.map +1 -0
  151. package/dist/modules/orders/guest-access.js +46 -0
  152. package/dist/modules/orders/module.d.ts.map +1 -1
  153. package/dist/modules/orders/module.js +1 -0
  154. package/dist/modules/orders/quote.d.ts.map +1 -1
  155. package/dist/modules/orders/quote.js +1 -0
  156. package/dist/modules/orders/repository/index.d.ts +1 -1
  157. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  158. package/dist/modules/orders/repository/index.js +2 -2
  159. package/dist/modules/orders/schema.d.ts +17 -0
  160. package/dist/modules/orders/schema.d.ts.map +1 -1
  161. package/dist/modules/orders/schema.js +5 -3
  162. package/dist/modules/orders/service.d.ts +16 -7
  163. package/dist/modules/orders/service.d.ts.map +1 -1
  164. package/dist/modules/orders/service.js +184 -55
  165. package/dist/modules/pricing/module.d.ts.map +1 -1
  166. package/dist/modules/pricing/module.js +1 -0
  167. package/dist/modules/pricing/service.d.ts +2 -0
  168. package/dist/modules/pricing/service.d.ts.map +1 -1
  169. package/dist/modules/pricing/service.js +12 -11
  170. package/dist/modules/promotions/module.d.ts.map +1 -1
  171. package/dist/modules/promotions/module.js +1 -0
  172. package/dist/modules/promotions/service.d.ts +3 -0
  173. package/dist/modules/promotions/service.d.ts.map +1 -1
  174. package/dist/modules/promotions/service.js +19 -11
  175. package/dist/modules/search/adapter.d.ts +11 -0
  176. package/dist/modules/search/adapter.d.ts.map +1 -1
  177. package/dist/modules/search/module.d.ts.map +1 -1
  178. package/dist/modules/search/module.js +4 -1
  179. package/dist/modules/search/service.d.ts +1 -0
  180. package/dist/modules/search/service.d.ts.map +1 -1
  181. package/dist/modules/search/service.js +57 -6
  182. package/dist/modules/settings/module.d.ts.map +1 -1
  183. package/dist/modules/settings/module.js +1 -0
  184. package/dist/modules/settings/service.d.ts +3 -0
  185. package/dist/modules/settings/service.d.ts.map +1 -1
  186. package/dist/modules/settings/service.js +6 -4
  187. package/dist/modules/shipping/service.js +9 -9
  188. package/dist/modules/tax/module.d.ts.map +1 -1
  189. package/dist/modules/tax/module.js +1 -0
  190. package/dist/modules/tax/service.d.ts +3 -0
  191. package/dist/modules/tax/service.d.ts.map +1 -1
  192. package/dist/modules/tax/service.js +11 -9
  193. package/dist/modules/webhooks/hook.js +2 -2
  194. package/dist/modules/webhooks/module.d.ts.map +1 -1
  195. package/dist/modules/webhooks/module.js +1 -0
  196. package/dist/modules/webhooks/service.d.ts +2 -0
  197. package/dist/modules/webhooks/service.d.ts.map +1 -1
  198. package/dist/modules/webhooks/service.js +4 -4
  199. package/dist/runtime/server.d.ts.map +1 -1
  200. package/dist/runtime/server.js +28 -2
  201. package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
  202. package/dist/test-utils/create-pglite-adapter.js +35 -13
  203. package/dist/test-utils/create-test-config.d.ts.map +1 -1
  204. package/dist/test-utils/create-test-config.js +4 -0
  205. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  206. package/dist/test-utils/rest-api-test-utils.js +2 -0
  207. package/dist/test-utils/test-actors.js +2 -2
  208. package/dist/utils/id.d.ts +2 -0
  209. package/dist/utils/id.d.ts.map +1 -1
  210. package/dist/utils/id.js +11 -0
  211. package/package.json +6 -5
  212. package/src/auth/actor.ts +110 -0
  213. package/src/auth/auth-schema-guard.ts +143 -0
  214. package/src/auth/auth-schema.ts +50 -19
  215. package/src/auth/middleware.ts +25 -101
  216. package/src/auth/org.ts +15 -3
  217. package/src/auth/organization-guard.ts +158 -0
  218. package/src/auth/permissions.ts +31 -8
  219. package/src/auth/role-authority.ts +107 -0
  220. package/src/auth/setup.ts +19 -1
  221. package/src/auth/strict-org-resolution.ts +7 -1
  222. package/src/auth/types.ts +1 -1
  223. package/src/config/defaults.ts +2 -0
  224. package/src/config/types.ts +31 -4
  225. package/src/hooks/checkout.ts +19 -12
  226. package/src/index.ts +21 -6
  227. package/src/interfaces/rest/audit-middleware.ts +2 -0
  228. package/src/interfaces/rest/customer-portal.ts +31 -24
  229. package/src/interfaces/rest/route-coverage.ts +103 -0
  230. package/src/interfaces/rest/router.ts +16 -10
  231. package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
  232. package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +2 -2
  233. package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
  234. package/src/interfaces/rest/routes/admin/staff.ts +161 -72
  235. package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
  236. package/src/interfaces/rest/routes/audit.ts +3 -3
  237. package/src/interfaces/rest/routes/carts.ts +17 -7
  238. package/src/interfaces/rest/routes/catalog.ts +11 -0
  239. package/src/interfaces/rest/routes/checkout.ts +41 -38
  240. package/src/interfaces/rest/routes/documents.ts +15 -1
  241. package/src/interfaces/rest/routes/inventory.ts +2 -0
  242. package/src/interfaces/rest/routes/media.ts +8 -3
  243. package/src/interfaces/rest/routes/orders.ts +27 -8
  244. package/src/interfaces/rest/routes/pricing.ts +2 -2
  245. package/src/interfaces/rest/routes/promotions.ts +12 -9
  246. package/src/interfaces/rest/routes/search.ts +3 -1
  247. package/src/interfaces/rest/utils.ts +61 -2
  248. package/src/kernel/compensation/executor.ts +2 -2
  249. package/src/kernel/database/plugin-types.ts +11 -0
  250. package/src/kernel/database/scoped-db.ts +12 -0
  251. package/src/kernel/database/tx-context.ts +25 -0
  252. package/src/kernel/hooks/create-context.ts +3 -0
  253. package/src/kernel/hooks/types.ts +2 -0
  254. package/src/kernel/plugin/manifest.ts +9 -5
  255. package/src/modules/analytics/service.ts +2 -2
  256. package/src/modules/audit/service.ts +3 -3
  257. package/src/modules/cart/repository/index.ts +22 -8
  258. package/src/modules/cart/schema.ts +4 -0
  259. package/src/modules/cart/service.ts +62 -27
  260. package/src/modules/catalog/brand-service.ts +7 -7
  261. package/src/modules/catalog/category-service.ts +7 -7
  262. package/src/modules/catalog/entity-service.ts +117 -35
  263. package/src/modules/catalog/read-policy.ts +16 -0
  264. package/src/modules/catalog/repository/index.ts +19 -3
  265. package/src/modules/catalog/service.ts +46 -31
  266. package/src/modules/customers/module.ts +2 -0
  267. package/src/modules/customers/service.ts +23 -22
  268. package/src/modules/documents/module.ts +2 -0
  269. package/src/modules/documents/service.ts +74 -11
  270. package/src/modules/fulfillment/module.ts +2 -0
  271. package/src/modules/fulfillment/service.ts +17 -6
  272. package/src/modules/inventory/service.ts +15 -24
  273. package/src/modules/media/service.ts +6 -6
  274. package/src/modules/orders/guest-access.ts +55 -0
  275. package/src/modules/orders/module.ts +1 -0
  276. package/src/modules/orders/quote.ts +1 -0
  277. package/src/modules/orders/repository/index.ts +2 -0
  278. package/src/modules/orders/schema.ts +5 -3
  279. package/src/modules/orders/service.ts +302 -52
  280. package/src/modules/pricing/module.ts +2 -0
  281. package/src/modules/pricing/service.ts +14 -16
  282. package/src/modules/promotions/module.ts +2 -0
  283. package/src/modules/promotions/service.ts +25 -16
  284. package/src/modules/search/adapter.ts +9 -0
  285. package/src/modules/search/module.ts +5 -1
  286. package/src/modules/search/service.ts +55 -6
  287. package/src/modules/settings/module.ts +2 -0
  288. package/src/modules/settings/service.ts +8 -4
  289. package/src/modules/shipping/service.ts +9 -9
  290. package/src/modules/tax/module.ts +1 -0
  291. package/src/modules/tax/service.ts +13 -9
  292. package/src/modules/webhooks/hook.ts +2 -2
  293. package/src/modules/webhooks/module.ts +2 -0
  294. package/src/modules/webhooks/service.ts +6 -4
  295. package/src/runtime/server.ts +41 -2
  296. package/src/test-utils/create-pglite-adapter.ts +34 -12
  297. package/src/test-utils/create-test-config.ts +10 -6
  298. package/src/test-utils/rest-api-test-utils.ts +2 -0
  299. package/src/test-utils/test-actors.ts +2 -2
  300. package/src/utils/id.ts +20 -0
@@ -2,7 +2,8 @@ import type { Hono } from "hono";
2
2
  import type { OpenAPIHono, RouteConfig } from "@hono/zod-openapi";
3
3
  import type { PluginDb } from "../database/plugin-types.js";
4
4
  import type { DatabaseAdapter } from "../database/adapter.js";
5
- import { resolveOrgId } from "../../auth/org.js";
5
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
6
+ import { isRoutePermissionGuard, markRoutePermissionGuard } from "../../interfaces/rest/utils.js";
6
7
  import { createScopedDb } from "../database/scoped-db.js";
7
8
  import {
8
9
  getPluginDatabaseScopeOrganizationId,
@@ -97,7 +98,7 @@ function buildPluginContextDatabase(
97
98
  ): PluginContext["database"] {
98
99
  const orgGetter = () =>
99
100
  getPluginDatabaseScopeOrganizationId() ??
100
- resolveOrgId(null, undefined, commerceConfig);
101
+ resolveOrgIdForCommerce(null, commerceConfig);
101
102
 
102
103
  const scopedDb = createScopedDb(rawDb, orgGetter);
103
104
  const warnUnscoped = createRateLimitedUnscopedAccessWarning(logger);
@@ -288,7 +289,7 @@ export function defineCommercePlugin(
288
289
  const wrappedHandler = async (...args: unknown[]) => {
289
290
  const c = args[0] as { get?: (key: string) => unknown } | undefined;
290
291
  const actor = c?.get?.("actor");
291
- const orgId = resolveOrgId(actor, undefined, k.config);
292
+ const orgId = resolveOrgIdForCommerce(actor, k.config);
292
293
  try {
293
294
  return await runWithPluginDatabaseScope(orgId, async () => {
294
295
  const out = originalHandler(...args);
@@ -305,19 +306,22 @@ export function defineCommercePlugin(
305
306
  throw err; // re-throw so global handler still catches
306
307
  }
307
308
  };
309
+ const registeredHandler = isRoutePermissionGuard(originalHandler)
310
+ ? markRoutePermissionGuard(wrappedHandler)
311
+ : wrappedHandler;
308
312
 
309
313
  if ("openapi" in route) {
310
314
  // OpenAPI route: validated by Zod, appears in /api/doc
311
315
  // Hono type interop — OpenAPIHono.openapi() expects strict RouteConfig+Handler
312
316
  // generics that can't be statically resolved for dynamic plugin routes.
313
317
  // @ts-expect-error -- dynamic plugin routes cannot satisfy Hono's strict handler generics
314
- (app as OpenAPIHono).openapi(route.openapi, wrappedHandler);
318
+ (app as OpenAPIHono).openapi(route.openapi, registeredHandler);
315
319
  } else {
316
320
  // Legacy route: raw handler, invisible to OpenAPI spec.
317
321
  // Explicit dispatch avoids casting Hono to a Record.
318
322
  // Handler cast is a single `as any` (Hono's overloaded method
319
323
  // signatures can't unify with our generic handler shape).
320
- const h = wrappedHandler as any;
324
+ const h = registeredHandler as any;
321
325
  switch (route.method.toLowerCase()) {
322
326
  case "get": app.get(route.path, h); break;
323
327
  case "post": app.post(route.path, h); break;
@@ -9,7 +9,7 @@
9
9
  import type { CommerceConfig } from "../../config/types.js";
10
10
  import { CommerceValidationError } from "../../kernel/errors.js";
11
11
  import { Err, Ok, type Result } from "../../kernel/result.js";
12
- import { resolveOrgId } from "../../auth/org.js";
12
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
13
13
  import type { Actor } from "../../auth/types.js";
14
14
  import type { ReportParams, RetailReportsEngine } from "./reports.js";
15
15
  import { RETAIL_REPORTS } from "./reports.js";
@@ -135,7 +135,7 @@ export class AnalyticsService {
135
135
  if (!this.deps.reports) {
136
136
  return Err(new CommerceValidationError("Retail reports are not available."));
137
137
  }
138
- const orgId = resolveOrgId(actor ?? null);
138
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
139
139
  return this.deps.reports.run(name, params, orgId);
140
140
  }
141
141
 
@@ -3,7 +3,7 @@ import type { InferSelectModel } from "drizzle-orm";
3
3
  import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
4
4
  import type { TxContext } from "../../kernel/database/tx-context.js";
5
5
  import type { HookContext } from "../../kernel/hooks/types.js";
6
- import { resolveOrgId } from "../../auth/org.js";
6
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
7
7
  import { auditLog } from "./schema.js";
8
8
 
9
9
  export type AuditEntry = InferSelectModel<typeof auditLog>;
@@ -47,7 +47,7 @@ export function createNullAuditService(): AuditService {
47
47
  async record(args) {
48
48
  entries.push({
49
49
  id: crypto.randomUUID(),
50
- organizationId: resolveOrgId(args.ctx.actor),
50
+ organizationId: resolveOrgIdForCommerce(args.ctx.actor, args.ctx.commerceConfig),
51
51
  entityType: args.entityType,
52
52
  entityId: args.entityId,
53
53
  event: args.event,
@@ -94,7 +94,7 @@ export function createAuditService(db: DrizzleDatabase): AuditService {
94
94
  : db;
95
95
 
96
96
  await dbOrTx.insert(auditLog).values({
97
- organizationId: resolveOrgId(ctx.actor),
97
+ organizationId: resolveOrgIdForCommerce(ctx.actor, ctx.commerceConfig),
98
98
  entityType,
99
99
  entityId,
100
100
  event,
@@ -130,34 +130,48 @@ export class CartRepository {
130
130
  }
131
131
 
132
132
  /**
133
- * Atomically transitions a cart from "active" to "checking_out".
134
- * Returns the updated cart if the transition succeeded, or undefined if
135
- * the cart was not in "active" status (e.g., a concurrent checkout already
136
- * claimed it). This prevents TOCTOU race conditions on double-checkout.
133
+ * Atomically transitions a cart from "active" to "checking_out" and records
134
+ * `claimToken` as the holder of that claim. Returns the updated cart if the
135
+ * transition succeeded, or undefined if the cart was not in "active" status
136
+ * (e.g., a concurrent checkout already claimed it). This prevents TOCTOU race
137
+ * conditions on double-checkout.
137
138
  */
138
139
  async transitionToCheckingOut(
139
140
  id: string,
141
+ claimToken: string,
140
142
  ctx?: TxContext,
141
143
  ): Promise<Cart | undefined> {
142
144
  const db = this.getDb(ctx);
143
145
  const rows = await db
144
146
  .update(carts)
145
- .set({ status: "checking_out", updatedAt: new Date() })
147
+ .set({ status: "checking_out", checkoutClaimToken: claimToken, updatedAt: new Date() })
146
148
  .where(and(eq(carts.id, id), eq(carts.status, "active")))
147
149
  .returning();
148
150
  return rows[0];
149
151
  }
150
152
 
151
- /** Release only an in-progress checkout claim; terminal cart states are untouched. */
153
+ /**
154
+ * Release an in-progress checkout claim held by `claimToken`. Terminal cart
155
+ * states are untouched, and so is a claim held by a different checkout
156
+ * attempt — a losing attempt's failure path must not reopen the cart the
157
+ * winner is still checking out.
158
+ */
152
159
  async releaseCheckoutClaim(
153
160
  id: string,
161
+ claimToken: string,
154
162
  ctx?: TxContext,
155
163
  ): Promise<Cart | undefined> {
156
164
  const db = this.getDb(ctx);
157
165
  const rows = await db
158
166
  .update(carts)
159
- .set({ status: "active", updatedAt: new Date() })
160
- .where(and(eq(carts.id, id), eq(carts.status, "checking_out")))
167
+ .set({ status: "active", checkoutClaimToken: null, updatedAt: new Date() })
168
+ .where(
169
+ and(
170
+ eq(carts.id, id),
171
+ eq(carts.status, "checking_out"),
172
+ eq(carts.checkoutClaimToken, claimToken),
173
+ ),
174
+ )
161
175
  .returning();
162
176
  return rows[0];
163
177
  }
@@ -15,6 +15,10 @@ export const carts = pgTable("carts", {
15
15
  // Shopper contact for guest carts — enables abandoned-checkout recovery
16
16
  email: text("email"),
17
17
  secret: text("secret"),
18
+ // Owner of an in-progress `checking_out` claim. Minted by the checkout
19
+ // attempt that wins the CAS; only that holder may release the claim, so a
20
+ // losing attempt's failure path cannot unwind the winner's claim.
21
+ checkoutClaimToken: text("checkout_claim_token"),
18
22
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
19
23
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
20
24
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
@@ -1,4 +1,4 @@
1
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
2
2
  import { assertOwnership, assertPermission } from "../../auth/permissions.js";
3
3
  import type { Actor } from "../../auth/types.js";
4
4
  import type { CommerceConfig } from "../../config/types.js";
@@ -60,6 +60,7 @@ function makeContext(
60
60
  actor: Actor | null,
61
61
  services: Record<string, unknown>,
62
62
  database: DatabaseAdapter,
63
+ config: CommerceConfig,
63
64
  tx: unknown = null,
64
65
  ): HookContext {
65
66
  return createHookContext({
@@ -69,6 +70,7 @@ function makeContext(
69
70
  services,
70
71
  context: { moduleName: "cart" },
71
72
  database: { db: database.db as PluginDb },
73
+ commerceConfig: config,
72
74
  });
73
75
  }
74
76
 
@@ -98,7 +100,7 @@ export class CartService {
98
100
 
99
101
  const ttlMinutes = this.deps.config.cart?.ttlMinutes ?? 60 * 24 * 7;
100
102
  const now = new Date();
101
- const orgId = resolveOrgId(actor ?? null);
103
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
102
104
 
103
105
  // SECURITY: cart.customerId is service-managed.
104
106
  //
@@ -145,6 +147,7 @@ export class CartService {
145
147
  }
146
148
  }
147
149
 
150
+ const guestSecret = resolvedCustomerId === null ? crypto.randomUUID() : undefined;
148
151
  const cart = await this.repo.create(
149
152
  {
150
153
  organizationId: orgId,
@@ -153,6 +156,7 @@ export class CartService {
153
156
  metadata: input.metadata ?? {},
154
157
  expiresAt: new Date(now.getTime() + ttlMinutes * 60 * 1000),
155
158
  ...(resolvedCustomerId !== null ? { customerId: resolvedCustomerId } : {}),
159
+ ...(guestSecret !== undefined ? { secret: guestSecret } : {}),
156
160
  ...(input.email !== undefined ? { email: input.email } : {}),
157
161
  },
158
162
  ctx,
@@ -173,7 +177,7 @@ export class CartService {
173
177
  // read any cart's contents, prices, line items, and metadata. This now
174
178
  // requires either an authenticated owner/staff actor OR the cart's
175
179
  // secret token (returned only at createGuest()).
176
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
180
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
177
181
  const cart = await this.repo.findById(orgId, id, ctx);
178
182
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
179
183
 
@@ -195,10 +199,31 @@ export class CartService {
195
199
  });
196
200
  }
197
201
 
202
+ /**
203
+ * Reads a cart for a trusted server pipeline after the request boundary has
204
+ * already authenticated the caller. Organization scope is explicit; caller
205
+ * identity and guest secrets are intentionally not consulted here.
206
+ */
207
+ async getByIdForInternalUse(
208
+ id: string,
209
+ organizationId: string,
210
+ ctx?: TxContext,
211
+ ): Promise<Result<Cart & { lineItems: CartLineItem[] }>> {
212
+ const cart = await this.repo.findById(organizationId, id, ctx);
213
+ if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
214
+
215
+ const lineItems = await this.repo.findLineItemsByCartId(id, ctx);
216
+ return Ok({
217
+ ...cart,
218
+ lineItems,
219
+ });
220
+ }
221
+
198
222
  async addItem(
199
223
  input: AddCartItemInput,
200
224
  actor?: Actor | null,
201
225
  ctx?: TxContext,
226
+ presentedSecret?: string,
202
227
  ): Promise<Result<CartLineItem>> {
203
228
  try {
204
229
  assertPermission(actor ?? null, "cart:update");
@@ -206,12 +231,12 @@ export class CartService {
206
231
  return Err(toCommerceError(error));
207
232
  }
208
233
 
209
- const orgId = resolveOrgId(actor ?? null);
234
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
210
235
  const cart = await this.repo.findById(orgId, input.cartId, ctx);
211
236
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
212
237
 
213
238
  try {
214
- await this.assertCartOwnership(actor ?? null, cart, ctx);
239
+ await this.assertCartOwnership(actor ?? null, cart, ctx, presentedSecret);
215
240
  } catch (error) {
216
241
  return Err(toCommerceError(error));
217
242
  }
@@ -253,7 +278,7 @@ export class CartService {
253
278
  );
254
279
  }
255
280
 
256
- const context = makeContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx);
281
+ const context = makeContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx);
257
282
  const beforeHooks = this.deps.hooks.resolve(
258
283
  "cart.beforeAddItem",
259
284
  ) as CartAddBeforeHook[];
@@ -319,6 +344,7 @@ export class CartService {
319
344
  itemId: string,
320
345
  actor?: Actor | null,
321
346
  ctx?: TxContext,
347
+ presentedSecret?: string,
322
348
  ): Promise<Result<void>> {
323
349
  try {
324
350
  assertPermission(actor ?? null, "cart:update");
@@ -326,12 +352,12 @@ export class CartService {
326
352
  return Err(toCommerceError(error));
327
353
  }
328
354
 
329
- const orgId = resolveOrgId(actor ?? null);
355
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
330
356
  const cart = await this.repo.findById(orgId, cartId, ctx);
331
357
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
332
358
 
333
359
  try {
334
- await this.assertCartOwnership(actor ?? null, cart, ctx);
360
+ await this.assertCartOwnership(actor ?? null, cart, ctx, presentedSecret);
335
361
  } catch (error) {
336
362
  return Err(toCommerceError(error));
337
363
  }
@@ -341,7 +367,7 @@ export class CartService {
341
367
  return Err(new CommerceNotFoundError("Cart item not found."));
342
368
  }
343
369
 
344
- const context = makeContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx);
370
+ const context = makeContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx);
345
371
  const beforeHooks = this.deps.hooks.resolve(
346
372
  "cart.beforeRemoveItem",
347
373
  ) as CartRemoveBeforeHook[];
@@ -360,6 +386,7 @@ export class CartService {
360
386
  input: UpdateCartItemInput,
361
387
  actor?: Actor | null,
362
388
  ctx?: TxContext,
389
+ presentedSecret?: string,
363
390
  ): Promise<Result<CartLineItem>> {
364
391
  try {
365
392
  assertPermission(actor ?? null, "cart:update");
@@ -367,12 +394,12 @@ export class CartService {
367
394
  return Err(toCommerceError(error));
368
395
  }
369
396
 
370
- const orgId = resolveOrgId(actor ?? null);
397
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
371
398
  const cart = await this.repo.findById(orgId, input.cartId, ctx);
372
399
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
373
400
 
374
401
  try {
375
- await this.assertCartOwnership(actor ?? null, cart, ctx);
402
+ await this.assertCartOwnership(actor ?? null, cart, ctx, presentedSecret);
376
403
  } catch (error) {
377
404
  return Err(toCommerceError(error));
378
405
  }
@@ -388,7 +415,7 @@ export class CartService {
388
415
  );
389
416
  }
390
417
 
391
- const context = makeContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx);
418
+ const context = makeContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx);
392
419
  const beforeHooks = this.deps.hooks.resolve(
393
420
  "cart.beforeUpdateQuantity",
394
421
  ) as CartUpdateBeforeHook[];
@@ -424,7 +451,7 @@ export class CartService {
424
451
  return Err(toCommerceError(error));
425
452
  }
426
453
 
427
- const orgId = resolveOrgId(actor ?? null);
454
+ const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
428
455
  const source = await this.repo.findById(orgId, sourceCartId, ctx);
429
456
  const target = await this.repo.findById(orgId, targetCartId, ctx);
430
457
  if (!source || !target)
@@ -479,7 +506,7 @@ export class CartService {
479
506
  } catch (error) {
480
507
  return Err(toCommerceError(error));
481
508
  }
482
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
509
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
483
510
  const rows = await this.repo.list(
484
511
  orgId,
485
512
  {
@@ -518,7 +545,7 @@ export class CartService {
518
545
  } catch (error) {
519
546
  return Err(toCommerceError(error));
520
547
  }
521
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
548
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
522
549
  const cart = await this.repo.findById(orgId, cartId, ctx);
523
550
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
524
551
  if (cart.status === "checked_out" || cart.status === "merged" || cart.status === "checking_out") {
@@ -544,7 +571,7 @@ export class CartService {
544
571
  null,
545
572
  updated ?? cart,
546
573
  "recover",
547
- makeContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null),
574
+ makeContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null),
548
575
  );
549
576
 
550
577
  return Ok({
@@ -558,7 +585,7 @@ export class CartService {
558
585
  }
559
586
 
560
587
  async abandon(cartId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>> {
561
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
588
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
562
589
  const cart = await this.repo.findById(orgId, cartId, ctx);
563
590
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
564
591
 
@@ -571,24 +598,27 @@ export class CartService {
571
598
  actor?: Actor | null,
572
599
  ctx?: TxContext,
573
600
  ): Promise<Result<void>> {
574
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
601
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
575
602
  const cart = await this.repo.findById(orgId, cartId, ctx);
576
603
  if (!cart) return Err(new CommerceNotFoundError("Cart not found."));
577
604
 
578
- await this.repo.updateStatus(cartId, "checked_out", ctx);
605
+ await this.repo.update(cartId, { status: "checked_out", checkoutClaimToken: null }, ctx);
579
606
  return Ok(undefined);
580
607
  }
581
608
 
582
609
  /**
583
- * Atomically transitions a cart from "active" to "checking_out".
584
- * Returns Err if the cart was already claimed by a concurrent checkout.
585
- * This prevents TOCTOU race conditions on double-checkout.
610
+ * Atomically transitions a cart from "active" to "checking_out", recording
611
+ * `claimToken` as the holder. Returns Err if the cart was already claimed by
612
+ * a concurrent checkout. This prevents TOCTOU race conditions on
613
+ * double-checkout, and the token is what lets {@link releaseCheckoutClaim}
614
+ * tell the winner's release from a loser's.
586
615
  */
587
616
  async claimForCheckout(
588
617
  cartId: string,
618
+ claimToken: string,
589
619
  ctx?: TxContext,
590
620
  ): Promise<Result<Cart>> {
591
- const claimed = await this.repo.transitionToCheckingOut(cartId, ctx);
621
+ const claimed = await this.repo.transitionToCheckingOut(cartId, claimToken, ctx);
592
622
  if (!claimed) {
593
623
  return Err(
594
624
  new CommerceValidationError(
@@ -601,13 +631,17 @@ export class CartService {
601
631
 
602
632
  /**
603
633
  * Atomically returns a failed checkout claim to `active`. A checked-out,
604
- * merged, abandoned, or already-active cart is left untouched.
634
+ * merged, abandoned, or already-active cart is left untouched — and so is a
635
+ * claim held by a different checkout attempt, so a losing attempt's failure
636
+ * path cannot reopen the cart the winner is still checking out. Returns null
637
+ * when nothing was released.
605
638
  */
606
639
  async releaseCheckoutClaim(
607
640
  cartId: string,
641
+ claimToken: string,
608
642
  ctx?: TxContext,
609
643
  ): Promise<Result<Cart | null>> {
610
- return Ok((await this.repo.releaseCheckoutClaim(cartId, ctx)) ?? null);
644
+ return Ok((await this.repo.releaseCheckoutClaim(cartId, claimToken, ctx)) ?? null);
611
645
  }
612
646
 
613
647
  /**
@@ -625,7 +659,8 @@ export class CartService {
625
659
 
626
660
  const cart = await this.repo.create(
627
661
  {
628
- organizationId: resolveOrgId(null),
662
+ // Actor-less by design; resolves to the deployment's declared organization.
663
+ organizationId: resolveOrgIdForCommerce(null, this.deps.config),
629
664
  customerId: undefined,
630
665
  status: "active",
631
666
  currency,
@@ -651,7 +686,7 @@ export class CartService {
651
686
  actor: Actor,
652
687
  ctx?: TxContext,
653
688
  ): Promise<Result<Cart>> {
654
- const orgId = resolveOrgId(actor);
689
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
655
690
  const sourceCart = await this.repo.findById(orgId, sourceCartId, ctx);
656
691
  if (!sourceCart || sourceCart.secret !== sourceSecret) {
657
692
  return Err(
@@ -1,4 +1,4 @@
1
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
2
2
  import { assertPermission } from "../../auth/permissions.js";
3
3
  import type { Actor } from "../../auth/types.js";
4
4
  import {
@@ -23,14 +23,14 @@ export class BrandService {
23
23
 
24
24
  private assertSameOrg(resource: { organizationId?: string | null } | undefined, actor: Actor | null): void {
25
25
  if (!resource) return;
26
- const orgId = resolveOrgId(actor);
26
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
27
27
  if (resource.organizationId && resource.organizationId !== orgId) {
28
28
  throw new CommerceNotFoundError("Entity not found.");
29
29
  }
30
30
  }
31
31
 
32
32
  async listBrands(ctx?: TxContext): Promise<Result<Brand[]>> {
33
- const allBrands = await this.repo.findAllBrands(resolveOrgId(ctx?.actor ?? null), ctx);
33
+ const allBrands = await this.repo.findAllBrands(resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config), ctx);
34
34
  return Ok(allBrands.sort((a, b) => a.displayName.localeCompare(b.displayName)));
35
35
  }
36
36
 
@@ -40,7 +40,7 @@ export class BrandService {
40
40
  const existingById = await this.repo.findBrandById(input.id, ctx);
41
41
  if (existingById) return Err(new CommerceConflictError(`Brand with id ${input.id} already exists.`));
42
42
  }
43
- const orgId = resolveOrgId(actor);
43
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
44
44
  const existingBySlug = await this.repo.findBrandBySlug(orgId, input.slug, ctx);
45
45
  if (existingBySlug) return Err(new CommerceConflictError(`Brand with slug ${input.slug} already exists.`));
46
46
  return Ok(await this.repo.createBrand({ organizationId: orgId, ...(input.id ? { id: input.id } : {}), slug: input.slug, displayName: input.displayName, metadata: input.metadata ?? {} }, ctx));
@@ -52,7 +52,7 @@ export class BrandService {
52
52
  if (!existing) return Err(new CommerceNotFoundError("Brand not found."));
53
53
  try { this.assertSameOrg(existing, actor); } catch (error) { return Err(toCommerceError(error)); }
54
54
  if (input.slug) {
55
- const existingBySlug = await this.repo.findBrandBySlug(resolveOrgId(actor), input.slug, ctx);
55
+ const existingBySlug = await this.repo.findBrandBySlug(resolveOrgIdForCommerce(actor, this.deps.config), input.slug, ctx);
56
56
  if (existingBySlug && existingBySlug.id !== id) return Err(new CommerceConflictError(`Brand with slug ${input.slug} already exists.`));
57
57
  }
58
58
  const updated = await this.repo.updateBrand(id, { ...(input.slug !== undefined ? { slug: input.slug } : {}), ...(input.displayName !== undefined ? { displayName: input.displayName } : {}), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}) }, ctx);
@@ -75,7 +75,7 @@ export class BrandService {
75
75
  const entity = await this.deps.repository.findEntityById(entityId, ctx);
76
76
  if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
77
77
  try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
78
- const addBrandOrgId = resolveOrgId(actor ?? ctx?.actor ?? null);
78
+ const addBrandOrgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
79
79
  const isBrandUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(brandId);
80
80
  let brand = isBrandUuid ? await this.repo.findBrandById(brandId, ctx) : null;
81
81
  if (!brand) brand = await this.repo.findBrandBySlug(addBrandOrgId, brandId, ctx);
@@ -93,7 +93,7 @@ export class BrandService {
93
93
  try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
94
94
  const isBrUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(brandId);
95
95
  let brand = isBrUuid ? await this.repo.findBrandById(brandId, ctx) : null;
96
- if (!brand) brand = await this.repo.findBrandBySlug(resolveOrgId(actor ?? ctx?.actor ?? null), brandId, ctx);
96
+ if (!brand) brand = await this.repo.findBrandBySlug(resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config), brandId, ctx);
97
97
  const removed = await this.repo.removeEntityFromBrand(entityId, brand?.id ?? brandId, ctx);
98
98
  if (!removed) return Err(new CommerceNotFoundError("Brand assignment not found."));
99
99
  return Ok(undefined);
@@ -1,4 +1,4 @@
1
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
2
2
  import { assertPermission } from "../../auth/permissions.js";
3
3
  import type { Actor } from "../../auth/types.js";
4
4
  import {
@@ -23,14 +23,14 @@ export class CategoryService {
23
23
 
24
24
  private assertSameOrg(resource: { organizationId?: string | null } | undefined, actor: Actor | null): void {
25
25
  if (!resource) return;
26
- const orgId = resolveOrgId(actor);
26
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
27
27
  if (resource.organizationId && resource.organizationId !== orgId) {
28
28
  throw new CommerceNotFoundError("Entity not found.");
29
29
  }
30
30
  }
31
31
 
32
32
  async listCategories(ctx?: TxContext, opts?: { includeArchived?: boolean }): Promise<Result<CategorySummary[]>> {
33
- const allCategories = await this.repo.findAllCategories(resolveOrgId(ctx?.actor ?? null), ctx);
33
+ const allCategories = await this.repo.findAllCategories(resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config), ctx);
34
34
  const visible = opts?.includeArchived
35
35
  ? allCategories
36
36
  : allCategories.filter((c) => (c.status ?? "active") === "active");
@@ -62,7 +62,7 @@ export class CategoryService {
62
62
  const existingById = await this.repo.findCategoryById(input.id, ctx);
63
63
  if (existingById) return Err(new CommerceConflictError(`Category with id ${input.id} already exists.`));
64
64
  }
65
- const orgId = resolveOrgId(actor);
65
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
66
66
  const existingBySlug = await this.repo.findCategoryBySlug(orgId, input.slug, ctx);
67
67
  if (existingBySlug) return Err(new CommerceConflictError(`Category with slug ${input.slug} already exists.`));
68
68
  const category = await this.repo.createCategory({ organizationId: orgId, ...(input.id ? { id: input.id } : {}), slug: input.slug, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, ...(input.parentId !== undefined ? { parentId: input.parentId } : {}) }, ctx);
@@ -75,7 +75,7 @@ export class CategoryService {
75
75
  if (!existing) return Err(new CommerceNotFoundError("Category not found."));
76
76
  try { this.assertSameOrg(existing, actor); } catch (error) { return Err(toCommerceError(error)); }
77
77
  if (input.slug) {
78
- const existingBySlug = await this.repo.findCategoryBySlug(resolveOrgId(actor), input.slug, ctx);
78
+ const existingBySlug = await this.repo.findCategoryBySlug(resolveOrgIdForCommerce(actor, this.deps.config), input.slug, ctx);
79
79
  if (existingBySlug && existingBySlug.id !== id) return Err(new CommerceConflictError(`Category with slug ${input.slug} already exists.`));
80
80
  }
81
81
  const updated = await this.repo.updateCategory(id, { ...(input.slug !== undefined ? { slug: input.slug } : {}), ...(input.sortOrder !== undefined ? { sortOrder: input.sortOrder } : {}), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}), ...(input.parentId !== undefined ? { parentId: input.parentId } : {}) }, ctx);
@@ -98,7 +98,7 @@ export class CategoryService {
98
98
  const entity = await this.deps.repository.findEntityById(entityId, ctx);
99
99
  if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
100
100
  try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
101
- const addCatOrgId = resolveOrgId(actor ?? ctx?.actor ?? null);
101
+ const addCatOrgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
102
102
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(categoryId);
103
103
  let category = isUuid ? await this.repo.findCategoryById(categoryId, ctx) : null;
104
104
  if (!category) category = await this.repo.findCategoryBySlug(addCatOrgId, categoryId, ctx);
@@ -116,7 +116,7 @@ export class CategoryService {
116
116
  try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
117
117
  const isCatUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(categoryId);
118
118
  let category = isCatUuid ? await this.repo.findCategoryById(categoryId, ctx) : null;
119
- if (!category) category = await this.repo.findCategoryBySlug(resolveOrgId(actor ?? ctx?.actor ?? null), categoryId, ctx);
119
+ if (!category) category = await this.repo.findCategoryBySlug(resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config), categoryId, ctx);
120
120
  const removed = await this.repo.removeEntityFromCategory(entityId, category?.id ?? categoryId, ctx);
121
121
  if (!removed) return Err(new CommerceNotFoundError("Category assignment not found."));
122
122
  return Ok(undefined);