@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
@@ -1,5 +1,6 @@
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 { DatabaseAdapter } from "../../kernel/database/adapter.js";
4
5
  import type { PluginDb } from "../../kernel/database/plugin-types.js";
5
6
  import type { TxContext } from "../../kernel/database/tx-context.js";
@@ -33,6 +34,7 @@ interface PricingServiceDeps {
33
34
  repository: PricingRepository;
34
35
  catalogRepository: CatalogRepository;
35
36
  hooks: HookRegistry;
37
+ config: CommerceConfig;
36
38
  services: Record<string, unknown>;
37
39
  database: DatabaseAdapter;
38
40
  }
@@ -41,6 +43,7 @@ function hookContext(
41
43
  actor: Actor | null,
42
44
  services: Record<string, unknown>,
43
45
  database: DatabaseAdapter,
46
+ config: CommerceConfig,
44
47
  tx: unknown,
45
48
  ): HookContext {
46
49
  return createHookContext({
@@ -50,6 +53,7 @@ function hookContext(
50
53
  services,
51
54
  context: { moduleName: "pricing" },
52
55
  database: { db: database.db as PluginDb },
56
+ commerceConfig: config,
53
57
  });
54
58
  }
55
59
 
@@ -211,7 +215,7 @@ export class PricingService {
211
215
  );
212
216
  }
213
217
 
214
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
218
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
215
219
  const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
