@porulle/core 0.10.8 → 0.13.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 (360) hide show
  1. package/dist/auth/auth-schema-guard.d.ts +2444 -0
  2. package/dist/auth/auth-schema-guard.d.ts.map +1 -0
  3. package/dist/auth/auth-schema-guard.js +113 -0
  4. package/dist/auth/auth-schema.d.ts +228 -0
  5. package/dist/auth/auth-schema.d.ts.map +1 -1
  6. package/dist/auth/auth-schema.js +24 -2
  7. package/dist/auth/middleware.d.ts +1 -0
  8. package/dist/auth/middleware.d.ts.map +1 -1
  9. package/dist/auth/middleware.js +13 -5
  10. package/dist/auth/org.d.ts +8 -2
  11. package/dist/auth/org.d.ts.map +1 -1
  12. package/dist/auth/org.js +12 -4
  13. package/dist/auth/organization-guard.d.ts +5 -0
  14. package/dist/auth/organization-guard.d.ts.map +1 -0
  15. package/dist/auth/organization-guard.js +124 -0
  16. package/dist/auth/permissions.d.ts +10 -0
  17. package/dist/auth/permissions.d.ts.map +1 -1
  18. package/dist/auth/permissions.js +31 -7
  19. package/dist/auth/role-authority.d.ts +44 -0
  20. package/dist/auth/role-authority.d.ts.map +1 -0
  21. package/dist/auth/role-authority.js +85 -0
  22. package/dist/auth/setup.d.ts.map +1 -1
  23. package/dist/auth/setup.js +17 -0
  24. package/dist/auth/strict-org-resolution.d.ts.map +1 -1
  25. package/dist/auth/strict-org-resolution.js +9 -1
  26. package/dist/auth/types.d.ts +1 -1
  27. package/dist/auth/types.d.ts.map +1 -1
  28. package/dist/config/defaults.d.ts.map +1 -1
  29. package/dist/config/defaults.js +2 -0
  30. package/dist/config/types.d.ts +33 -4
  31. package/dist/config/types.d.ts.map +1 -1
  32. package/dist/hooks/checkout.d.ts.map +1 -1
  33. package/dist/hooks/checkout.js +10 -9
  34. package/dist/index.d.ts +9 -6
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +5 -3
  37. package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
  38. package/dist/interfaces/rest/audit-middleware.js +2 -0
  39. package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
  40. package/dist/interfaces/rest/customer-portal.js +9 -8
  41. package/dist/interfaces/rest/index.d.ts.map +1 -1
  42. package/dist/interfaces/rest/index.js +4 -0
  43. package/dist/interfaces/rest/route-coverage.d.ts +14 -0
  44. package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
  45. package/dist/interfaces/rest/route-coverage.js +81 -0
  46. package/dist/interfaces/rest/router.d.ts +7 -2
  47. package/dist/interfaces/rest/router.d.ts.map +1 -1
  48. package/dist/interfaces/rest/router.js +8 -4
  49. package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
  50. package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
  51. package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
  52. package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
  53. package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
  54. package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
  55. package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
  56. package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
  57. package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
  58. package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
  59. package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
  60. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
  61. package/dist/interfaces/rest/routes/admin/staff.js +126 -66
  62. package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
  63. package/dist/interfaces/rest/routes/audit.js +3 -3
  64. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  65. package/dist/interfaces/rest/routes/carts.js +13 -10
  66. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  67. package/dist/interfaces/rest/routes/catalog.js +47 -4
  68. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  69. package/dist/interfaces/rest/routes/checkout.js +30 -35
  70. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
  71. package/dist/interfaces/rest/routes/documents.js +10 -5
  72. package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
  73. package/dist/interfaces/rest/routes/inventory.js +2 -0
  74. package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
  75. package/dist/interfaces/rest/routes/media.js +7 -3
  76. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  77. package/dist/interfaces/rest/routes/orders.js +26 -8
  78. package/dist/interfaces/rest/routes/pricing.js +2 -2
  79. package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
  80. package/dist/interfaces/rest/routes/promotions.js +13 -10
  81. package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
  82. package/dist/interfaces/rest/routes/search.js +14 -1
  83. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
  84. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
  85. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
  86. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
  87. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
  88. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
  89. package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
  90. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  91. package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
  92. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  93. package/dist/interfaces/rest/schemas/catalog.js +86 -0
  94. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  95. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  96. package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
  97. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  98. package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
  99. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  100. package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
  101. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  102. package/dist/interfaces/rest/schemas/search.d.ts +1 -0
  103. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  104. package/dist/interfaces/rest/schemas/search.js +1 -0
  105. package/dist/interfaces/rest/utils.d.ts +15 -0
  106. package/dist/interfaces/rest/utils.d.ts.map +1 -1
  107. package/dist/interfaces/rest/utils.js +45 -2
  108. package/dist/kernel/compensation/executor.js +2 -2
  109. package/dist/kernel/database/tx-context.d.ts +8 -0
  110. package/dist/kernel/database/tx-context.d.ts.map +1 -1
  111. package/dist/kernel/database/tx-context.js +13 -0
  112. package/dist/kernel/hooks/create-context.d.ts +2 -0
  113. package/dist/kernel/hooks/create-context.d.ts.map +1 -1
  114. package/dist/kernel/hooks/create-context.js +1 -0
  115. package/dist/kernel/hooks/types.d.ts +2 -0
  116. package/dist/kernel/hooks/types.d.ts.map +1 -1
  117. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  118. package/dist/kernel/plugin/manifest.js +9 -5
  119. package/dist/modules/analytics/service.js +2 -2
  120. package/dist/modules/audit/service.js +3 -3
  121. package/dist/modules/cart/repository/index.d.ts +13 -7
  122. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  123. package/dist/modules/cart/repository/index.js +16 -10
  124. package/dist/modules/cart/schema.d.ts +17 -0
  125. package/dist/modules/cart/schema.d.ts.map +1 -1
  126. package/dist/modules/cart/schema.js +4 -0
  127. package/dist/modules/cart/service.d.ts +22 -9
  128. package/dist/modules/cart/service.d.ts.map +1 -1
  129. package/dist/modules/cart/service.js +57 -33
  130. package/dist/modules/catalog/brand-service.js +7 -7
  131. package/dist/modules/catalog/category-service.js +7 -7
  132. package/dist/modules/catalog/entity-service.d.ts +19 -10
  133. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  134. package/dist/modules/catalog/entity-service.js +180 -51
  135. package/dist/modules/catalog/module.d.ts +6 -1
  136. package/dist/modules/catalog/module.d.ts.map +1 -1
  137. package/dist/modules/catalog/module.js +6 -1
  138. package/dist/modules/catalog/ownership.d.ts +5 -0
  139. package/dist/modules/catalog/ownership.d.ts.map +1 -0
  140. package/dist/modules/catalog/ownership.js +11 -0
  141. package/dist/modules/catalog/read-policy.d.ts +9 -0
  142. package/dist/modules/catalog/read-policy.d.ts.map +1 -0
  143. package/dist/modules/catalog/read-policy.js +8 -0
  144. package/dist/modules/catalog/repository/index.d.ts +55 -3
  145. package/dist/modules/catalog/repository/index.d.ts.map +1 -1
  146. package/dist/modules/catalog/repository/index.js +335 -6
  147. package/dist/modules/catalog/revision.d.ts +4 -0
  148. package/dist/modules/catalog/revision.d.ts.map +1 -0
  149. package/dist/modules/catalog/revision.js +33 -0
  150. package/dist/modules/catalog/schema.d.ts +979 -110
  151. package/dist/modules/catalog/schema.d.ts.map +1 -1
  152. package/dist/modules/catalog/schema.js +112 -2
  153. package/dist/modules/catalog/schemas.d.ts +2 -0
  154. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  155. package/dist/modules/catalog/schemas.js +2 -0
  156. package/dist/modules/catalog/service.d.ts +80 -15
  157. package/dist/modules/catalog/service.d.ts.map +1 -1
  158. package/dist/modules/catalog/service.js +522 -14
  159. package/dist/modules/channels/adapter.d.ts +85 -0
  160. package/dist/modules/channels/adapter.d.ts.map +1 -1
  161. package/dist/modules/channels/adapter.js +9 -1
  162. package/dist/modules/customers/module.d.ts.map +1 -1
  163. package/dist/modules/customers/module.js +1 -0
  164. package/dist/modules/customers/service.d.ts +2 -0
  165. package/dist/modules/customers/service.d.ts.map +1 -1
  166. package/dist/modules/customers/service.js +21 -20
  167. package/dist/modules/documents/module.d.ts.map +1 -1
  168. package/dist/modules/documents/module.js +1 -0
  169. package/dist/modules/documents/service.d.ts +18 -4
  170. package/dist/modules/documents/service.d.ts.map +1 -1
  171. package/dist/modules/documents/service.js +54 -16
  172. package/dist/modules/fulfillment/module.d.ts.map +1 -1
  173. package/dist/modules/fulfillment/module.js +1 -0
  174. package/dist/modules/fulfillment/service.d.ts +2 -0
  175. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  176. package/dist/modules/fulfillment/service.js +15 -6
  177. package/dist/modules/inventory/service.d.ts.map +1 -1
  178. package/dist/modules/inventory/service.js +15 -14
  179. package/dist/modules/media/module.d.ts +68 -0
  180. package/dist/modules/media/module.d.ts.map +1 -1
  181. package/dist/modules/media/module.js +2 -0
  182. package/dist/modules/media/schema.d.ts +70 -0
  183. package/dist/modules/media/schema.d.ts.map +1 -1
  184. package/dist/modules/media/schema.js +20 -2
  185. package/dist/modules/media/service.d.ts +8 -1
  186. package/dist/modules/media/service.d.ts.map +1 -1
  187. package/dist/modules/media/service.js +71 -23
  188. package/dist/modules/orders/guest-access.d.ts +27 -0
  189. package/dist/modules/orders/guest-access.d.ts.map +1 -0
  190. package/dist/modules/orders/guest-access.js +46 -0
  191. package/dist/modules/orders/module.d.ts.map +1 -1
  192. package/dist/modules/orders/module.js +1 -0
  193. package/dist/modules/orders/quote.d.ts.map +1 -1
  194. package/dist/modules/orders/quote.js +1 -0
  195. package/dist/modules/orders/repository/index.d.ts +1 -1
  196. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  197. package/dist/modules/orders/repository/index.js +2 -2
  198. package/dist/modules/orders/schema.d.ts +17 -0
  199. package/dist/modules/orders/schema.d.ts.map +1 -1
  200. package/dist/modules/orders/schema.js +5 -3
  201. package/dist/modules/orders/service.d.ts +16 -7
  202. package/dist/modules/orders/service.d.ts.map +1 -1
  203. package/dist/modules/orders/service.js +184 -55
  204. package/dist/modules/pricing/module.d.ts.map +1 -1
  205. package/dist/modules/pricing/module.js +1 -0
  206. package/dist/modules/pricing/schema.d.ts +17 -0
  207. package/dist/modules/pricing/schema.d.ts.map +1 -1
  208. package/dist/modules/pricing/schema.js +1 -0
  209. package/dist/modules/pricing/schemas.d.ts +1 -0
  210. package/dist/modules/pricing/schemas.d.ts.map +1 -1
  211. package/dist/modules/pricing/schemas.js +1 -0
  212. package/dist/modules/pricing/service.d.ts +2 -0
  213. package/dist/modules/pricing/service.d.ts.map +1 -1
  214. package/dist/modules/pricing/service.js +18 -12
  215. package/dist/modules/promotions/module.d.ts.map +1 -1
  216. package/dist/modules/promotions/module.js +1 -0
  217. package/dist/modules/promotions/service.d.ts +3 -0
  218. package/dist/modules/promotions/service.d.ts.map +1 -1
  219. package/dist/modules/promotions/service.js +19 -11
  220. package/dist/modules/search/adapter.d.ts +8 -0
  221. package/dist/modules/search/adapter.d.ts.map +1 -1
  222. package/dist/modules/search/module.d.ts.map +1 -1
  223. package/dist/modules/search/module.js +3 -0
  224. package/dist/modules/search/service.d.ts +5 -0
  225. package/dist/modules/search/service.d.ts.map +1 -1
  226. package/dist/modules/search/service.js +131 -11
  227. package/dist/modules/settings/module.d.ts.map +1 -1
  228. package/dist/modules/settings/module.js +1 -0
  229. package/dist/modules/settings/service.d.ts +3 -0
  230. package/dist/modules/settings/service.d.ts.map +1 -1
  231. package/dist/modules/settings/service.js +6 -4
  232. package/dist/modules/shipping/service.js +9 -9
  233. package/dist/modules/tax/module.d.ts.map +1 -1
  234. package/dist/modules/tax/module.js +1 -0
  235. package/dist/modules/tax/service.d.ts +3 -0
  236. package/dist/modules/tax/service.d.ts.map +1 -1
  237. package/dist/modules/tax/service.js +11 -9
  238. package/dist/modules/webhooks/hook.js +2 -2
  239. package/dist/modules/webhooks/module.d.ts.map +1 -1
  240. package/dist/modules/webhooks/module.js +1 -0
  241. package/dist/modules/webhooks/service.d.ts +2 -0
  242. package/dist/modules/webhooks/service.d.ts.map +1 -1
  243. package/dist/modules/webhooks/service.js +4 -4
  244. package/dist/runtime/kernel-modules.d.ts +73 -0
  245. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  246. package/dist/runtime/server.d.ts.map +1 -1
  247. package/dist/runtime/server.js +10 -2
  248. package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
  249. package/dist/test-utils/create-pglite-adapter.js +35 -13
  250. package/dist/test-utils/create-test-config.d.ts.map +1 -1
  251. package/dist/test-utils/create-test-config.js +4 -0
  252. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  253. package/dist/test-utils/rest-api-test-utils.js +2 -0
  254. package/dist/test-utils/test-actors.js +2 -2
  255. package/dist/utils/id.d.ts +2 -0
  256. package/dist/utils/id.d.ts.map +1 -1
  257. package/dist/utils/id.js +11 -0
  258. package/package.json +6 -5
  259. package/src/auth/auth-schema-guard.ts +143 -0
  260. package/src/auth/auth-schema.ts +50 -19
  261. package/src/auth/middleware.ts +17 -5
  262. package/src/auth/org.ts +15 -3
  263. package/src/auth/organization-guard.ts +158 -0
  264. package/src/auth/permissions.ts +31 -8
  265. package/src/auth/role-authority.ts +107 -0
  266. package/src/auth/setup.ts +17 -0
  267. package/src/auth/strict-org-resolution.ts +7 -1
  268. package/src/auth/types.ts +1 -1
  269. package/src/config/defaults.ts +2 -0
  270. package/src/config/types.ts +32 -4
  271. package/src/hooks/checkout.ts +19 -12
  272. package/src/index.ts +30 -6
  273. package/src/interfaces/rest/audit-middleware.ts +2 -0
  274. package/src/interfaces/rest/customer-portal.ts +31 -24
  275. package/src/interfaces/rest/index.ts +4 -0
  276. package/src/interfaces/rest/route-coverage.ts +103 -0
  277. package/src/interfaces/rest/router.ts +16 -10
  278. package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
  279. package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
  280. package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
  281. package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
  282. package/src/interfaces/rest/routes/admin/staff.ts +161 -72
  283. package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
  284. package/src/interfaces/rest/routes/audit.ts +3 -3
  285. package/src/interfaces/rest/routes/carts.ts +17 -7
  286. package/src/interfaces/rest/routes/catalog.ts +96 -0
  287. package/src/interfaces/rest/routes/checkout.ts +41 -38
  288. package/src/interfaces/rest/routes/documents.ts +15 -1
  289. package/src/interfaces/rest/routes/inventory.ts +2 -0
  290. package/src/interfaces/rest/routes/media.ts +8 -3
  291. package/src/interfaces/rest/routes/orders.ts +27 -8
  292. package/src/interfaces/rest/routes/pricing.ts +2 -2
  293. package/src/interfaces/rest/routes/promotions.ts +12 -9
  294. package/src/interfaces/rest/routes/search.ts +19 -1
  295. package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
  296. package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
  297. package/src/interfaces/rest/schemas/catalog.ts +92 -0
  298. package/src/interfaces/rest/schemas/search.ts +2 -0
  299. package/src/interfaces/rest/utils.ts +61 -2
  300. package/src/kernel/compensation/executor.ts +2 -2
  301. package/src/kernel/database/tx-context.ts +25 -0
  302. package/src/kernel/hooks/create-context.ts +3 -0
  303. package/src/kernel/hooks/types.ts +2 -0
  304. package/src/kernel/plugin/manifest.ts +9 -5
  305. package/src/modules/analytics/service.ts +2 -2
  306. package/src/modules/audit/service.ts +3 -3
  307. package/src/modules/cart/repository/index.ts +22 -8
  308. package/src/modules/cart/schema.ts +4 -0
  309. package/src/modules/cart/service.ts +62 -27
  310. package/src/modules/catalog/brand-service.ts +7 -7
  311. package/src/modules/catalog/category-service.ts +7 -7
  312. package/src/modules/catalog/entity-service.ts +188 -53
  313. package/src/modules/catalog/module.ts +15 -0
  314. package/src/modules/catalog/ownership.ts +17 -0
  315. package/src/modules/catalog/read-policy.ts +16 -0
  316. package/src/modules/catalog/repository/index.ts +566 -4
  317. package/src/modules/catalog/revision.ts +35 -0
  318. package/src/modules/catalog/schema.ts +169 -1
  319. package/src/modules/catalog/schemas.ts +2 -0
  320. package/src/modules/catalog/service.ts +766 -26
  321. package/src/modules/channels/adapter.ts +107 -1
  322. package/src/modules/customers/module.ts +2 -0
  323. package/src/modules/customers/service.ts +23 -22
  324. package/src/modules/documents/module.ts +2 -0
  325. package/src/modules/documents/service.ts +74 -11
  326. package/src/modules/fulfillment/module.ts +2 -0
  327. package/src/modules/fulfillment/service.ts +17 -6
  328. package/src/modules/inventory/service.ts +15 -24
  329. package/src/modules/media/module.ts +2 -0
  330. package/src/modules/media/schema.ts +23 -2
  331. package/src/modules/media/service.ts +89 -28
  332. package/src/modules/orders/guest-access.ts +55 -0
  333. package/src/modules/orders/module.ts +1 -0
  334. package/src/modules/orders/quote.ts +1 -0
  335. package/src/modules/orders/repository/index.ts +2 -0
  336. package/src/modules/orders/schema.ts +5 -3
  337. package/src/modules/orders/service.ts +302 -52
  338. package/src/modules/pricing/module.ts +2 -0
  339. package/src/modules/pricing/schema.ts +1 -0
  340. package/src/modules/pricing/schemas.ts +1 -0
  341. package/src/modules/pricing/service.ts +21 -18
  342. package/src/modules/promotions/module.ts +2 -0
  343. package/src/modules/promotions/service.ts +25 -16
  344. package/src/modules/search/adapter.ts +8 -0
  345. package/src/modules/search/module.ts +3 -0
  346. package/src/modules/search/service.ts +142 -12
  347. package/src/modules/settings/module.ts +2 -0
  348. package/src/modules/settings/service.ts +8 -4
  349. package/src/modules/shipping/service.ts +9 -9
  350. package/src/modules/tax/module.ts +1 -0
  351. package/src/modules/tax/service.ts +13 -9
  352. package/src/modules/webhooks/hook.ts +2 -2
  353. package/src/modules/webhooks/module.ts +2 -0
  354. package/src/modules/webhooks/service.ts +6 -4
  355. package/src/runtime/server.ts +16 -2
  356. package/src/test-utils/create-pglite-adapter.ts +34 -12
  357. package/src/test-utils/create-test-config.ts +10 -6
  358. package/src/test-utils/rest-api-test-utils.ts +2 -0
  359. package/src/test-utils/test-actors.ts +2 -2
  360. package/src/utils/id.ts +20 -0
