@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
@@ -10,6 +10,7 @@ export interface ChannelConnectorCapabilities {
10
10
  readonly importCatalog: boolean;
11
11
  readonly importInventory: boolean;
12
12
  readonly pushOrder: boolean;
13
+ readonly pushCatalog?: boolean;
13
14
  readonly receiveWebhooks: boolean;
14
15
  readonly reserve?: boolean;
15
16
  }
@@ -24,11 +25,49 @@ export interface ChannelStore {
24
25
  webhookSecret: string | null;
25
26
  }
26
27
 
28
+ export interface ChannelCatalogLocalizedAttributes {
29
+ locale: string;
30
+ title: string;
31
+ subtitle?: string;
32
+ description?: string;
33
+ richDescription?: unknown;
34
+ seoTitle?: string;
35
+ seoDescription?: string;
36
+ }
37
+
38
+ export interface ChannelCatalogImage {
39
+ externalId?: string;
40
+ url: string;
41
+ alt?: string;
42
+ role: "primary" | "gallery" | "thumbnail" | "video" | "document";
43
+ sortOrder?: number;
44
+ variantExternalIds?: string[];
45
+ }
46
+
47
+ export interface ChannelCatalogOptionType {
48
+ name: string;
49
+ displayName: string;
50
+ sortOrder?: number;
51
+ values: Array<{
52
+ value: string;
53
+ displayValue: string;
54
+ sortOrder?: number;
55
+ }>;
56
+ }
57
+
58
+ export interface ChannelCatalogPrice {
59
+ currency: string;
60
+ amount: number;
61
+ compareAtAmount?: number;
62
+ }
63
+
27
64
  export interface ChannelCatalogVariant {
28
65
  externalId: string;
29
66
  sku?: string;
30
67
  barcode?: string;
31
68
  metadata?: Record<string, unknown>;
69
+ optionValues?: Record<string, string>;
70
+ prices?: ChannelCatalogPrice[];
32
71
  }
33
72
 