216
220
  if (!entity) {
217
221
  return Err(
@@ -224,6 +228,7 @@ export class PricingService {
224
228
  entityId: input.entityId,
225
229
  currency: normalizeCurrency(input.currency),
226
230
  amount: input.amount,
231
+ ...(input.compareAtAmount !== undefined ? { compareAtAmount: input.compareAtAmount } : {}),
227
232
  metadata: input.metadata ?? {},
228
233
  variantId: input.variantId ?? null,
229
234
  customerGroupId: input.customerGroupId ?? null,
@@ -252,8 +257,12 @@ export class PricingService {
252
257
 
253
258
  const record = existing
254
259
  ? (await this.repo.updatePrice(
255
- existing.id,
256
- { amount: priceData.amount, metadata: priceData.metadata },
260
+ existing.id,
261
+ {
262
+ amount: priceData.amount,
263
+ ...(priceData.compareAtAmount !== undefined ? { compareAtAmount: priceData.compareAtAmount } : {}),
264
+ metadata: priceData.metadata,
265
+ },
257
266
  ctx,
258
267
  )) ?? existing
259
268
  : await this.repo.createPrice(priceData, ctx);
@@ -262,10 +271,7 @@ export class PricingService {
262
271
  "pricing.afterCreate",
263
272
  ) as AfterHook<Price>[];
264
273
  const hctx = hookContext(
265
- actor ?? ctx?.actor ?? null,
266
- this.deps.services,
267
- this.deps.database,
268
- ctx?.tx ?? null,
274
+ actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
269
275
  );
270
276
  await runAfterHooks(afterHooks, null, record, "create", hctx);
271
277
 
@@ -296,7 +302,7 @@ export class PricingService {
296
302
  );
297
303
  }
298
304
 
299
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
305
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
300
306
 
301
307
  const modifierData: PriceModifierInsert = {
302
308
  organizationId: orgId,
@@ -322,10 +328,7 @@ export class PricingService {
322
328
  "pricing.afterCreate",
323
329
  ) as AfterHook<PriceModifier>[];
324
330
  const hctx = hookContext(
325
- actor ?? ctx?.actor ?? null,
326
- this.deps.services,
327
- this.deps.database,
328
- ctx?.tx ?? null,
331
+ actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
329
332
  );
330
333
  await runAfterHooks(afterHooks, null, modifier, "create", hctx);
331
334
 
@@ -337,7 +340,7 @@ export class PricingService {
337
340
  actor?: Actor | null,
338
341
  ctx?: TxContext,
339
342
  ): Promise<Result<PriceModifier[]>> {
340
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
343
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
341
344
  const rows = await this.repo.findModifiers(
342
345
  orgId,
343
346
  {
@@ -367,7 +370,7 @@ export class PricingService {
367
370
  actor?: Actor | null,
368
371
  ctx?: TxContext,
369
372
  ): Promise<Result<PriceModifier>> {
370
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
373
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
371
374
  const existing = await this.repo.findModifierById(orgId, id, ctx);
372
375
  if (!existing) {
373
376
  return Err(new CommerceNotFoundError("Price modifier not found."));
@@ -394,7 +397,7 @@ export class PricingService {
394
397
  actor?: Actor | null,
395
398
  ctx?: TxContext,
396
399
  ): Promise<Result<{ deleted: true }>> {
397
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
400
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
398
401
  const existing = await this.repo.findModifierById(orgId, id, ctx);
399
402
  if (!existing) {
400
403
  return Err(new CommerceNotFoundError("Price modifier not found."));
@@ -413,7 +416,7 @@ export class PricingService {
413
416
  actor?: Actor | null,
414
417
  ctx?: TxContext,
415
418
  ): Promise<Result<{ prices: Price[]; modifiers: PriceModifier[] }>> {
416
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
419
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
417
420
  // Get all prices for the entity if specified, or filter after retrieval
418
421
  let prices: Price[] = [];
419
422
  if (filter?.entityId) {
@@ -472,7 +475,7 @@ export class PricingService {
472
475
  actor?: Actor | null,
473
476
  ctx?: TxContext,
474
477
  ): Promise<Result<ResolvedPrice>> {
475
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
478
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
476
479
  const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
477
480
  if (!entity) {
478
481
  return Err(
@@ -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 { CatalogServiceImpl } from "../catalog/service.js";
@@ -26,6 +27,7 @@ export const promotionsModule = defineModule<
26
27
  catalogRepository: deps.services.catalog.repository,
27
28
  ordersRepository: new OrdersRepository(deps.db.db as DrizzleDatabase),
28
29
  hooks: deps.hooks,
30
+ config: deps.config as CommerceConfig,
29
31
  services: deps.services,
30
32
  database: deps.db,
31
33
  }),
@@ -19,8 +19,9 @@ import type {
19
19
  } from "./repository/index.js";
20
20
  import type { CatalogRepository } from "../catalog/repository/index.js";
21
21
  import type { OrdersRepository } from "../orders/repository/index.js";
22
- import { resolveOrgId } from "../../auth/org.js";
22
+ import { resolveOrgId, resolveOrgIdForCommerce } from "../../auth/org.js";
23
23
  import type { Actor } from "../../auth/types.js";
24
+ import type { CommerceConfig } from "../../config/types.js";
24
25
 
25
26
  // PromotionType is derived from the zod enum (single source of truth) in
26
27
  // ./schemas.js; re-export it here for backwards-compatible imports.
@@ -35,6 +36,7 @@ interface PromotionServiceDeps {
35
36
  catalogRepository: CatalogRepository;
36
37
  ordersRepository: OrdersRepository;
37
38
  hooks: HookRegistry;
39
+ config: CommerceConfig;
38
40
  services: Record<string, unknown>;
39
41
  database: DatabaseAdapter;
40
42
  }
@@ -43,6 +45,7 @@ function hookContext(
43
45
  actor: Actor | null,
44
46
  services: Record<string, unknown>,
45
47
  database: DatabaseAdapter,
48
+ config: CommerceConfig,
46
49
  tx: unknown,
47
50
  ): HookContext {
48
51
  return createHookContext({
@@ -52,6 +55,7 @@ function hookContext(
52
55
  services,
53
56
  context: { moduleName: "promotions" },
54
57
  database: { db: database.db as PluginDb },
58
+ commerceConfig: config,
55
59
  });
56
60
  }
57
61
 
@@ -165,6 +169,16 @@ export class PromotionService {
165
169
  this.ordersRepo = deps.ordersRepository;
166
170
  }
167
171
 
172
+ private resolveEvaluationOrgId(
173
+ ctx: TxContext | undefined,
174
+ explicitOrgId?: string,
175
+ ): string {
176
+ if (explicitOrgId) {
177
+ return resolveOrgId(ctx?.actor ?? null, explicitOrgId);
178
+ }
179
+ return resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
180
+ }
181
+
168
182
  async create(
169
183
  input: CreatePromotionInput,
170
184
  actor?: Actor | null,
@@ -189,7 +203,7 @@ export class PromotionService {
189
203
  );
190
204
  }
191
205
 
192
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
206
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
193
207
 
194
208
  if (input.code) {
195
209
  const normalized = input.code.trim().toUpperCase();
@@ -229,10 +243,7 @@ export class PromotionService {
229
243
  "promotions.afterCreate",
230
244
  ) as AfterHook<Promotion>[];
231
245
  const hctx = hookContext(
232
- actor ?? ctx?.actor ?? null,
233
- this.deps.services,
234
- this.deps.database,
235
- ctx?.tx ?? null,
246
+ actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
236
247
  );
237
248
  await runAfterHooks(afterHooks, null, promotion, "create", hctx);
238
249
 
@@ -253,7 +264,8 @@ export class PromotionService {
253
264
  const afterHooks = this.deps.hooks.resolve(
254
265
  "promotions.afterUpdate",
255
266
  ) as AfterHook<Promotion>[];
256
- const hctx = hookContext(null, this.deps.services, this.deps.database, ctx?.tx ?? null);
267
+ // Actor-less by design; resolves to the deployment's declared organization.
268
+ const hctx = hookContext(null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
257
269
  await runAfterHooks(afterHooks, promotion, updated, "update", hctx);
258
270
 
259
271
  return Ok(updated);
@@ -331,10 +343,7 @@ export class PromotionService {
331
343
  "promotions.afterUpdate",
332
344
  ) as AfterHook<Promotion>[];
333
345
  const hctx = hookContext(
334
- actor ?? ctx?.actor ?? null,
335
- this.deps.services,
336
- this.deps.database,
337
- ctx?.tx ?? null,
346
+ actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
338
347
  );
339
348
  await runAfterHooks(afterHooks, existing, updated, "update", hctx);
340
349
 
@@ -346,7 +355,7 @@ export class PromotionService {
346
355
  actor?: Actor | null,
347
356
  ctx?: TxContext,
348
357
  ): Promise<Result<Promotion[]>> {
349
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
358
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
350
359
  const all = await this.repo.findAll(orgId, ctx);
351
360
  const now = new Date();
352
361
 
@@ -375,7 +384,7 @@ export class PromotionService {
375
384
  timestamp = new Date(),
376
385
  ctx?: TxContext,
377
386
  ): Promise<Result<Promotion[]>> {
378
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
387
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
379
388
  const active = await this.repo.findActive(orgId, ctx);
380
389
  // Further filter by timestamp in case repo returns slightly stale results
381
390
  const filtered = active.filter((p) => matchesWindow(p, timestamp));
@@ -388,7 +397,7 @@ export class PromotionService {
388
397
  ctx?: TxContext,
389
398
  ): Promise<Result<Promotion>> {
390
399
  const normalized = code.trim().toUpperCase();
391
- const evalOrgId = resolveOrgId(ctx?.actor ?? null, context.orgId);
400
+ const evalOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
392
401
  const promotion = await this.repo.findByCode(evalOrgId, normalized, ctx);
393
402
  if (!promotion) {
394
403
  return Err(
@@ -434,7 +443,7 @@ export class PromotionService {
434
443
  ctx?: TxContext,
435
444
  ): Promise<Result<PromotionApplicationResult>> {
436
445
  const timestamp = context.timestamp ?? new Date();
437
- const applyOrgId = resolveOrgId(ctx?.actor ?? null, context.orgId);
446
+ const applyOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
438
447
  const result: PromotionApplicationResult = {
439
448
  totalDiscount: 0,
440
449
  freeShipping: false,
@@ -580,7 +589,7 @@ export class PromotionService {
580
589
  return "First-order promotion requires authenticated customer.";
581
590
  }
582
591
  const orders = await this.ordersRepo.findByCustomerId(
583
- resolveOrgId(ctx?.actor ?? null, context.orgId),
592
+ this.resolveEvaluationOrgId(ctx, context.orgId),
584
593
  context.customerId,
585
594
  ctx,
586
595
  );
@@ -2,22 +2,28 @@ import type { Result } from "../../kernel/result.js";
2
2
 
3
3
  export interface SearchDocument {
4
4
  id: string;
5
+ organizationId?: string;
5
6
  type: string;
6
7
  slug: string;
7
8
  title: string;
8
9
  description?: string;
9
10
  status?: string;
11
+ isVisible?: boolean;
10
12
  categories: string[];
11
13
  brands: string[];
12
14
  text: string;
15
+ attributes?: Record<string, string | string[]>;
13
16
  payload?: Record<string, unknown>;
14
17
  }
15
18
 
16
19
  export interface SearchFilters {
20
+ /** Server-supplied tenant constraint; REST callers cannot choose this value. */
21
+ organizationId?: string;
17
22
  type?: string;
18
23
  category?: string;
19
24
  brand?: string;
20
25
  status?: string;
26
+ attributes?: Record<string, string | string[]>;
21
27
  }
22
28
 
23
29
  export interface SearchQueryParams {
@@ -32,6 +38,8 @@ export interface SearchSuggestParams {
32
38
  prefix: string;
33
39
  type?: string;
34
40
  limit?: number;
41
+ /** Server-supplied tenant constraint; REST callers cannot choose this value. */
42
+ organizationId?: string;
35
43
  }
36
44
 
37
45
  export interface SearchHit {
@@ -16,6 +16,9 @@ export const searchModule = defineModule<Record<string, never>, SearchService, S
16
16
  const config = deps.config as CommerceConfig;
17
17
  return new SearchService({
18
18
  catalogRepository: deps.services.catalog.repository,
19
+ resolveEntityFieldDefinitions: deps.services.catalog.resolveEntityFieldDefinitions.bind(deps.services.catalog),
20
+ config,
21
+ ...(config.entities ? { entities: config.entities } : {}),
19
22
  ...(config.search?.adapter ? { adapter: config.search.adapter } : {}),
20
23
  ...(config.search?.defaultFacets
21
24
  ? { defaultFacets: config.search.defaultFacets }
@@ -1,7 +1,13 @@
1
- import { Ok, type Result } from "../../kernel/result.js";
2
- import { resolveOrgId } from "../../auth/org.js";
1
+ import { Err, Ok, type Result } from "../../kernel/result.js";
2
+ import { resolveOrgIdForCommerce } from "../../auth/org.js";
3
+ import { OrgResolutionError } from "../../kernel/errors.js";
3
4
  import type { TxContext } from "../../kernel/database/tx-context.js";
4
- import type { CatalogRepository, SellableEntity } from "../catalog/repository/index.js";
5
+ import type { CommerceConfig } from "../../config/types.js";
6
+ import type {
7
+ CatalogRepository,
8
+ SellableCustomField,
9
+ SellableEntity,
10
+ } from "../catalog/repository/index.js";
5
11
  import type {
6
12
  SearchAdapter,
7
13
  SearchDocument,
@@ -10,9 +16,14 @@ import type {
10
16
  SearchQueryResult,
11
17
  SearchSuggestParams,
12
18
  } from "./adapter.js";
19
+ import type { EntityFieldDefinitionResolver } from "../catalog/service.js";
20
+ import { canReadUnpublishedCatalog } from "../catalog/read-policy.js";
13
21
 
14
22
  interface SearchServiceDeps {
15
23
  catalogRepository: CatalogRepository;
24
+ config: CommerceConfig;
25
+ entities?: CommerceConfig["entities"];
26
+ resolveEntityFieldDefinitions?: EntityFieldDefinitionResolver;
16
27
  adapter?: SearchAdapter;
17
28
  defaultFacets?: string[];
18
29
  }
@@ -39,6 +50,19 @@ function includesAllTokens(haystack: string, tokens: string[]): boolean {
39
50
  return tokens.every((token) => lower.includes(token));
40
51
  }
41
52
 
53
+ function customFieldValue(field: SellableCustomField): string | undefined {
54
+ if (field.textValue != null) return field.textValue;
55
+ if (field.numberValue != null) return String(field.numberValue);
56
+ if (field.booleanValue != null) return String(field.booleanValue);
57
+ if (field.dateValue != null) return field.dateValue.toISOString();
58
+ return undefined;
59
+ }
60
+
61
+ function attributeValues(value: string | string[] | undefined): string[] {
62
+ if (value == null) return [];
63
+ return Array.isArray(value) ? value : [value];
64
+ }
65
+
42
66
  function scoreText(document: SearchDocument, tokens: string[]): number {
43
67
  if (tokens.length === 0) return 1;
44
68
 
@@ -109,16 +133,43 @@ export class SearchService {
109
133
  entity: SellableEntity,
110
134
  ctx?: TxContext,
111
135
  ): Promise<SearchDocument> {
112
- const attributes =
136
+ const localizedAttributes =
113
137
  await this.deps.catalogRepository.findAttributesByEntityId(
114
138
  entity.id,
115
139
  ctx,
116
140
  );
117
- const primary = attributes[0];
141
+ const primary = localizedAttributes[0];
118
142
  const title = primary?.title ?? entity.slug;
119
143
  const description = primary?.description;
120
144
  const categories = await this.entityCategories(entity.id, ctx);
121
145
  const brands = await this.entityBrands(entity.id, ctx);
146
+ const definitions = this.deps.resolveEntityFieldDefinitions
147
+ ? await this.deps.resolveEntityFieldDefinitions(entity.type, entity.organizationId, ctx)
148
+ : (this.deps.entities?.[entity.type]?.fields ?? []);
149
+ const filterableFields = new Set(
150
+ definitions
151
+ .filter((field) => field.filterable === true)
152
+ .map((field) => field.name),
153
+ );
154
+ const customFields = await this.deps.catalogRepository.findCustomFieldsByEntityId(
155
+ entity.id,
156
+ ctx,
157
+ );
158
+ const attributeValuesByName = new Map<string, string[]>();
159
+ for (const field of customFields) {
160
+ if (field.locale !== "en" || !filterableFields.has(field.fieldName)) continue;
161
+ const value = customFieldValue(field);
162
+ if (value == null) continue;
163
+ const values = attributeValuesByName.get(field.fieldName) ?? [];
164
+ if (!values.includes(value)) values.push(value);
165
+ attributeValuesByName.set(field.fieldName, values);
166
+ }
167
+ const attributes = Object.fromEntries(
168
+ [...attributeValuesByName.entries()].map(([name, values]) => [
169
+ name,
170
+ values.length === 1 ? values[0]! : values,
171
+ ]),
172
+ ) as Record<string, string | string[]>;
122
173
 
123
174
  const textParts: string[] = [
124
175
  entity.slug,
@@ -126,20 +177,23 @@ export class SearchService {
126
177
  description ?? "",
127
178
  ...categories,
128
179
  ...brands,
129
- ...attributes.map((attr) => attr.title),
130
- ...attributes.map((attr) => attr.description ?? ""),
180
+ ...localizedAttributes.map((attr) => attr.title),
181
+ ...localizedAttributes.map((attr) => attr.description ?? ""),
131
182
  ];
132
183
 
133
184
  return {
134
185
  id: entity.id,
186
+ organizationId: entity.organizationId,
135
187
  type: entity.type,
136
188
  slug: entity.slug,
137
189
  title,
138
190
  ...(description ? { description } : {}),
139
191
  status: entity.status,
192
+ isVisible: entity.isVisible,
140
193
  categories,
141
194
  brands,
142
195
  text: textParts.join(" ").trim(),
196
+ ...(Object.keys(attributes).length > 0 ? { attributes } : {}),
143
197
  payload: {
144
198
  metadata: entity.metadata ?? undefined,
145
199
  },
@@ -147,10 +201,18 @@ export class SearchService {
147
201
  }
148
202
 
149
203
  private async allDocuments(ctx?: TxContext): Promise<SearchDocument[]> {
150
- const orgId = resolveOrgId(ctx?.actor ?? null);
204
+ const actor = ctx?.actor;
205
+ if (!actor) {
206
+ // Deliberate exception: this needs an authenticated actor, not merely a
207
+ // resolvable organization, because visibility scoping is decided from the
208
+ // actor's permissions. A declared deployment default would satisfy the
209
+ // organization but not that.
210
+ throw new OrgResolutionError("Organization could not be resolved for a search read.");
211
+ }
212
+ const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
151
213
  const entities = await this.deps.catalogRepository.findEntities(
152
214
  orgId,
153
- undefined,
215
+ canReadUnpublishedCatalog(actor) ? undefined : { status: "active", isVisible: true },
154
216
  ctx,
155
217
  );
156
218
  return Promise.all(
@@ -163,11 +225,17 @@ export class SearchService {
163
225
  filters: SearchFilters | undefined,
164
226
  ): boolean {
165
227
  if (!filters) return true;
228
+ if (filters.organizationId && document.organizationId !== filters.organizationId) return false;
166
229
  if (filters.type && document.type !== filters.type) return false;
167
230
  if (filters.status && document.status !== filters.status) return false;
168
231
  if (filters.category && !document.categories.includes(filters.category))
169
232
  return false;
170
233
  if (filters.brand && !document.brands.includes(filters.brand)) return false;
234
+ for (const [name, requested] of Object.entries(filters.attributes ?? {})) {
235
+ const actual = attributeValues(document.attributes?.[name]);
236
+ const expected = attributeValues(requested);
237
+ if (!expected.some((value) => actual.includes(value))) return false;
238
+ }
171
239
  return true;
172
240
  }
173
241
 
@@ -214,6 +282,26 @@ export class SearchService {
214
282
  }
215
283
  }
216
284
  }
285
+
286
+ const attributeName = facet.startsWith("attributes.")
287
+ ? facet.slice("attributes.".length)
288
+ : facet;
289
+ if (
290
+ attributeName !== "type" &&
291
+ attributeName !== "status" &&
292
+ attributeName !== "category" &&
293
+ attributeName !== "categories" &&
294
+ attributeName !== "brand" &&
295
+ attributeName !== "brands"
296
+ ) {
297
+ const values: Record<string, number> = {};
298
+ for (const document of documents) {
299
+ for (const value of new Set(attributeValues(document.attributes?.[attributeName]))) {
300
+ values[value] = (values[value] ?? 0) + 1;
301
+ }
302
+ }
303
+ if (Object.keys(values).length > 0) output[attributeName] = values;
304
+ }
217
305
  }
218
306
 
219
307
  return output;
@@ -244,18 +332,49 @@ export class SearchService {
244
332
  const safeQuery = (params.query ?? "").slice(0, 500);
245
333
 
246
334
  if (this.deps.adapter) {
247
- return this.deps.adapter.search({
335
+ const actor = ctx?.actor ?? null;
336
+ // Deliberate exception: this needs an authenticated actor, not merely a
337
+ // resolvable organization, because visibility scoping is decided from the
338
+ // actor's permissions. A declared deployment default would satisfy the
339
+ // organization but not that.
340
+ if (!actor) return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
341
+ const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
342
+ const result = await this.deps.adapter.search({
248
343
  ...params,
249
344
  query: safeQuery,
250
345
  page,
251
346
  limit,
347
+ filters: {
348
+ ...params.filters,
349
+ organizationId,
350
+ ...(!canReadUnpublishedCatalog(actor) ? { status: "active" } : {}),
351
+ },
352
+ });
353
+ if (!result.ok) return result;
354
+ const hits = result.value.hits.filter((hit) =>
355
+ hit.document.organizationId === organizationId &&
356
+ (canReadUnpublishedCatalog(actor) ||
357
+ (hit.document.status === "active" && hit.document.isVisible !== false)),
358
+ );
359
+ return Ok({
360
+ ...result.value,
361
+ hits,
362
+ total: hits.length,
363
+ facets: this.computeFacets(hits.map((hit) => hit.document), params.facets),
252
364
  });
253
365
  }
254
366
 
255
367
  // In-memory fallback: require a non-empty query to avoid loading all entities
256
368
  // (allDocuments triggers N+1 DB queries per entity — unsafe at scale)
257
369
  const tokens = tokenize(safeQuery);
258
- if (tokens.length === 0 && !params.filters?.type && !params.filters?.category && !params.filters?.brand && !params.filters?.status) {
370
+ if (
371
+ tokens.length === 0 &&
372
+ !params.filters?.type &&
373
+ !params.filters?.category &&
374
+ !params.filters?.brand &&
375
+ !params.filters?.status &&
376
+ Object.keys(params.filters?.attributes ?? {}).length === 0
377
+ ) {
259
378
  return Ok({ hits: [], total: 0, page, limit, facets: {} });
260
379
  }
261
380
 
@@ -309,11 +428,22 @@ export class SearchService {
309
428
  if (prefix.length === 0) return Ok([]);
310
429
 
311
430
  if (this.deps.adapter) {
312
- return this.deps.adapter.suggest({
431
+ const actor = ctx?.actor ?? null;
432
+ // Deliberate exception: this needs an authenticated actor, not merely a
433
+ // resolvable organization, because visibility scoping is decided from the
434
+ // actor's permissions. A declared deployment default would satisfy the
435
+ // organization but not that.
436
+ if (!actor) return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
437
+ const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
438
+ const result = await this.deps.adapter.suggest({
313
439
  ...params,
314
440
  prefix,
315
441
  limit,
442
+ organizationId,
316
443
  });
444
+ if (!result.ok) return result;
445
+ const visibleTitles = new Set((await this.allDocuments(ctx)).map((document) => document.title));
446
+ return Ok(result.value.filter((title) => visibleTitles.has(title)));
317
447
  }
318
448
 
319
449
  // In-memory fallback: require prefix >= 2 chars to avoid loading all entities
@@ -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 { SettingsRepository } from "./repository/index.js";
@@ -14,5 +15,6 @@ export const settingsModule = defineModule<
14
15
  service: (deps) =>
15
16
  new SettingsService({
16
17
  repository: new SettingsRepository(deps.db.db as DrizzleDatabase),
18
+ config: deps.config as CommerceConfig,
17
19
  }),
18
20
  });
@@ -1,5 +1,6 @@
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
5
  import { CommerceValidationError } from "../../kernel/errors.js";
5
6
  import { Err, Ok, type Result } from "../../kernel/result.js";
@@ -9,6 +10,7 @@ const GROUP_NAME_RE = /^[a-z][a-z0-9_-]*$/;
9
10
 
10
11
  interface SettingsServiceDeps {
11
12
  repository: SettingsRepository;
13
+ config: CommerceConfig;
12
14
  }
13
15
 
14
16
  /**
@@ -20,9 +22,11 @@ interface SettingsServiceDeps {
20
22
  */
21
23
  export class SettingsService {
22
24
  private repository: SettingsRepository;
25
+ private config: CommerceConfig;
23
26
 
24
27
  constructor(deps: SettingsServiceDeps) {
25
28
  this.repository = deps.repository;
29
+ this.config = deps.config;
26
30
  }
27
31
 
28
32
  /** Runtime read for plugins/hooks. Unset groups read as `{}`. */
@@ -40,7 +44,7 @@ export class SettingsService {
40
44
  actor?: Actor | null,
41
45
  ctx?: TxContext,
42
46
  ): Promise<Result<Record<string, unknown>>> {
43
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
47
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
44
48
  return Ok(await this.read(orgId, group, ctx));
45
49
  }
46
50
 
@@ -48,7 +52,7 @@ export class SettingsService {
48
52
  actor?: Actor | null,
49
53
  ctx?: TxContext,
50
54
  ): Promise<Result<Record<string, Record<string, unknown>>>> {
51
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
55
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
52
56
  const rows = await this.repository.findAll(orgId, ctx);
53
57
  const all: Record<string, Record<string, unknown>> = {};
54
58
  for (const row of rows) all[row.group] = row.value;
@@ -69,7 +73,7 @@ export class SettingsService {
69
73
  ),
70
74
  );
71
75
  }
72
- const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
76
+ const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
73
77
  const current = await this.read(orgId, group, ctx);
74
78
  const next: Record<string, unknown> = { ...current };
75
79
  for (const [key, value] of Object.entries(patch)) {