@@ -19,6 +19,17 @@ import {
19
19
 
20
20
  const DataResponse = CatalogEntityResponse;
21
21
  const DataWithPaginationResponse = CatalogEntityListResponse;
22
+ const FieldOwnershipSchema = z.object({
23
+ id: z.string(),
24
+ organizationId: z.string(),
25
+ entityId: z.string(),
26
+ variantId: z.string().nullable(),
27
+ storeId: z.string().nullable(),
28
+ fieldPath: z.string(),
29
+ owner: z.enum(["platform", "store", "shared"]),
30
+ updatedAt: z.string(),
31
+ }).openapi("CatalogFieldOwnership");
32
+ const FieldOwnershipListResponse = z.object({ data: z.array(FieldOwnershipSchema) }).openapi("CatalogFieldOwnershipListResponse");
22
33
 
23
34
  // ─── Path Params ────────────────────────────────────────────────────────────
24
35
 
@@ -35,6 +46,15 @@ const EntityIdLocaleParam = z.object({
35
46
  locale: z.string().min(1).openapi({ example: "en" }),
36
47
  });
37
48
 
49
+ const EntityCustomFieldParam = z.object({
50
+ id: z.uuid().openapi({ example: "550e8400-e29b-41d4-a716-446655440000" }),
51
+ fieldName: z.string().min(1).openapi({ example: "warranty" }),
52
+ });
53
+
54
+ const CustomFieldReviewQuery = z.object({
55
+ locale: z.string().min(1).optional().openapi({ example: "en" }),
56
+ });
57
+
38
58
  const CategoryIdParam = z.object({
39
59
  categoryId: z.uuid().openapi({ example: "550e8400-e29b-41d4-a716-446655440000" }),
40
60
  });
@@ -104,6 +124,48 @@ export const getEntityAttributesRoute = createRoute({
104
124
  },
105
125
  });