34
73
  export interface ChannelCatalogItem {
@@ -38,6 +77,13 @@ export interface ChannelCatalogItem {
38
77
  description?: string;
39
78
  variants: ChannelCatalogVariant[];
40
79
  metadata?: Record<string, unknown>;
80
+ attributes?: ChannelCatalogLocalizedAttributes[];
81
+ images?: ChannelCatalogImage[];
82
+ options?: ChannelCatalogOptionType[];
83
+ tags?: string[];
84
+ brand?: string;
85
+ categories?: string[];
86
+ status?: "draft" | "active" | "archived" | "discontinued";
41
87
  }
42
88
 
43
89
  export interface ChannelCatalogPage {
@@ -76,6 +122,54 @@ export interface ChannelPushOrderResult {
76
122
  remoteUrl?: string;
77
123
  }
78
124
 
125
+ export type ChannelPushCatalogIntent = "filterable" | "display" | "tag";
126
+
127
+ export interface ChannelPushCatalogField {
128
+ fieldPath: string;
129
+ intent: ChannelPushCatalogIntent;
130
+ value: unknown;
131
+ locale?: string;
132
+ remoteKey?: string;
133
+ }
134
+
135
+ export interface ChannelPushCatalogImage {
136
+ externalId?: string;
137
+ url: string;
138
+ alt?: string;
139
+ role: "primary" | "gallery" | "thumbnail" | "video" | "document";
140
+ sortOrder?: number;
141
+ variantExternalIds?: string[];
142
+ }
143
+
144
+ export interface ChannelPushCatalogVariant {
145
+ externalId: string;
146
+ fields: ChannelPushCatalogField[];
147
+ }
148
+
149
+ export interface ChannelPushCatalogItem {
150
+ externalId: string;
151
+ variants?: ChannelPushCatalogVariant[];
152
+ fields: ChannelPushCatalogField[];
153
+ images?: ChannelPushCatalogImage[];
154
+ }
155
+
156
+ export interface ChannelPushCatalogPreviousField {
157
+ fieldPath: string;
158
+ value: unknown;
159
+ }
160
+
161
+ export interface ChannelPushCatalogItemOutcome {
162
+ externalId: string;
163
+ ok: boolean;
164
+ error?: ChannelConnectorError;
165
+ remoteUpdatedAt?: string;
166
+ previousFields?: ChannelPushCatalogPreviousField[];
167
+ }
168
+
169
+ export interface ChannelPushCatalogResult {
170
+ outcomes: ChannelPushCatalogItemOutcome[];
171
+ }
172
+
79
173
  export interface ChannelOrderStatus {
80
174
  status: "pending" | "confirmed" | "failed" | "cancelled" | "fulfilled";
81
175
  }
@@ -113,6 +207,11 @@ export interface ChannelConnector {
113
207
  importCatalog(store: ChannelStore, cursor?: string): Promise<Result<ChannelCatalogPage>>;
114
208
  fetchInventory(store: ChannelStore, ids?: string[]): Promise<Result<ChannelInventoryLevel[]>>;
115
209
  pushOrder(store: ChannelStore, slice: ChannelOrderSlice): Promise<Result<ChannelPushOrderResult, ChannelConnectorError>>;
210
+ pushCatalog?(
211
+ store: ChannelStore,
212
+ items: ChannelPushCatalogItem[],
213
+ opts?: { dryRun?: boolean },
214
+ ): Promise<Result<ChannelPushCatalogResult, ChannelConnectorError>>;
116
215
  fetchOrderStatus(store: ChannelStore, remoteId: string): Promise<Result<ChannelOrderStatus, ChannelConnectorError>>;
117
216
  verifyWebhook(store: ChannelStore, request: Request): Promise<Result<ChannelWebhookEvent>>;
118
217
  verifyAppWebhook?(
@@ -135,5 +234,12 @@ export interface ChannelConnector {
135
234
  }
136
235
 
137
236
  export function defineChannelConnector<T extends ChannelConnector>(connector: T): T {
138
- return connector;
237
+ if (connector.capabilities.pushCatalog !== undefined) return connector;
238
+ return {
239
+ ...connector,
240
+ capabilities: {
241
+ ...connector.capabilities,
242
+ pushCatalog: false,
243
+ },
244
+ } as T;
139
245
  }
@@ -1,3 +1,4 @@
1
+ import type { CommerceConfig } from "../../config/types.js";
1
2
  import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
2
3
  import { defineModule } from "../../kernel/module/index.js";
3
4
  import { CustomersRepository } from "./repository/index.js";
@@ -21,6 +22,7 @@ export const customersModule = defineModule({
21
22
  new CustomerService({
22
23
  repository: new CustomersRepository(deps.db.db as DrizzleDatabase),
23
24
  hooks: deps.hooks,
25
+ config: deps.config as CommerceConfig,
24
26
  services: deps.services,
25
27
  database: deps.db,
26
28
  }),
@@ -1,6 +1,7 @@
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
+ import type { CommerceConfig } from "../../config/types.js";
4
5
  import { CommerceNotFoundError, toCommerceError } from "../../kernel/errors.js";
5
6
  import { runAfterHooks } from "../../kernel/hooks/executor.js";
6
7
  import { createHookContext } from "../../kernel/hooks/create-context.js";
@@ -22,6 +23,7 @@ import type {
22
23
  interface CustomerServiceDeps {
23
24
  repository: CustomersRepository;
24
25
  hooks: HookRegistry;
26
+ config: CommerceConfig;
25
27
  services: Record<string, unknown>;
26
28
  database: DatabaseAdapter;
27
29
  }
@@ -30,6 +32,7 @@ function hookContext(
30
32
  actor: Actor | null,
31
33
  services: Record<string, unknown>,
32
34
  database: DatabaseAdapter,
35
+ config: CommerceConfig,
33
36
  tx: unknown,
34
37
  ): HookContext {
35
38
  return createHookContext({
@@ -39,6 +42,7 @@ function hookContext(
39
42
  services,
40
43
  context: { moduleName: "customers" },
41
44
  database: { db: database.db as PluginDb },
45
+ commerceConfig: config,
42
46
  });
43
47
  }
44
48
 
@@ -73,7 +77,7 @@ export class CustomerService {
73
77
  return Err(toCommerceError(error));
74
78
  }
75
79
 
76
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
80
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
77
81
  const isWalkIn = input.userId === undefined;
78
82
  const userId = input.userId ?? `anonymous_${crypto.randomUUID()}`;
79
83
  const metadata = {
@@ -97,7 +101,7 @@ export class CustomerService {
97
101
  const afterHooks = this.deps.hooks.resolve(
98
102
  "customers.afterCreate",
99
103
  ) as AfterHook<Customer>[];
100
- const hctx = hookContext(actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
104
+ const hctx = hookContext(actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
101
105
  await runAfterHooks(afterHooks, null, customer, "create", hctx);
102
106
 
103
107
  return Ok(customer);
@@ -107,7 +111,7 @@ export class CustomerService {
107
111
 
108
112
  async listInteractions(customerId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<CustomerInteraction[]>> {
109
113
  try { assertPermission(actor ?? null, "customers:read"); } catch (error) { return Err(toCommerceError(error)); }
110
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
114
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
111
115
  return Ok(await this.repo.listInteractions(orgId, customerId, ctx));
112
116
  }
113
117
 
@@ -118,7 +122,7 @@ export class CustomerService {
118
122
  ctx?: TxContext,
119
123
  ): Promise<Result<CustomerInteraction>> {
120
124
  try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
121
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
125
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
122
126
  const customer = await this.repo.findById(orgId, customerId, ctx);
123
127
  if (!customer) return Err(new CommerceNotFoundError("Customer not found."));
124
128
  const interaction = await this.repo.createInteraction(
@@ -144,7 +148,7 @@ export class CustomerService {
144
148
  ctx?: TxContext,
145
149
  ): Promise<Result<CustomerInteraction>> {
146
150
  try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
147
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
151
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
148
152
  const existing = await this.repo.findInteractionById(orgId, interactionId, ctx);
149
153
  if (!existing || existing.customerId !== customerId) return Err(new CommerceNotFoundError("Interaction not found."));
150
154
  const patch = {
@@ -160,7 +164,7 @@ export class CustomerService {
160
164
 
161
165
  async deleteInteraction(customerId: string, interactionId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>> {
162
166
  try { assertPermission(actor ?? null, "customers:update"); } catch (error) { return Err(toCommerceError(error)); }
163
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
167
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
164
168
  const existing = await this.repo.findInteractionById(orgId, interactionId, ctx);
165
169
  if (!existing || existing.customerId !== customerId) return Err(new CommerceNotFoundError("Interaction not found."));
166
170
  await this.repo.deleteInteraction(orgId, interactionId, ctx);
@@ -190,7 +194,7 @@ export class CustomerService {
190
194
  const afterHooks = this.deps.hooks.resolve(
191
195
  "customers.afterCreate",
192
196
  ) as AfterHook<Customer>[];
193
- const hctx = hookContext(actor, this.deps.services, this.deps.database, ctx?.tx ?? null);
197
+ const hctx = hookContext(actor, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
194
198
  await runAfterHooks(afterHooks, null, customer, "create", hctx);
195
199
 
196
200
  return customer;
@@ -200,7 +204,7 @@ export class CustomerService {
200
204
  actor?: Actor | null,
201
205
  ctx?: TxContext,
202
206
  ): Promise<Result<Customer[]>> {
203
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
207
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
204
208
  const customers = await this.repo.findAll(orgId, ctx);
205
209
  return Ok(customers);
206
210
  }
@@ -210,7 +214,7 @@ export class CustomerService {
210
214
  actor?: Actor | null,
211
215
  ctx?: TxContext,
212
216
  ): Promise<Result<Customer>> {
213
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
217
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
214
218
  const customer = await this.repo.findById(orgId, id, ctx);
215
219
  if (!customer) return Err(new CommerceNotFoundError("Customer not found."));
216
220
  return Ok(customer);
@@ -221,7 +225,7 @@ export class CustomerService {
221
225
  actor?: Actor | null,
222
226
  ctx?: TxContext,
223
227
  ): Promise<Result<Customer>> {
224
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
228
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
225
229
  const resolvedActor = actor ?? ctx?.actor ?? null;
226
230
  const customer = await this.getOrCreateByUserId(
227
231
  orgId,
@@ -247,7 +251,7 @@ export class CustomerService {
247
251
  return Err(toCommerceError(error));
248
252
  }
249
253
 
250
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
254
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
251
255
  const existing = await this.repo.findById(orgId, id, ctx);
252
256
  if (!existing) return Err(new CommerceNotFoundError("Customer not found."));
253
257
 
@@ -271,10 +275,7 @@ export class CustomerService {
271
275
  "customers.afterUpdate",
272
276
  ) as AfterHook<Customer>[];
273
277
  const hctx = hookContext(
274
- actor ?? ctx?.actor ?? null,
275
- this.deps.services,
276
- this.deps.database,
277
- ctx?.tx ?? null,
278
+ actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
278
279
  );
279
280
  await runAfterHooks(afterHooks, existing, updated, "update", hctx);
280
281
 
@@ -289,7 +290,7 @@ export class CustomerService {
289
290
  actor?: Actor | null,
290
291
  ctx?: TxContext,
291
292
  ): Promise<Result<Customer>> {
292
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
293
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
293
294
  const resolvedActor = actor ?? ctx?.actor ?? null;
294
295
  const customer = await this.getOrCreateByUserId(
295
296
  orgId,
@@ -306,7 +307,7 @@ export class CustomerService {
306
307
  const afterHooks = this.deps.hooks.resolve(
307
308
  "customers.afterUpdate",
308
309
  ) as AfterHook<Customer>[];
309
- const hctx = hookContext(resolvedActor, this.deps.services, this.deps.database, ctx?.tx ?? null);
310
+ const hctx = hookContext(resolvedActor, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
310
311
  await runAfterHooks(afterHooks, customer, updated, "update", hctx);
311
312
 
312
313
  return Ok(updated);
@@ -317,7 +318,7 @@ export class CustomerService {
317
318
  actor?: Actor | null,
318
319
  ctx?: TxContext,
319
320
  ): Promise<Result<CustomerAddress[]>> {
320
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
321
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
321
322
  const resolvedActor = actor ?? ctx?.actor ?? null;
322
323
  const customer = await this.getOrCreateByUserId(
323
324
  orgId,
@@ -338,7 +339,7 @@ export class CustomerService {
338
339
  actor?: Actor | null,
339
340
  ctx?: TxContext,
340
341
  ): Promise<Result<CustomerAddress>> {
341
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
342
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
342
343
  const resolvedActor = actor ?? ctx?.actor ?? null;
343
344
  const customer = await this.getOrCreateByUserId(
344
345
  orgId,
@@ -376,7 +377,7 @@ export class CustomerService {
376
377
  actor?: Actor | null,
377
378
  ctx?: TxContext,
378
379
  ): Promise<Result<void>> {
379
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
380
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
380
381
  const resolvedActor = actor ?? ctx?.actor ?? null;
381
382
  const customer = await this.getOrCreateByUserId(
382
383
  orgId,
@@ -403,7 +404,7 @@ export class CustomerService {
403
404
  actor?: Actor | null,
404
405
  ctx?: TxContext,
405
406
  ): Promise<{ id: string; name: string } | null> {
406
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
407
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
407
408
  const user = await this.repo.findByPosPin(orgId, hashedPin, ctx);
408
409
  if (!user) return null;
409
410
  return {
@@ -1,3 +1,4 @@
1
+ import type { CommerceConfig } from "../../config/types.js";
1
2
  import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
2
3
  import { defineModule } from "../../kernel/module/index.js";
3
4
  import { DocumentsRepository } from "./repository/index.js";
@@ -15,5 +16,6 @@ export const documentsModule = defineModule<
15
16
  new DocumentsService({
16
17
  repository: new DocumentsRepository(deps.db.db as DrizzleDatabase),
17
18
  services: deps.services as Record<string, unknown>,
19
+ config: deps.config as CommerceConfig,
18
20
  }),
19
21
  });
@@ -1,7 +1,9 @@
1
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
2
2
  import type { Actor } from "../../auth/types.js";
3
+ import type { CommerceConfig } from "../../config/types.js";
3
4
  import type { TxContext } from "../../kernel/database/tx-context.js";
4
- import { CommerceValidationError } from "../../kernel/errors.js";
5
+ import { hasPermission } from "../../auth/permissions.js";
6
+ import { CommerceForbiddenError, CommerceValidationError } from "../../kernel/errors.js";
5
7
  import { Err, Ok, type Result } from "../../kernel/result.js";
6
8
  import type { HydratedOrder, OrderService } from "../orders/service.js";
7
9
  import type { SettingsService } from "../settings/service.js";
@@ -19,6 +21,15 @@ interface DocumentsServiceDeps {
19
21
  // The shared kernel service container — orders/settings/email are resolved
20
22
  // at call time (they may instantiate after this module).
21
23
  services: Record<string, unknown>;
24
+ config: CommerceConfig;
25
+ }
26
+
27
+ interface CustomerLookup {
28
+ getById(
29
+ id: string,
30
+ actor?: Actor | null,
31
+ ctx?: TxContext,
32
+ ): Promise<{ ok: true; value: { email: string | null } } | { ok: false }>;
22
33
  }
23
34
 
24
35
  interface EmailAdapter {
@@ -33,10 +44,12 @@ const DEFAULT_INVOICE_PREFIX = "INV-";
33
44
  export class DocumentsService {
34
45
  private repository: DocumentsRepository;
35
46
  private services: Record<string, unknown>;
47
+ private config: CommerceConfig;
36
48
 
37
49
  constructor(deps: DocumentsServiceDeps) {
38
50
  this.repository = deps.repository;
39
51
  this.services = deps.services;
52
+ this.config = deps.config;
40
53
  }
41
54
 
42
55
  private get orders(): OrderService {
@@ -90,16 +103,18 @@ export class DocumentsService {
90
103
  orderId: string,
91
104
  actor: Actor | null,
92
105
  ctx?: TxContext,
106
+ guestCredential?: string,
93
107
  ): Promise<Result<HydratedOrder>> {
94
- return this.orders.getById(orderId, actor, ctx);
108
+ return this.orders.getById(orderId, actor, ctx, guestCredential);
95
109
  }
96
110
 
97
111
  async renderInvoiceHtml(
98
112
  orderId: string,
99
113
  actor: Actor | null,
100
114
  ctx?: TxContext,
115
+ guestCredential?: string,
101
116
  ): Promise<Result<{ html: string; invoiceNumber: string }>> {
102
- const order = await this.loadOrder(orderId, actor, ctx);
117
+ const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
103
118
  if (!order.ok) return order;
104
119
  const branding = await this.brandingFor(order.value.organizationId, ctx);
105
120
  const doc = await this.issueInvoiceNumber(order.value, ctx);
@@ -113,8 +128,9 @@ export class DocumentsService {
113
128
  orderId: string,
114
129
  actor: Actor | null,
115
130
  ctx?: TxContext,
131
+ guestCredential?: string,
116
132
  ): Promise<Result<{ pdf: Uint8Array; invoiceNumber: string }>> {
117
- const order = await this.loadOrder(orderId, actor, ctx);
133
+ const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
118
134
  if (!order.ok) return order;
119
135
  const branding = await this.brandingFor(order.value.organizationId, ctx);
120
136
  const doc = await this.issueInvoiceNumber(order.value, ctx);
@@ -130,39 +146,86 @@ export class DocumentsService {
130
146
  orderId: string,
131
147
  actor: Actor | null,
132
148
  ctx?: TxContext,
149
+ guestCredential?: string,
133
150
  ): Promise<Result<{ html: string }>> {
134
- const order = await this.loadOrder(orderId, actor, ctx);
151
+ const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
135
152
  if (!order.ok) return order;
136
153
  const branding = await this.brandingFor(order.value.organizationId, ctx);
137
154
  return Ok({ html: receiptHtml({ order: order.value, branding }) });
138
155
  }
139
156
 
157
+ /**
158
+ * The address that belongs to this order: its customer profile's email.
159
+ * A guest order has no customer profile and therefore no address of its own.
160
+ */
161
+ private async orderEmail(
162
+ order: HydratedOrder,
163
+ actor: Actor | null,
164
+ ctx?: TxContext,
165
+ ): Promise<string | null> {
166
+ if (!order.customerId) return null;
167
+ const customers = this.services.customers as CustomerLookup | undefined;
168
+ if (!customers) return null;
169
+ const customer = await customers.getById(order.customerId, actor, ctx);
170
+ return customer.ok ? (customer.value.email ?? null) : null;
171
+ }
172
+
140
173
  /**
141
174
  * Emails the invoice via the configured email adapter. The adapter's
142
175
  * template contract is (template, to, data) — the rendered HTML and the
143
176
  * fiscal number ride in `data` so template-based adapters can use either.
177
+ *
178
+ * The destination is the order's own address, not the caller's choice. A
179
+ * cart secret authorizes reading an order, and a caller-supplied `to` turned
180
+ * that read into delivery: items, totals and shipping address mailed to any
181
+ * address, with nothing left in the shopper's inbox. Only staff holding
182
+ * org-wide `orders:read` may name a different recipient.
144
183
  */
145
184
  async emailInvoice(
146
185
  orderId: string,
147
186
  to: string,
148
187
  actor: Actor | null,
149
188
  ctx?: TxContext,
189
+ guestCredential?: string,
150
190
  ): Promise<Result<{ sent: true; invoiceNumber: string; to: string }>> {
151
191
  const email = this.services.email as EmailAdapter | undefined;
152
192
  if (!email) {
153
193
  return Err(new CommerceValidationError("No email adapter is configured."));
154
194
  }
155
- const rendered = await this.renderInvoiceHtml(orderId, actor, ctx);
195
+ const rendered = await this.renderInvoiceHtml(orderId, actor, ctx, guestCredential);
156
196
  if (!rendered.ok) return rendered;
157
197
 
158
- const order = await this.loadOrder(orderId, actor, ctx);
198
+ const order = await this.loadOrder(orderId, actor, ctx, guestCredential);
159
199
  if (!order.ok) return order;
160
200
 
201
+ const ownAddress = await this.orderEmail(order.value, actor, ctx);
202
+ const mayDirect = hasPermission(actor, "orders:read");
203
+ const requested = to.trim();
204
+
205
+ let destination: string;
206
+ if (mayDirect && requested.length > 0) {
207
+ destination = requested;
208
+ } else if (ownAddress === null) {
209
+ return Err(
210
+ new CommerceValidationError(
211
+ "This order has no email address of its own. Render the invoice instead, or send it as staff.",
212
+ ),
213
+ );
214
+ } else if (requested.length > 0 && requested.toLowerCase() !== ownAddress.toLowerCase()) {
215
+ return Err(
216
+ new CommerceForbiddenError(
217
+ "An invoice can only be emailed to the address on the order.",
218
+ ),
219
+ );
220
+ } else {
221
+ destination = ownAddress;
222
+ }
223
+
161
224
  await email.send({
162
225
  template: "order-invoice",
163
- to,
226
+ to: destination,
164
227
  data: {
165
- orgId: resolveOrgId(actor ?? ctx?.actor ?? null),
228
+ orgId: resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config),
166
229
  orderId,
167
230
  orderNumber: order.value.orderNumber,
168
231
  invoiceNumber: rendered.value.invoiceNumber,
@@ -171,6 +234,6 @@ export class DocumentsService {
171
234
  html: rendered.value.html,
172
235
  },
173
236
  });
174
- return Ok({ sent: true, invoiceNumber: rendered.value.invoiceNumber, to });
237
+ return Ok({ sent: true, invoiceNumber: rendered.value.invoiceNumber, to: destination });
175
238
  }
176
239
  }
@@ -1,3 +1,4 @@
1
+ import type { CommerceConfig } from "../../config/types.js";
1
2
  import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
2
3
  import { defineModule } from "../../kernel/module/index.js";
3
4
  import type { InventoryService } from "../inventory/service.js";
@@ -36,6 +37,7 @@ export const fulfillmentModule = defineModule<
36
37
  ordersRepository: new OrdersRepository(deps.db.db as DrizzleDatabase),
37
38
  inventoryService: deps.services.inventory,
38
39
  hooks: deps.hooks,
40
+ config: deps.config as CommerceConfig,
39
41
  services: deps.services,
40
42
  database: deps.db,
41
43
  }),
@@ -1,10 +1,12 @@
1
1
  import { createHmac } from "node:crypto";
2
- import { resolveOrgId } from "../../auth/org.js";
2
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
3
+ import { assertPermission } from "../../auth/permissions.js";
3
4
  import type { Actor } from "../../auth/types.js";
5
+ import type { CommerceConfig } from "../../config/types.js";
4
6
  import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
5
7
  import type { PluginDb } from "../../kernel/database/plugin-types.js";
6
8
  import type { TxContext } from "../../kernel/database/tx-context.js";
7
- import { CommerceNotFoundError, CommerceValidationError } from "../../kernel/errors.js";
9
+ import { CommerceNotFoundError, CommerceValidationError, toCommerceError } from "../../kernel/errors.js";
8
10
  import { runAfterHooks } from "../../kernel/hooks/executor.js";
9
11
  import { createHookContext } from "../../kernel/hooks/create-context.js";
10
12
  import type { HookRegistry } from "../../kernel/hooks/registry.js";
@@ -48,6 +50,7 @@ interface FulfillmentServiceDeps {
48
50
  ordersRepository: OrdersRepository;
49
51
  inventoryService?: InventoryServiceLike;
50
52
  hooks: HookRegistry;
53
+ config: CommerceConfig;
51
54
  services: Record<string, unknown>;
52
55
  database: DatabaseAdapter;
53
56
  }
@@ -259,7 +262,7 @@ export class FulfillmentService {
259
262
  }
260
263
 
261
264
  async fulfillOrder(orderId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>> {
262
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
265
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
263
266
  const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
264
267
  if (!order) return Err(new CommerceNotFoundError("Order not found."));
265
268
 
@@ -326,6 +329,7 @@ export class FulfillmentService {
326
329
  services: this.deps.services,
327
330
  context: { moduleName: "fulfillment" },
328
331
  database: { db: this.deps.database.db as PluginDb },
332
+ commerceConfig: this.deps.config,
329
333
  });
330
334
  await runAfterHooks(afterHooks, null, record, "create", hookCtx);
331
335
 
@@ -365,7 +369,13 @@ export class FulfillmentService {
365
369
  actor?: Actor | null,
366
370
  ctx?: TxContext,
367
371
  ): Promise<Result<FulfillmentRecord>> {
368
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
372
+ try {
373
+ assertPermission(actor ?? null, "orders:update");
374
+ } catch (error) {
375
+ return Err(toCommerceError(error));
376
+ }
377
+
378
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
369
379
  const order = await this.deps.ordersRepository.findById(orgId, input.orderId, ctx);
370
380
  if (!order) return Err(new CommerceNotFoundError("Order not found."));
371
381
 
@@ -432,7 +442,7 @@ export class FulfillmentService {
432
442
  fulfillmentId: created.id,
433
443
  eventType: "created",
434
444
  toStatus: status,
435
- ...(actor?.userId !== undefined ? { actorId: actor.userId } : {}),
445
+ ...(actor?.userId != null ? { actorId: actor.userId } : {}),
436
446
  description: input.trackingNumber
437
447
  ? `Shipment recorded (${input.trackingNumber})`
438
448
  : "Fulfillment recorded",
@@ -478,6 +488,7 @@ export class FulfillmentService {
478
488
  services: this.deps.services,
479
489
  context: { moduleName: "fulfillment" },
480
490
  database: { db: this.deps.database.db as PluginDb },
491
+ commerceConfig: this.deps.config,
481
492
  });
482
493
  await runAfterHooks(afterHooks, null, record, "create", hookCtx);
483
494
 
@@ -563,7 +574,7 @@ export class FulfillmentService {
563
574
  return Err(new CommerceNotFoundError("Digital download not found."));
564
575
  }
565
576
 
566
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
577
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
567
578
  const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
568
579
  if (!order || order.customerId !== userId) {
569
580
  return Err(new CommerceNotFoundError("Order not found."));