106
126
 
127
+ export const listFieldOwnershipRoute = createRoute({
128
+ method: "get",
129
+ path: "/entities/{id}/field-ownership",
130
+ tags: ["Catalog"],
131
+ summary: "List field ownership for a catalog entity",
132
+ request: {
133
+ params: EntityIdParam,
134
+ query: z.object({ storeId: z.string().optional() }),
135
+ },
136
+ responses: {
137
+ 200: { content: { "application/json": { schema: FieldOwnershipListResponse } }, description: "Success" },
138
+ ...errorResponses,
139
+ },
140
+ });
141
+
142
+ export const setFieldOwnershipRoute = createRoute({
143
+ method: "put",
144
+ path: "/entities/{id}/field-ownership",
145
+ tags: ["Catalog"],
146
+ summary: "Set field ownership for a catalog entity",
147
+ request: {
148
+ params: EntityIdParam,
149
+ body: {
150
+ content: {
151
+ "application/json": {
152
+ schema: z.object({
153
+ fieldPath: z.string().min(1),
154
+ owner: z.enum(["platform", "store", "shared"]),
155
+ storeId: z.string().min(1).nullable().optional(),
156
+ variantId: z.uuid().nullable().optional(),
157
+ }),
158
+ },
159
+ },
160
+ required: true,
161
+ },
162
+ },
163
+ responses: {
164
+ 200: { content: { "application/json": { schema: z.object({ data: z.object({ updated: z.literal(true) }) }) } }, description: "Updated" },
165
+ ...errorResponses,
166
+ },
167
+ });
168
+
107
169
  export const listCategoriesRoute = createRoute({
108
170
  method: "get",
109
171
  path: "/categories",
@@ -312,6 +374,36 @@ export const setEntityAttributesRoute = createRoute({
312
374
  },
313
375
  });
314
376
 
377
+ export const approveCustomFieldRoute = createRoute({
378
+ method: "post",
379
+ path: "/entities/{id}/custom-fields/{fieldName}/approve",
380
+ tags: ["Catalog"],
381
+ summary: "Approve a proposed custom-field value",
382
+ request: {
383
+ params: EntityCustomFieldParam,
384
+ query: CustomFieldReviewQuery,
385
+ },
386
+ responses: {
387
+ 200: { content: { "application/json": { schema: DataResponse } }, description: "Approved" },
388
+ ...errorResponses,
389
+ },
390
+ });
391
+
392
+ export const rejectCustomFieldRoute = createRoute({
393
+ method: "post",
394
+ path: "/entities/{id}/custom-fields/{fieldName}/reject",
395
+ tags: ["Catalog"],
396
+ summary: "Reject a proposed custom-field value",
397
+ request: {
398
+ params: EntityCustomFieldParam,
399
+ query: CustomFieldReviewQuery,
400
+ },
401
+ responses: {
402
+ 200: { content: { "application/json": { schema: DataResponse } }, description: "Rejected" },
403
+ ...errorResponses,
404
+ },
405
+ });
406
+
315
407
  export const createCategoryRoute = createRoute({
316
408
  method: "post",
317
409
  path: "/categories",
@@ -8,6 +8,8 @@ export const searchRoute = createRoute({
8
8
  path: "/",
9
9
  tags: ["Search"],
10
10
  summary: "Search catalog entities",
11
+ description:
12
+ "Attribute filters use repeatable `attr.<name>=<value>` query parameters: repeated values for one name are ORed, distinct names are ANDed. Attribute names must match `^[A-Za-z0-9_-]+$`; anything else returns 422. Request attribute facet counts by naming the attribute in `facets`.",
11
13
  request: {
12
14
  query: z.object({
13
15
  q: z.string().max(500).optional(),
@@ -3,6 +3,25 @@ import { mapErrorToStatus } from "../../kernel/error-mapper.js";
3
3
  import { toCommerceError } from "../../kernel/errors.js";
4
4
  import type { Actor } from "../../auth/types.js";
5
5
 
6
+ export const ROUTE_PERMISSION_GUARD = Symbol("porulle.routePermissionGuard");
7
+
8
+ type RouteGuardHandler = {
9
+ [ROUTE_PERMISSION_GUARD]?: { methods?: readonly string[] };
10
+ };
11
+
12
+ export function markRoutePermissionGuard<T>(handler: T, methods?: readonly string[]): T {
13
+ if (typeof handler === "function") {
14
+ Object.defineProperty(handler, ROUTE_PERMISSION_GUARD, { value: { methods } });
15
+ }
16
+ return handler;
17
+ }
18
+
19
+ type PermissionContext = {
20
+ get(key: string): unknown;
21
+ json(data: unknown, status: number): unknown;
22
+ req: { method: string };
23
+ };
24
+
6
25
  /**
7
26
  * Shared Hono environment type for all sub-routers.
8
27
  * Matches the Variables set by middleware in the top-level server app.
@@ -61,7 +80,7 @@ export { mapErrorToStatus };
61
80
  * Usage: router.post("/", requirePerm("webhooks:manage"), handler);
62
81
  */
63
82
  export function requirePerm(permission: string) {
64
- return async (c: { get(key: string): unknown; json(data: unknown, status: number): unknown }, next: () => Promise<void>) => {
83
+ const middleware = async (c: { get(key: string): unknown; json(data: unknown, status: number): unknown }, next: () => Promise<void>) => {
65
84
  const actor = c.get("actor") as { permissions?: string[] } | null;
66
85
  if (!actor) {
67
86
  return c.json({ error: { code: "UNAUTHORIZED", message: "Authentication required." } }, 401);
@@ -79,6 +98,46 @@ export function requirePerm(permission: string) {
79
98
  }
80
99
  return c.json({ error: { code: "FORBIDDEN", message: `Permission '${permission}' is required.` } }, 403);
81
100
  };
101
+ return markRoutePermissionGuard(middleware);
102
+ }
103
+
104
+ export function requireAnyPerm(permissions: readonly string[]) {
105
+ const middleware = async (c: { get(key: string): unknown; json(data: unknown, status: number): unknown }, next: () => Promise<void>) => {
106
+ const actor = c.get("actor") as { permissions?: string[] } | null;
107
+ const granted = actor?.permissions ?? [];
108
+ const allowed = permissions.some((permission) =>
109
+ granted.includes(permission) ||
110
+ granted.includes("*:*") ||
111
+ granted.includes(`${permission.split(":")[0]}:*`),
112
+ );
113
+ if (!actor) {
114
+ return c.json({ error: { code: "UNAUTHORIZED", message: "Authentication required." } }, 401);
115
+ }
116
+ if (allowed) {
117
+ await next();
118
+ return;
119
+ }
120
+ return c.json({ error: { code: "FORBIDDEN", message: `One of these permissions is required: ${permissions.join(", ")}.` } }, 403);
121
+ };
122
+ return markRoutePermissionGuard(middleware);
123
+ }
124
+
125
+ export function requireMethodPerm(methods: readonly string[], permission: string) {
126
+ const permissionGuard = requirePerm(permission);
127
+ const middleware = async (c: PermissionContext, next: () => Promise<void>) => {
128
+ if (!methods.includes(c.req.method)) {
129
+ await next();
130
+ return;
131
+ }
132
+ return permissionGuard(c, next);
133
+ };
134
+ return markRoutePermissionGuard(middleware, methods);
135
+ }
136
+
137
+ export function isRoutePermissionGuard(handler: unknown, method?: string): boolean {
138
+ if (typeof handler !== "function") return false;
139
+ const guard = (handler as unknown as RouteGuardHandler)[ROUTE_PERMISSION_GUARD];
140
+ return Boolean(guard && (!method || !guard.methods || guard.methods.includes(method)));
82
141
  }
83
142
 
84
143
  export function parseSort(
@@ -100,5 +159,5 @@ export function parseSort(
100
159
  }
101
160
 
102
161
  export function isUUID(value: string): boolean {
103
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
162
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
104
163
  }
@@ -1,4 +1,4 @@
1
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
2
2
  import type { CompensationContext, Step } from "./types.js";
3
3
  import type { Result } from "../result.js";
4
4
 
@@ -85,7 +85,7 @@ export async function runCompensationChain<TInput>(
85
85
  );
86
86
  if (ctx.failureRepository) {
87
87
  try {
88
- const orgId = resolveOrgId(ctx.hook.actor);
88
+ const orgId = resolveOrgIdForCommerce(ctx.hook.actor, ctx.hook.commerceConfig);
89
89
  const recordResult = await ctx.failureRepository.record(
90
90
  {
91
91
  organizationId: orgId,
@@ -6,11 +6,19 @@ export interface TxContext<TTx = unknown> {
6
6
  tx: TTx;
7
7
  actor: Actor | null;
8
8
  requestId: string;
9
+ hookContext?: Record<string, unknown>;
9
10
  }
10
11
 
12
+ export interface HookContextCarrier {
13
+ hookContext?: Record<string, unknown>;
14
+ }
15
+
16
+ export type CatalogWriteContext<TTx = unknown> = TxContext<TTx> | HookContextCarrier;
17
+
11
18
  export interface WithTransactionOptions {
12
19
  actor: Actor | null;
13
20
  requestId?: string;
21
+ hookContext?: Record<string, unknown>;
14
22
  }
15
23
 
16
24
  export function createTxContext<TTx>(
@@ -21,6 +29,7 @@ export function createTxContext<TTx>(
21
29
  tx,
22
30
  actor: options.actor,
23
31
  requestId: options.requestId ?? randomUUID(),
32
+ ...(options.hookContext ? { hookContext: options.hookContext } : {}),
24
33
  };
25
34
  }
26
35
 
@@ -44,3 +53,19 @@ export function reuseOrCreateTxContext<TTx>(
44
53
  }
45
54
  return createTxContext(tx, options);
46
55
  }
56
+
57
+ function hookContextFromWriteContext(ctx?: CatalogWriteContext): Record<string, unknown> | undefined {
58
+ return ctx?.hookContext;
59
+ }
60
+
61
+ function isTransactionalWriteContext<TTx>(ctx: CatalogWriteContext<TTx>): ctx is TxContext<TTx> {
62
+ return "tx" in ctx && ctx.tx != null;
63
+ }
64
+
65
+ export function resolveWriteContextHookContext(ctx?: CatalogWriteContext): Record<string, unknown> | undefined {
66
+ return hookContextFromWriteContext(ctx);
67
+ }
68
+
69
+ export function isWriteContextTransactional<TTx>(ctx?: CatalogWriteContext<TTx>): ctx is TxContext<TTx> {
70
+ return ctx != null && isTransactionalWriteContext(ctx);
71
+ }
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { Actor } from "../../auth/types.js";
3
+ import type { CommerceConfig } from "../../config/types.js";
3
4
  import type { JobsAdapter } from "../jobs/adapter.js";
4
5
  import { NullJobsAdapter } from "../jobs/adapter.js";
5
6
  import type { PluginDb } from "../database/plugin-types.js";
@@ -21,6 +22,7 @@ export interface CreateHookContextArgs {
21
22
  * @deprecated Pass {@link CreateHookContextArgs.database} or {@link CreateHookContextArgs.db} instead.
22
23
  */
23
24
  kernel?: { database: { db: PluginDb } };
25
+ commerceConfig?: CommerceConfig | null;
24
26
  }
25
27
 
26
28
  const nullJobs = new NullJobsAdapter();
@@ -48,5 +50,6 @@ export function createHookContext(args: CreateHookContextArgs): HookContext {
48
50
  origin: args.origin ?? "rest",
49
51
  jobs: args.jobs ?? nullJobs,
50
52
  db,
53
+ ...(args.commerceConfig !== undefined ? { commerceConfig: args.commerceConfig } : {}),
51
54
  };
52
55
  }
@@ -1,4 +1,5 @@
1
1
  import type { Actor } from "../../auth/types.js";
2
+ import type { CommerceConfig } from "../../config/types.js";
2
3
  import type { JobsAdapter } from "../jobs/adapter.js";
3
4
  import type { PluginDb } from "../database/plugin-types.js";
4
5
 
@@ -40,6 +41,7 @@ export interface HookContext {
40
41
  * Populated by `createHookContext` when callers pass `db`, `database`, or `kernel`.
41
42
  */
42
43
  db: PluginDb;
44
+ commerceConfig?: CommerceConfig | null;
43
45
  }
44
46
 
45
47
  export type BeforeHook<TData> = (args: {
@@ -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(),