@endora-commerce/mod-catalog 0.100.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 (443) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/admin/components/PackagingUnitsEditor.d.ts +12 -0
  4. package/dist/admin/components/PackagingUnitsEditor.d.ts.map +1 -0
  5. package/dist/admin/components/PackagingUnitsEditor.js +99 -0
  6. package/dist/admin/components/PackagingUnitsEditor.js.map +1 -0
  7. package/dist/admin/components/ProductAttributesTab.d.ts +13 -0
  8. package/dist/admin/components/ProductAttributesTab.d.ts.map +1 -0
  9. package/dist/admin/components/ProductAttributesTab.js +140 -0
  10. package/dist/admin/components/ProductAttributesTab.js.map +1 -0
  11. package/dist/admin/components/ProductInventoryTab.d.ts +14 -0
  12. package/dist/admin/components/ProductInventoryTab.d.ts.map +1 -0
  13. package/dist/admin/components/ProductInventoryTab.js +284 -0
  14. package/dist/admin/components/ProductInventoryTab.js.map +1 -0
  15. package/dist/admin/components/ProductScopeEditor.d.ts +94 -0
  16. package/dist/admin/components/ProductScopeEditor.d.ts.map +1 -0
  17. package/dist/admin/components/ProductScopeEditor.js +395 -0
  18. package/dist/admin/components/ProductScopeEditor.js.map +1 -0
  19. package/dist/admin/components/ProductsBulkEditDialog.d.ts +11 -0
  20. package/dist/admin/components/ProductsBulkEditDialog.d.ts.map +1 -0
  21. package/dist/admin/components/ProductsBulkEditDialog.js +304 -0
  22. package/dist/admin/components/ProductsBulkEditDialog.js.map +1 -0
  23. package/dist/admin/index.d.ts +45 -0
  24. package/dist/admin/index.d.ts.map +1 -0
  25. package/dist/admin/index.js +184 -0
  26. package/dist/admin/index.js.map +1 -0
  27. package/dist/admin/lib/resolve-product-selection.d.ts +21 -0
  28. package/dist/admin/lib/resolve-product-selection.d.ts.map +1 -0
  29. package/dist/admin/lib/resolve-product-selection.js +25 -0
  30. package/dist/admin/lib/resolve-product-selection.js.map +1 -0
  31. package/dist/admin/pages/AttachmentTypesPage.d.ts +18 -0
  32. package/dist/admin/pages/AttachmentTypesPage.d.ts.map +1 -0
  33. package/dist/admin/pages/AttachmentTypesPage.js +92 -0
  34. package/dist/admin/pages/AttachmentTypesPage.js.map +1 -0
  35. package/dist/admin/pages/AttributeSetsPage.d.ts +21 -0
  36. package/dist/admin/pages/AttributeSetsPage.d.ts.map +1 -0
  37. package/dist/admin/pages/AttributeSetsPage.js +158 -0
  38. package/dist/admin/pages/AttributeSetsPage.js.map +1 -0
  39. package/dist/admin/pages/AttributesManager.d.ts +9 -0
  40. package/dist/admin/pages/AttributesManager.d.ts.map +1 -0
  41. package/dist/admin/pages/AttributesManager.js +305 -0
  42. package/dist/admin/pages/AttributesManager.js.map +1 -0
  43. package/dist/admin/pages/BulkOperationDetailPage.d.ts +9 -0
  44. package/dist/admin/pages/BulkOperationDetailPage.d.ts.map +1 -0
  45. package/dist/admin/pages/BulkOperationDetailPage.js +187 -0
  46. package/dist/admin/pages/BulkOperationDetailPage.js.map +1 -0
  47. package/dist/admin/pages/BulkOperationsPage.d.ts +9 -0
  48. package/dist/admin/pages/BulkOperationsPage.d.ts.map +1 -0
  49. package/dist/admin/pages/BulkOperationsPage.js +98 -0
  50. package/dist/admin/pages/BulkOperationsPage.js.map +1 -0
  51. package/dist/admin/pages/CategoriesTree.d.ts +9 -0
  52. package/dist/admin/pages/CategoriesTree.d.ts.map +1 -0
  53. package/dist/admin/pages/CategoriesTree.js +176 -0
  54. package/dist/admin/pages/CategoriesTree.js.map +1 -0
  55. package/dist/admin/pages/ProductEditor.d.ts +13 -0
  56. package/dist/admin/pages/ProductEditor.d.ts.map +1 -0
  57. package/dist/admin/pages/ProductEditor.js +1050 -0
  58. package/dist/admin/pages/ProductEditor.js.map +1 -0
  59. package/dist/admin/pages/ProductsList.d.ts +9 -0
  60. package/dist/admin/pages/ProductsList.d.ts.map +1 -0
  61. package/dist/admin/pages/ProductsList.js +399 -0
  62. package/dist/admin/pages/ProductsList.js.map +1 -0
  63. package/dist/backend/commands/attribute-commands.d.ts +111 -0
  64. package/dist/backend/commands/attribute-commands.d.ts.map +1 -0
  65. package/dist/backend/commands/attribute-commands.js +482 -0
  66. package/dist/backend/commands/attribute-commands.js.map +1 -0
  67. package/dist/backend/demo/reset.d.ts +23 -0
  68. package/dist/backend/demo/reset.d.ts.map +1 -0
  69. package/dist/backend/demo/reset.js +41 -0
  70. package/dist/backend/demo/reset.js.map +1 -0
  71. package/dist/backend/demo/rows.d.ts +103 -0
  72. package/dist/backend/demo/rows.d.ts.map +1 -0
  73. package/dist/backend/demo/rows.js +162 -0
  74. package/dist/backend/demo/rows.js.map +1 -0
  75. package/dist/backend/demo/seed.d.ts +35 -0
  76. package/dist/backend/demo/seed.d.ts.map +1 -0
  77. package/dist/backend/demo/seed.js +177 -0
  78. package/dist/backend/demo/seed.js.map +1 -0
  79. package/dist/backend/entities/attachment-type.entity.d.ts +16 -0
  80. package/dist/backend/entities/attachment-type.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/attachment-type.entity.js +57 -0
  82. package/dist/backend/entities/attachment-type.entity.js.map +1 -0
  83. package/dist/backend/entities/attribute-set-attribute.entity.d.ts +26 -0
  84. package/dist/backend/entities/attribute-set-attribute.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/attribute-set-attribute.entity.js +55 -0
  86. package/dist/backend/entities/attribute-set-attribute.entity.js.map +1 -0
  87. package/dist/backend/entities/attribute-set.entity.d.ts +22 -0
  88. package/dist/backend/entities/attribute-set.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/attribute-set.entity.js +67 -0
  90. package/dist/backend/entities/attribute-set.entity.js.map +1 -0
  91. package/dist/backend/entities/bulk-operation.entity.d.ts +78 -0
  92. package/dist/backend/entities/bulk-operation.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/bulk-operation.entity.js +138 -0
  94. package/dist/backend/entities/bulk-operation.entity.js.map +1 -0
  95. package/dist/backend/entities/bundle-slot-option.entity.d.ts +23 -0
  96. package/dist/backend/entities/bundle-slot-option.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/bundle-slot-option.entity.js +68 -0
  98. package/dist/backend/entities/bundle-slot-option.entity.js.map +1 -0
  99. package/dist/backend/entities/bundle-slot.entity.d.ts +25 -0
  100. package/dist/backend/entities/bundle-slot.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/bundle-slot.entity.js +74 -0
  102. package/dist/backend/entities/bundle-slot.entity.js.map +1 -0
  103. package/dist/backend/entities/category.entity.d.ts +26 -0
  104. package/dist/backend/entities/category.entity.d.ts.map +1 -0
  105. package/dist/backend/entities/category.entity.js +119 -0
  106. package/dist/backend/entities/category.entity.js.map +1 -0
  107. package/dist/backend/entities/gallery-item-label.entity.d.ts +22 -0
  108. package/dist/backend/entities/gallery-item-label.entity.d.ts.map +1 -0
  109. package/dist/backend/entities/gallery-item-label.entity.js +50 -0
  110. package/dist/backend/entities/gallery-item-label.entity.js.map +1 -0
  111. package/dist/backend/entities/gallery-item.entity.d.ts +17 -0
  112. package/dist/backend/entities/gallery-item.entity.d.ts.map +1 -0
  113. package/dist/backend/entities/gallery-item.entity.js +58 -0
  114. package/dist/backend/entities/gallery-item.entity.js.map +1 -0
  115. package/dist/backend/entities/grouped-item.entity.d.ts +23 -0
  116. package/dist/backend/entities/grouped-item.entity.d.ts.map +1 -0
  117. package/dist/backend/entities/grouped-item.entity.js +68 -0
  118. package/dist/backend/entities/grouped-item.entity.js.map +1 -0
  119. package/dist/backend/entities/product-attachment.entity.d.ts +23 -0
  120. package/dist/backend/entities/product-attachment.entity.d.ts.map +1 -0
  121. package/dist/backend/entities/product-attachment.entity.js +77 -0
  122. package/dist/backend/entities/product-attachment.entity.js.map +1 -0
  123. package/dist/backend/entities/product-attribute.entity.d.ts +72 -0
  124. package/dist/backend/entities/product-attribute.entity.d.ts.map +1 -0
  125. package/dist/backend/entities/product-attribute.entity.js +161 -0
  126. package/dist/backend/entities/product-attribute.entity.js.map +1 -0
  127. package/dist/backend/entities/product-editor-preference.entity.d.ts +28 -0
  128. package/dist/backend/entities/product-editor-preference.entity.d.ts.map +1 -0
  129. package/dist/backend/entities/product-editor-preference.entity.js +63 -0
  130. package/dist/backend/entities/product-editor-preference.entity.js.map +1 -0
  131. package/dist/backend/entities/product-link.entity.d.ts +25 -0
  132. package/dist/backend/entities/product-link.entity.d.ts.map +1 -0
  133. package/dist/backend/entities/product-link.entity.js +70 -0
  134. package/dist/backend/entities/product-link.entity.js.map +1 -0
  135. package/dist/backend/entities/product-packaging-unit.entity.d.ts +26 -0
  136. package/dist/backend/entities/product-packaging-unit.entity.d.ts.map +1 -0
  137. package/dist/backend/entities/product-packaging-unit.entity.js +76 -0
  138. package/dist/backend/entities/product-packaging-unit.entity.js.map +1 -0
  139. package/dist/backend/entities/product-value-override.entity.d.ts +43 -0
  140. package/dist/backend/entities/product-value-override.entity.d.ts.map +1 -0
  141. package/dist/backend/entities/product-value-override.entity.js +90 -0
  142. package/dist/backend/entities/product-value-override.entity.js.map +1 -0
  143. package/dist/backend/entities/product-variant.entity.d.ts +18 -0
  144. package/dist/backend/entities/product-variant.entity.d.ts.map +1 -0
  145. package/dist/backend/entities/product-variant.entity.js +68 -0
  146. package/dist/backend/entities/product-variant.entity.js.map +1 -0
  147. package/dist/backend/entities/product.entity.d.ts +108 -0
  148. package/dist/backend/entities/product.entity.d.ts.map +1 -0
  149. package/dist/backend/entities/product.entity.js +230 -0
  150. package/dist/backend/entities/product.entity.js.map +1 -0
  151. package/dist/backend/index.d.ts +262 -0
  152. package/dist/backend/index.d.ts.map +1 -0
  153. package/dist/backend/index.js +642 -0
  154. package/dist/backend/index.js.map +1 -0
  155. package/dist/backend/plugin.d.ts +216 -0
  156. package/dist/backend/plugin.d.ts.map +1 -0
  157. package/dist/backend/plugin.js +223 -0
  158. package/dist/backend/plugin.js.map +1 -0
  159. package/dist/backend/prompt-tools.d.ts +47 -0
  160. package/dist/backend/prompt-tools.d.ts.map +1 -0
  161. package/dist/backend/prompt-tools.js +284 -0
  162. package/dist/backend/prompt-tools.js.map +1 -0
  163. package/dist/backend/routes.admin.d.ts +105 -0
  164. package/dist/backend/routes.admin.d.ts.map +1 -0
  165. package/dist/backend/routes.admin.js +1162 -0
  166. package/dist/backend/routes.admin.js.map +1 -0
  167. package/dist/backend/routes.api-key.d.ts +33 -0
  168. package/dist/backend/routes.api-key.d.ts.map +1 -0
  169. package/dist/backend/routes.api-key.js +43 -0
  170. package/dist/backend/routes.api-key.js.map +1 -0
  171. package/dist/backend/routes.external.d.ts +32 -0
  172. package/dist/backend/routes.external.d.ts.map +1 -0
  173. package/dist/backend/routes.external.js +115 -0
  174. package/dist/backend/routes.external.js.map +1 -0
  175. package/dist/backend/routes.public.d.ts +46 -0
  176. package/dist/backend/routes.public.d.ts.map +1 -0
  177. package/dist/backend/routes.public.js +318 -0
  178. package/dist/backend/routes.public.js.map +1 -0
  179. package/dist/backend/services/asset-references.d.ts +4 -0
  180. package/dist/backend/services/asset-references.d.ts.map +1 -0
  181. package/dist/backend/services/asset-references.js +104 -0
  182. package/dist/backend/services/asset-references.js.map +1 -0
  183. package/dist/backend/services/attachment.service.d.ts +45 -0
  184. package/dist/backend/services/attachment.service.d.ts.map +1 -0
  185. package/dist/backend/services/attachment.service.js +268 -0
  186. package/dist/backend/services/attachment.service.js.map +1 -0
  187. package/dist/backend/services/attribute-option-validator.d.ts +47 -0
  188. package/dist/backend/services/attribute-option-validator.d.ts.map +1 -0
  189. package/dist/backend/services/attribute-option-validator.js +69 -0
  190. package/dist/backend/services/attribute-option-validator.js.map +1 -0
  191. package/dist/backend/services/attribute-set-validations.d.ts +65 -0
  192. package/dist/backend/services/attribute-set-validations.d.ts.map +1 -0
  193. package/dist/backend/services/attribute-set-validations.js +89 -0
  194. package/dist/backend/services/attribute-set-validations.js.map +1 -0
  195. package/dist/backend/services/attribute-set.service.d.ts +50 -0
  196. package/dist/backend/services/attribute-set.service.d.ts.map +1 -0
  197. package/dist/backend/services/attribute-set.service.js +401 -0
  198. package/dist/backend/services/attribute-set.service.js.map +1 -0
  199. package/dist/backend/services/attribute-type-mapping.d.ts +66 -0
  200. package/dist/backend/services/attribute-type-mapping.d.ts.map +1 -0
  201. package/dist/backend/services/attribute-type-mapping.js +121 -0
  202. package/dist/backend/services/attribute-type-mapping.js.map +1 -0
  203. package/dist/backend/services/attribute-value-key.service.d.ts +48 -0
  204. package/dist/backend/services/attribute-value-key.service.d.ts.map +1 -0
  205. package/dist/backend/services/attribute-value-key.service.js +118 -0
  206. package/dist/backend/services/attribute-value-key.service.js.map +1 -0
  207. package/dist/backend/services/audit-references.d.ts +13 -0
  208. package/dist/backend/services/audit-references.d.ts.map +1 -0
  209. package/dist/backend/services/audit-references.js +51 -0
  210. package/dist/backend/services/audit-references.js.map +1 -0
  211. package/dist/backend/services/bulk-operation-queue.d.ts +26 -0
  212. package/dist/backend/services/bulk-operation-queue.d.ts.map +1 -0
  213. package/dist/backend/services/bulk-operation-queue.js +34 -0
  214. package/dist/backend/services/bulk-operation-queue.js.map +1 -0
  215. package/dist/backend/services/bulk-operation.service.d.ts +195 -0
  216. package/dist/backend/services/bulk-operation.service.d.ts.map +1 -0
  217. package/dist/backend/services/bulk-operation.service.js +510 -0
  218. package/dist/backend/services/bulk-operation.service.js.map +1 -0
  219. package/dist/backend/services/bundle.service.d.ts +87 -0
  220. package/dist/backend/services/bundle.service.d.ts.map +1 -0
  221. package/dist/backend/services/bundle.service.js +286 -0
  222. package/dist/backend/services/bundle.service.js.map +1 -0
  223. package/dist/backend/services/catalog-admin.service.d.ts +398 -0
  224. package/dist/backend/services/catalog-admin.service.d.ts.map +1 -0
  225. package/dist/backend/services/catalog-admin.service.js +1516 -0
  226. package/dist/backend/services/catalog-admin.service.js.map +1 -0
  227. package/dist/backend/services/catalog-attribute-read.service.d.ts +78 -0
  228. package/dist/backend/services/catalog-attribute-read.service.d.ts.map +1 -0
  229. package/dist/backend/services/catalog-attribute-read.service.js +216 -0
  230. package/dist/backend/services/catalog-attribute-read.service.js.map +1 -0
  231. package/dist/backend/services/catalog-bulk-import.service.d.ts +30 -0
  232. package/dist/backend/services/catalog-bulk-import.service.d.ts.map +1 -0
  233. package/dist/backend/services/catalog-bulk-import.service.js +136 -0
  234. package/dist/backend/services/catalog-bulk-import.service.js.map +1 -0
  235. package/dist/backend/services/catalog-bulk-update.service.d.ts +91 -0
  236. package/dist/backend/services/catalog-bulk-update.service.d.ts.map +1 -0
  237. package/dist/backend/services/catalog-bulk-update.service.js +330 -0
  238. package/dist/backend/services/catalog-bulk-update.service.js.map +1 -0
  239. package/dist/backend/services/catalog-category-read.service.d.ts +66 -0
  240. package/dist/backend/services/catalog-category-read.service.d.ts.map +1 -0
  241. package/dist/backend/services/catalog-category-read.service.js +248 -0
  242. package/dist/backend/services/catalog-category-read.service.js.map +1 -0
  243. package/dist/backend/services/catalog-org-price-decorator.d.ts +90 -0
  244. package/dist/backend/services/catalog-org-price-decorator.d.ts.map +1 -0
  245. package/dist/backend/services/catalog-org-price-decorator.js +184 -0
  246. package/dist/backend/services/catalog-org-price-decorator.js.map +1 -0
  247. package/dist/backend/services/catalog-product-filter.service.d.ts +82 -0
  248. package/dist/backend/services/catalog-product-filter.service.d.ts.map +1 -0
  249. package/dist/backend/services/catalog-product-filter.service.js +275 -0
  250. package/dist/backend/services/catalog-product-filter.service.js.map +1 -0
  251. package/dist/backend/services/catalog-product-read.service.d.ts +46 -0
  252. package/dist/backend/services/catalog-product-read.service.d.ts.map +1 -0
  253. package/dist/backend/services/catalog-product-read.service.js +197 -0
  254. package/dist/backend/services/catalog-product-read.service.js.map +1 -0
  255. package/dist/backend/services/catalog-query.service.d.ts +444 -0
  256. package/dist/backend/services/catalog-query.service.d.ts.map +1 -0
  257. package/dist/backend/services/catalog-query.service.js +1633 -0
  258. package/dist/backend/services/catalog-query.service.js.map +1 -0
  259. package/dist/backend/services/catalog-quick-search.service.d.ts +78 -0
  260. package/dist/backend/services/catalog-quick-search.service.d.ts.map +1 -0
  261. package/dist/backend/services/catalog-quick-search.service.js +159 -0
  262. package/dist/backend/services/catalog-quick-search.service.js.map +1 -0
  263. package/dist/backend/services/catalog-write-ports.d.ts +25 -0
  264. package/dist/backend/services/catalog-write-ports.d.ts.map +1 -0
  265. package/dist/backend/services/catalog-write-ports.js +63 -0
  266. package/dist/backend/services/catalog-write-ports.js.map +1 -0
  267. package/dist/backend/services/category-admin.service.d.ts +102 -0
  268. package/dist/backend/services/category-admin.service.d.ts.map +1 -0
  269. package/dist/backend/services/category-admin.service.js +271 -0
  270. package/dist/backend/services/category-admin.service.js.map +1 -0
  271. package/dist/backend/services/gallery.service.d.ts +85 -0
  272. package/dist/backend/services/gallery.service.d.ts.map +1 -0
  273. package/dist/backend/services/gallery.service.js +326 -0
  274. package/dist/backend/services/gallery.service.js.map +1 -0
  275. package/dist/backend/services/grouped.service.d.ts +39 -0
  276. package/dist/backend/services/grouped.service.d.ts.map +1 -0
  277. package/dist/backend/services/grouped.service.js +136 -0
  278. package/dist/backend/services/grouped.service.js.map +1 -0
  279. package/dist/backend/services/label-resolver.d.ts +16 -0
  280. package/dist/backend/services/label-resolver.d.ts.map +1 -0
  281. package/dist/backend/services/label-resolver.js +43 -0
  282. package/dist/backend/services/label-resolver.js.map +1 -0
  283. package/dist/backend/services/packaging-unit.service.d.ts +28 -0
  284. package/dist/backend/services/packaging-unit.service.d.ts.map +1 -0
  285. package/dist/backend/services/packaging-unit.service.js +197 -0
  286. package/dist/backend/services/packaging-unit.service.js.map +1 -0
  287. package/dist/backend/services/primary-asset-url.d.ts +4 -0
  288. package/dist/backend/services/primary-asset-url.d.ts.map +1 -0
  289. package/dist/backend/services/primary-asset-url.js +55 -0
  290. package/dist/backend/services/primary-asset-url.js.map +1 -0
  291. package/dist/backend/services/product-editor-preferences.service.d.ts +20 -0
  292. package/dist/backend/services/product-editor-preferences.service.d.ts.map +1 -0
  293. package/dist/backend/services/product-editor-preferences.service.js +44 -0
  294. package/dist/backend/services/product-editor-preferences.service.js.map +1 -0
  295. package/dist/backend/services/product-link.service.d.ts +169 -0
  296. package/dist/backend/services/product-link.service.d.ts.map +1 -0
  297. package/dist/backend/services/product-link.service.js +297 -0
  298. package/dist/backend/services/product-link.service.js.map +1 -0
  299. package/dist/backend/services/product-overrides.service.d.ts +77 -0
  300. package/dist/backend/services/product-overrides.service.d.ts.map +1 -0
  301. package/dist/backend/services/product-overrides.service.js +204 -0
  302. package/dist/backend/services/product-overrides.service.js.map +1 -0
  303. package/dist/backend/services/product-scope-context.service.d.ts +36 -0
  304. package/dist/backend/services/product-scope-context.service.d.ts.map +1 -0
  305. package/dist/backend/services/product-scope-context.service.js +106 -0
  306. package/dist/backend/services/product-scope-context.service.js.map +1 -0
  307. package/dist/backend/services/product-type-validations.d.ts +42 -0
  308. package/dist/backend/services/product-type-validations.d.ts.map +1 -0
  309. package/dist/backend/services/product-type-validations.js +55 -0
  310. package/dist/backend/services/product-type-validations.js.map +1 -0
  311. package/dist/backend/services/product-value-resolver.service.d.ts +59 -0
  312. package/dist/backend/services/product-value-resolver.service.d.ts.map +1 -0
  313. package/dist/backend/services/product-value-resolver.service.js +111 -0
  314. package/dist/backend/services/product-value-resolver.service.js.map +1 -0
  315. package/dist/backend/services/system-attribute-scopes.d.ts +18 -0
  316. package/dist/backend/services/system-attribute-scopes.d.ts.map +1 -0
  317. package/dist/backend/services/system-attribute-scopes.js +18 -0
  318. package/dist/backend/services/system-attribute-scopes.js.map +1 -0
  319. package/dist/backend/services/viewer-organization.d.ts +25 -0
  320. package/dist/backend/services/viewer-organization.d.ts.map +1 -0
  321. package/dist/backend/services/viewer-organization.js +31 -0
  322. package/dist/backend/services/viewer-organization.js.map +1 -0
  323. package/dist/manifest.d.ts +204 -0
  324. package/dist/manifest.d.ts.map +1 -0
  325. package/dist/manifest.js +686 -0
  326. package/dist/manifest.js.map +1 -0
  327. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts +33 -0
  328. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts.map +1 -0
  329. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js +118 -0
  330. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js.map +1 -0
  331. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts +24 -0
  332. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts.map +1 -0
  333. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js +31 -0
  334. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js.map +1 -0
  335. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts +32 -0
  336. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts.map +1 -0
  337. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js +58 -0
  338. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js.map +1 -0
  339. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts +22 -0
  340. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts.map +1 -0
  341. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js +66 -0
  342. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js.map +1 -0
  343. package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts +18 -0
  344. package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts.map +1 -0
  345. package/dist/migrations/20260429T112543_catalog_product_attachments.js +73 -0
  346. package/dist/migrations/20260429T112543_catalog_product_attachments.js.map +1 -0
  347. package/dist/migrations/20260429T123726_catalog_product_links.d.ts +21 -0
  348. package/dist/migrations/20260429T123726_catalog_product_links.d.ts.map +1 -0
  349. package/dist/migrations/20260429T123726_catalog_product_links.js +48 -0
  350. package/dist/migrations/20260429T123726_catalog_product_links.js.map +1 -0
  351. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts +23 -0
  352. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts.map +1 -0
  353. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js +94 -0
  354. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js.map +1 -0
  355. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts +24 -0
  356. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts.map +1 -0
  357. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js +31 -0
  358. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js.map +1 -0
  359. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts +31 -0
  360. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts.map +1 -0
  361. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js +133 -0
  362. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js.map +1 -0
  363. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts +16 -0
  364. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts.map +1 -0
  365. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js +23 -0
  366. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js.map +1 -0
  367. package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts +10 -0
  368. package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts.map +1 -0
  369. package/dist/migrations/20260526T124736_catalog_product_status_inactive.js +22 -0
  370. package/dist/migrations/20260526T124736_catalog_product_status_inactive.js.map +1 -0
  371. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts +38 -0
  372. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts.map +1 -0
  373. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js +98 -0
  374. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js.map +1 -0
  375. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts +13 -0
  376. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts.map +1 -0
  377. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js +17 -0
  378. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js.map +1 -0
  379. package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts +16 -0
  380. package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts.map +1 -0
  381. package/dist/migrations/20260611T140407_catalog_bulk_operations.js +42 -0
  382. package/dist/migrations/20260611T140407_catalog_bulk_operations.js.map +1 -0
  383. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts +14 -0
  384. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts.map +1 -0
  385. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js +18 -0
  386. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js.map +1 -0
  387. package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts +11 -0
  388. package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts.map +1 -0
  389. package/dist/migrations/20260611T140412_catalog_product_packaging_units.js +29 -0
  390. package/dist/migrations/20260611T140412_catalog_product_packaging_units.js.map +1 -0
  391. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts +23 -0
  392. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts.map +1 -0
  393. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js +35 -0
  394. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js.map +1 -0
  395. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts +12 -0
  396. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts.map +1 -0
  397. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js +16 -0
  398. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js.map +1 -0
  399. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts +32 -0
  400. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts.map +1 -0
  401. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js +286 -0
  402. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js.map +1 -0
  403. package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts +36 -0
  404. package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts.map +1 -0
  405. package/dist/migrations/20260804T152604_catalog_widen_product_sku.js +63 -0
  406. package/dist/migrations/20260804T152604_catalog_widen_product_sku.js.map +1 -0
  407. package/dist/migrations/20260804T160244_catalog_category_activation.d.ts +18 -0
  408. package/dist/migrations/20260804T160244_catalog_category_activation.d.ts.map +1 -0
  409. package/dist/migrations/20260804T160244_catalog_category_activation.js +22 -0
  410. package/dist/migrations/20260804T160244_catalog_category_activation.js.map +1 -0
  411. package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts +27 -0
  412. package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts.map +1 -0
  413. package/dist/migrations/20260912T094557_catalog_sales_channel_products.js +44 -0
  414. package/dist/migrations/20260912T094557_catalog_sales_channel_products.js.map +1 -0
  415. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts +27 -0
  416. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts.map +1 -0
  417. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js +44 -0
  418. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js.map +1 -0
  419. package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts +36 -0
  420. package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts.map +1 -0
  421. package/dist/migrations/20260925T125527_catalog_inventory_columns.js +87 -0
  422. package/dist/migrations/20260925T125527_catalog_inventory_columns.js.map +1 -0
  423. package/dist/migrations/index.d.ts +55 -0
  424. package/dist/migrations/index.d.ts.map +1 -0
  425. package/dist/migrations/index.js +80 -0
  426. package/dist/migrations/index.js.map +1 -0
  427. package/dist/ports/index.d.ts +96 -0
  428. package/dist/ports/index.d.ts.map +1 -0
  429. package/dist/ports/index.js +2 -0
  430. package/dist/ports/index.js.map +1 -0
  431. package/docs/catalog/attachments.md +69 -0
  432. package/docs/catalog/attribute-sets.md +63 -0
  433. package/docs/catalog/attributes.md +244 -0
  434. package/docs/catalog/composite-products.md +115 -0
  435. package/docs/catalog/gallery-and-labels.md +73 -0
  436. package/docs/catalog/packaging-units.md +67 -0
  437. package/docs/catalog/per-channel-per-language-overrides.md +234 -0
  438. package/docs/catalog/product-links.md +79 -0
  439. package/docs/catalog.md +207 -0
  440. package/i18n/en.json +649 -0
  441. package/i18n/pl.json +649 -0
  442. package/package.json +109 -0
  443. package/tailwind.css +14 -0
@@ -0,0 +1,318 @@
1
+ import { z } from 'zod';
2
+ import { ERROR_CODES, isPriceSort, productLinkKindSchema, productListSortSchema, validateBundleConfigurationRequestSchema, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { effectiveState } from '@endora-commerce/platform/kernel';
5
+ import { getResolvedChannel } from '@endora-commerce/platform/kernel';
6
+ import { markPersonalisedPricing, productAudienceOf } from '@endora-commerce/platform/http';
7
+ /**
8
+ * Public catalog routes (US1 read surface).
9
+ * Storefront SSR + crawlers + anonymous API consumers. No auth.
10
+ *
11
+ * Read-backend selection (T067/T068):
12
+ * - When `CATALOG_SEARCH_BACKEND=meilisearch` AND a read backend was wired
13
+ * AND `search` is effectively present, list-products is served from
14
+ * Meilisearch.
15
+ * - When the index is unreachable, `searchQueryPort` answers
16
+ * `{ status: 'index-unavailable' }` and the route degrades to the Postgres
17
+ * path (R-08 reserved-fallback, so search is never fully broken).
18
+ * - The `changedSince` query param stays on Postgres because it has no
19
+ * equivalent in the Meilisearch index today.
20
+ * - Default backend remains Postgres so existing tests + deployments
21
+ * keep their behaviour without an opt-in.
22
+ */
23
+ const acceptLanguageHeaderSchema = z.string().optional();
24
+ export async function registerCatalogPublicRoutes(app, deps) {
25
+ const { queryService, searchQueryService } = deps;
26
+ const resolvePlaceholder = deps.resolveProductImagePlaceholderUrl;
27
+ // Fills `primaryAssetUrl` on imageless summaries with the configured
28
+ // placeholder (resolved once per request, only when at least one item needs
29
+ // it). Leaves products that already have an image untouched.
30
+ async function withListPlaceholder(result, salesChannelCode) {
31
+ if (!resolvePlaceholder)
32
+ return result;
33
+ if (!result.data.some((p) => !p.primaryAssetUrl))
34
+ return result;
35
+ const url = await resolvePlaceholder(salesChannelCode);
36
+ if (!url)
37
+ return result;
38
+ return {
39
+ ...result,
40
+ data: result.data.map((p) => p.primaryAssetUrl ? p : { ...p, primaryAssetUrl: url }),
41
+ };
42
+ }
43
+ /**
44
+ * Feature 086 / FR-016 — may this page order or filter by price at all?
45
+ *
46
+ * Two conditions, and both are refusals rather than degradations: an ordering
47
+ * by a number the page may not show discloses that number's rank, which is
48
+ * most of what a competitor wants, and it is cheap to get right and expensive
49
+ * to retrofit.
50
+ *
51
+ * - a **non-public sales channel** withholds prices wholesale — `CatalogQueryService`
52
+ * resolves none at all on one — so there is nothing to order by;
53
+ * - a **page-level display mode of `none`** is the supported "hide prices
54
+ * until login" configuration. Page-level, deliberately: a single product
55
+ * overridden to `none` keeps its position (spec clarification 2), because
56
+ * a per-product override reads as "ask us for a quote" rather than "this
57
+ * price is secret", and withdrawing the control for it would make the sort
58
+ * appear and disappear as a buyer walks the catalogue.
59
+ *
60
+ * The mode is resolved for **this viewer**, so the same shop refuses an
61
+ * anonymous visitor and serves a signed-in buyer where the two `pricing.*`
62
+ * settings keys differ, which is exactly what the configuration means.
63
+ */
64
+ async function priceControlsAvailable(ctx) {
65
+ if (!ctx.resolvedChannel.isPublic) {
66
+ return {
67
+ available: false,
68
+ reason: 'This sales channel does not publish prices, so the catalogue cannot be ordered or filtered by price.',
69
+ };
70
+ }
71
+ if (!deps.listingPriceOrder)
72
+ return { available: true };
73
+ const organization = ctx.audience.organizationId === null ? null : { id: ctx.audience.organizationId };
74
+ const mode = await deps.listingPriceOrder.pageDisplayMode({
75
+ context: {
76
+ salesChannel: {
77
+ id: ctx.resolvedChannel.id,
78
+ defaultCurrency: ctx.resolvedChannel.defaultCurrency,
79
+ },
80
+ organization,
81
+ },
82
+ });
83
+ if (mode === 'none') {
84
+ return {
85
+ available: false,
86
+ reason: 'Prices are not displayed on this page, so the catalogue cannot be ordered or filtered by price.',
87
+ };
88
+ }
89
+ return { available: true };
90
+ }
91
+ // GET /api/v1/catalog/products
92
+ app.get('/api/v1/catalog/products', async (request, reply) => {
93
+ const { q, limit, cursor, sort, categorySlug, attributeFilters, changedSince, minPrice, maxPrice } = parseListQuery(request);
94
+ const ctx = readContext(request);
95
+ // The page's prices are resolved for this caller, so a page resolved for a
96
+ // buying organisation must not enter a shared cache.
97
+ markPersonalisedPricing(reply, ctx.audience);
98
+ // FR-008 — a minimum above a maximum is a 400 naming both parameters, not
99
+ // an empty page. An empty page for a contradictory range is
100
+ // indistinguishable from an empty page for a genuine one.
101
+ if (minPrice !== undefined && maxPrice !== undefined && minPrice > maxPrice) {
102
+ throw new HttpError(400, ERROR_CODES.PRICE_RANGE_INVALID, 'The minimum price must not exceed the maximum price.', [
103
+ { path: 'minPrice', issue: 'must not exceed maxPrice' },
104
+ { path: 'maxPrice', issue: 'must not be below minPrice' },
105
+ ]);
106
+ }
107
+ // Resolved once and used twice: to refuse the request, and to tell the
108
+ // storefront whether to render the controls at all (FR-023). One answer, so
109
+ // a page cannot advertise a control the next request refuses.
110
+ const priceControls = await priceControlsAvailable(ctx);
111
+ const wantsPrice = isPriceSort(sort) || minPrice !== undefined || maxPrice !== undefined;
112
+ if (wantsPrice && !priceControls.available) {
113
+ request.log.info({ channel: ctx.resolvedChannel.code }, 'catalog: price ordering refused — this page does not display prices');
114
+ throw new HttpError(400, ERROR_CODES.PRICE_ORDERING_UNAVAILABLE, priceControls.reason);
115
+ }
116
+ const capabilities = { priceOrdering: priceControls.available };
117
+ // Issue #144 — the presence answer is *decided* here, before the query, and
118
+ // never caught after it. Constitution XVII: a module that is off behaves as
119
+ // if never installed, and with `search` never installed this listing is the
120
+ // Postgres query below.
121
+ //
122
+ // A degrade rather than a refusal, deliberately: `catalog` is
123
+ // non-deactivatable, Postgres is this route's default backend, and 503-ing a
124
+ // public catalogue because an optional search module is off would take the
125
+ // storefront down for a capability it never required. The manifest declares
126
+ // it, in `nonBindingDependencies`, so the operator's confirmation dialog
127
+ // says so before they flip `search` off.
128
+ //
129
+ // Issue #153 — `searchQueryService` is `search`'s **port** now, not a
130
+ // `SearchQueryService` this module constructed out of `search`'s class. The
131
+ // two facts that follow are why the code below looks different:
132
+ //
133
+ // - the presence probe is load-bearing rather than belt-and-braces. A
134
+ // port resolution is gated, so calling it with `search` off throws
135
+ // `ModuleDisabledError` — which is exactly what must not reach a public
136
+ // catalogue listing;
137
+ // - the unreachable-index fallback is read off the return type. It used to
138
+ // be `catch (err) { if (err instanceof SearchBackendUnavailable) … else
139
+ // throw err }`, which is the conditional re-throw `check:port-catches`
140
+ // refuses around a port call, and refuses because a status test lets a
141
+ // switched-off module through by accident. `SearchListOutcome` states
142
+ // the two answers, and the conversion happens inside `search`.
143
+ //
144
+ // Feature 086 adds one conjunct: **and no price ordering or price range was
145
+ // requested.** Not because Meilisearch cannot sort — issue #287 repaired
146
+ // that, and `name` and `-createdAt` are answered by the index now — but
147
+ // because the index has never carried a price, deliberately: one document
148
+ // per product per channel, and per-buyer pricing would multiply the corpus
149
+ // by the customer base. Nor could it express the resolution even if the
150
+ // fields were there: its `sort` is a tie-break *sequence*, not a coalesce,
151
+ // so `[priceOnListA, priceOnListB]` orders every product A prices above
152
+ // every product it does not and only then consults B — a different relation
153
+ // from "A's price if A prices it, else B's".
154
+ //
155
+ // `IndexedDocument.price` is not the exception it looks like. It carries
156
+ // the legacy `defaultPrice` attribute, is read by nothing on the query
157
+ // path, and is not any price list's figure.
158
+ const useMeili = process.env['CATALOG_SEARCH_BACKEND'] === 'meilisearch' &&
159
+ searchQueryService !== undefined &&
160
+ effectiveState.isPresent('search') &&
161
+ changedSince === undefined &&
162
+ !wantsPrice;
163
+ if (useMeili) {
164
+ const outcome = await searchQueryService.listProducts({
165
+ ...(q !== undefined ? { q } : {}),
166
+ limit,
167
+ ...(cursor !== undefined ? { cursor } : {}),
168
+ ...(sort !== undefined ? { sort } : {}),
169
+ ...(categorySlug !== undefined ? { categorySlug } : {}),
170
+ ...(attributeFilters !== undefined ? { attributeFilters } : {}),
171
+ }, ctx);
172
+ if (outcome.status === 'ok') {
173
+ reply.header('x-search-backend', 'meilisearch');
174
+ return {
175
+ ...(await withListPlaceholder(outcome.result, ctx.resolvedChannel.code)),
176
+ capabilities,
177
+ };
178
+ }
179
+ request.log.warn({ err: outcome.reason }, 'meilisearch unavailable; falling back to postgres');
180
+ }
181
+ const result = await queryService.listProducts({ q, limit, cursor, sort, categorySlug, attributeFilters, changedSince, minPrice, maxPrice }, ctx);
182
+ reply.header('x-search-backend', 'postgres');
183
+ return { ...(await withListPlaceholder(result, ctx.resolvedChannel.code)), capabilities };
184
+ });
185
+ // GET /api/v1/catalog/products/:idOrSlug
186
+ app.get('/api/v1/catalog/products/:idOrSlug', async (request, reply) => {
187
+ const ctx = readContext(request);
188
+ markPersonalisedPricing(reply, ctx.audience);
189
+ const product = await queryService.getProductByIdOrSlug(request.params.idOrSlug, ctx);
190
+ // Imageless product → fall back to the configured placeholder so the PDP
191
+ // hero/card renders something instead of an empty box.
192
+ if (resolvePlaceholder &&
193
+ !product.primaryAssetUrl &&
194
+ product.assets.length === 0) {
195
+ const url = await resolvePlaceholder(ctx.resolvedChannel.code);
196
+ if (url)
197
+ product.primaryAssetUrl = url;
198
+ }
199
+ return { data: product };
200
+ });
201
+ // Feature 002 US4 — public Product Links read.
202
+ if (deps.productLinkService) {
203
+ const links = deps.productLinkService;
204
+ app.get('/api/v1/catalog/products/:idOrSlug/links', async (request, reply) => {
205
+ const ctx = readContext(request);
206
+ markPersonalisedPricing(reply, ctx.audience);
207
+ const product = await queryService.getProductByIdOrSlug(request.params.idOrSlug, ctx);
208
+ const kind = request.query.kind
209
+ ? productLinkKindSchema.parse(request.query.kind)
210
+ : undefined;
211
+ const rows = await links.listForStorefront(product.id, ctx, kind);
212
+ return { data: rows };
213
+ });
214
+ }
215
+ // Feature 002 US5 — public bundle-configuration validation. Pure
216
+ // compute, no state mutation; only POST on the public surface. Errors
217
+ // are returned inside the data envelope so the storefront can highlight
218
+ // each offending slot — except PRODUCT_TYPE_MISMATCH (400) and
219
+ // PRODUCT_NOT_FOUND (404), which are HTTP-level rejections.
220
+ if (deps.bundleService) {
221
+ const bundle = deps.bundleService;
222
+ app.post('/api/v1/catalog/products/:idOrSlug/bundle-configuration/validate', {
223
+ schema: { body: validateBundleConfigurationRequestSchema },
224
+ }, async (request) => {
225
+ const ctx = readContext(request);
226
+ const product = await queryService.getProductByIdOrSlug(request.params.idOrSlug, ctx);
227
+ const body = validateBundleConfigurationRequestSchema.parse(request.body);
228
+ const result = await bundle.validateConfiguration(product.id, body.selections);
229
+ return { data: result };
230
+ });
231
+ }
232
+ // GET /api/v1/catalog/categories
233
+ app.get('/api/v1/catalog/categories', async (request) => {
234
+ const ctx = readContext(request);
235
+ const tree = await queryService.getCategoryTree(ctx);
236
+ return { data: tree };
237
+ });
238
+ // GET /api/v1/catalog/filters
239
+ app.get('/api/v1/catalog/filters', async (request) => {
240
+ const ctx = readContext(request);
241
+ const filters = await queryService.getFilterDefinitions(ctx);
242
+ return { data: filters };
243
+ });
244
+ }
245
+ function readContext(request) {
246
+ // Feature 053 / FR-002: the sales channel is resolved once by the canonical
247
+ // middleware. Read it here instead of re-parsing the `x-sales-channel` header.
248
+ const ch = getResolvedChannel(request);
249
+ const acceptLanguage = acceptLanguageHeaderSchema.parse(request.headers['accept-language']);
250
+ const preferredLanguage = acceptLanguage ? acceptLanguage.split(',')[0]?.trim() : undefined;
251
+ return {
252
+ resolvedChannel: {
253
+ id: ch.id,
254
+ code: ch.code,
255
+ isPublic: ch.isPublic,
256
+ defaultCurrency: ch.defaultCurrency,
257
+ defaultLanguage: ch.defaultLanguage,
258
+ },
259
+ audience: productAudienceOf(request),
260
+ preferredLanguage,
261
+ };
262
+ }
263
+ /**
264
+ * Parses list-query parameters out of the request URL, including the nested
265
+ * `filter[attr.<key>]=value` syntax. We hand-parse this because Fastify's
266
+ * default query parser flattens bracketed keys into strings.
267
+ */
268
+ function parseListQuery(request) {
269
+ const raw = (request.query ?? {});
270
+ const q = typeof raw['q'] === 'string' ? raw['q'] : undefined;
271
+ const limitRaw = typeof raw['limit'] === 'string' ? Number(raw['limit']) : undefined;
272
+ const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(200, limitRaw) : 50;
273
+ const cursor = typeof raw['cursor'] === 'string' ? raw['cursor'] : undefined;
274
+ const changedSince = typeof raw['changedSince'] === 'string' ? raw['changedSince'] : undefined;
275
+ // The enum is the contract's, read once, so a member added there reaches this
276
+ // route without a second list to keep in step (feature 086 added two).
277
+ const sortRaw = typeof raw['sort'] === 'string' ? raw['sort'] : undefined;
278
+ const sortParsed = productListSortSchema.safeParse(sortRaw);
279
+ const sort = sortParsed.success ? sortParsed.data : undefined;
280
+ // Feature 086 — the range bounds. A non-numeric or negative bound is dropped
281
+ // rather than refused, which is what every other malformed parameter on this
282
+ // hand-parsed surface does; `minPrice > maxPrice` is the one the buyer is
283
+ // told about, because it is the one they typed on purpose.
284
+ const parseBound = (value) => {
285
+ if (typeof value !== 'string' || value.trim() === '')
286
+ return undefined;
287
+ const n = Number(value);
288
+ return Number.isFinite(n) && n >= 0 ? n : undefined;
289
+ };
290
+ const minPrice = parseBound(raw['minPrice']);
291
+ const maxPrice = parseBound(raw['maxPrice']);
292
+ const attributeFilters = {};
293
+ let categorySlug;
294
+ for (const [key, value] of Object.entries(raw)) {
295
+ const attrMatch = /^filter\[attr\.([^\]]+)\]$/.exec(key);
296
+ if (attrMatch && attrMatch[1]) {
297
+ const attrKey = attrMatch[1];
298
+ const values = Array.isArray(value) ? value.map(String) : [String(value)];
299
+ attributeFilters[attrKey] = values;
300
+ continue;
301
+ }
302
+ if (key === 'filter[category]' && typeof value === 'string') {
303
+ categorySlug = value;
304
+ }
305
+ }
306
+ return {
307
+ q,
308
+ limit,
309
+ cursor,
310
+ sort,
311
+ categorySlug,
312
+ ...(Object.keys(attributeFilters).length > 0 ? { attributeFilters } : {}),
313
+ changedSince,
314
+ minPrice,
315
+ maxPrice,
316
+ };
317
+ }
318
+ //# sourceMappingURL=routes.public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.public.js","sourceRoot":"","sources":["../../src/backend/routes.public.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AssetReferenceRegistryPort } from '@endora-commerce/contracts';
3
+ export declare function registerCatalogAssetReferences(registry: AssetReferenceRegistryPort, emFactory: () => EntityManager): void;
4
+ //# sourceMappingURL=asset-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-references.d.ts","sourceRoot":"","sources":["../../../src/backend/services/asset-references.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,4BAA4B,CAAC;AAEpC,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,0BAA0B,EACpC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAQN"}
@@ -0,0 +1,104 @@
1
+ // Catalog → Assets Library reference descriptors — feature 013 / US2 / T064.
2
+ //
3
+ // Registers four FK descriptors that the AssetReferenceRegistry consults
4
+ // when an admin tries to delete an asset (FR-030). Each descriptor runs a
5
+ // single batched query (`asset_id = ANY($1)`) and joins back to the parent
6
+ // row's display info for the human-readable label rendered in the admin
7
+ // "in use by" dialog.
8
+ export function registerCatalogAssetReferences(registry, emFactory) {
9
+ const descriptors = [
10
+ productGalleryDescriptor(emFactory),
11
+ productAttachmentDescriptor(emFactory),
12
+ productVirtualDownloadDescriptor(emFactory),
13
+ categoryMainImageDescriptor(emFactory),
14
+ ];
15
+ for (const d of descriptors)
16
+ registry.register(d);
17
+ }
18
+ function productGalleryDescriptor(emFactory) {
19
+ return {
20
+ ownerModuleId: 'catalog',
21
+ async findReferences(assetIds) {
22
+ if (assetIds.length === 0)
23
+ return [];
24
+ const em = emFactory();
25
+ const rows = (await em.execute(`select gi.asset_id::text as asset_id,
26
+ gi.id::text as item_id,
27
+ gi.position,
28
+ p.id::text as product_id,
29
+ p.name as product_name
30
+ from gallery_items gi
31
+ join products p on p.id = gi.product_id
32
+ where gi.asset_id in (${assetIds.map(() => '?').join(',')})`, assetIds));
33
+ return rows.map((r) => ({
34
+ kind: 'product_gallery',
35
+ entityId: r.product_id,
36
+ label: `Product "${pickName(r.product_name)}" — gallery position ${r.position}`,
37
+ }));
38
+ },
39
+ };
40
+ }
41
+ function productAttachmentDescriptor(emFactory) {
42
+ return {
43
+ ownerModuleId: 'catalog',
44
+ async findReferences(assetIds) {
45
+ if (assetIds.length === 0)
46
+ return [];
47
+ const em = emFactory();
48
+ const rows = (await em.execute(`select pa.asset_id::text as asset_id,
49
+ pa.name as attachment_name,
50
+ p.id::text as product_id,
51
+ p.name as product_name
52
+ from product_attachments pa
53
+ join products p on p.id = pa.product_id
54
+ where pa.asset_id in (${assetIds.map(() => '?').join(',')})`, assetIds));
55
+ return rows.map((r) => ({
56
+ kind: 'product_attachment',
57
+ entityId: r.product_id,
58
+ label: `Product "${pickName(r.product_name)}" — attachment "${r.attachment_name}"`,
59
+ }));
60
+ },
61
+ };
62
+ }
63
+ function productVirtualDownloadDescriptor(emFactory) {
64
+ return {
65
+ ownerModuleId: 'catalog',
66
+ async findReferences(assetIds) {
67
+ if (assetIds.length === 0)
68
+ return [];
69
+ const em = emFactory();
70
+ const rows = (await em.execute(`select p.id::text as product_id, p.name as product_name
71
+ from products p
72
+ where p.download_asset_id in (${assetIds.map(() => '?').join(',')})`, assetIds));
73
+ return rows.map((r) => ({
74
+ kind: 'product_virtual_download',
75
+ entityId: r.product_id,
76
+ label: `Product "${pickName(r.product_name)}" — virtual download`,
77
+ }));
78
+ },
79
+ };
80
+ }
81
+ function categoryMainImageDescriptor(emFactory) {
82
+ return {
83
+ ownerModuleId: 'catalog',
84
+ async findReferences(assetIds) {
85
+ if (assetIds.length === 0)
86
+ return [];
87
+ const em = emFactory();
88
+ const rows = (await em.execute(`select c.id::text as category_id, c.name as category_name
89
+ from categories c
90
+ where c.main_image_asset_id in (${assetIds.map(() => '?').join(',')})`, assetIds));
91
+ return rows.map((r) => ({
92
+ kind: 'category_main_image',
93
+ entityId: r.category_id,
94
+ label: `Category "${pickName(r.category_name)}" — main image`,
95
+ }));
96
+ },
97
+ };
98
+ }
99
+ function pickName(blob) {
100
+ if (typeof blob === 'string')
101
+ return blob;
102
+ return blob['en-US'] ?? blob['en'] ?? blob['pl'] ?? Object.values(blob)[0] ?? '(unnamed)';
103
+ }
104
+ //# sourceMappingURL=asset-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-references.js","sourceRoot":"","sources":["../../../src/backend/services/asset-references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AttachmentService — admin-only CRUD for AttachmentTypes (global
3
+ * dictionary) and ProductAttachments (per-product wrappers around an
4
+ * Asset). Feature 002 US3, data-model.md §2.5, §2.6.
5
+ *
6
+ * Foundation has no public AssetsService — Asset is imported as an
7
+ * entity directly (research.md R-9 + foundation pattern), and the
8
+ * service layer guards `kind ∈ {pdf, certificate, other}` for
9
+ * attachments (image / video are reserved for the gallery).
10
+ */
11
+ import type { EntityManager } from '@mikro-orm/postgresql';
12
+ import { type AssetReadPort, type AttachmentType as AttachmentTypeDto, type CreateAttachmentTypeRequest, type UpdateAttachmentTypeRequest, type ProductAttachment as ProductAttachmentDto, type CreateAttachmentRequest, type UpdateAttachmentRequest } from '@endora-commerce/contracts';
13
+ import type { CommandBus } from '@endora-commerce/platform/commands';
14
+ export declare class AttachmentService {
15
+ #private;
16
+ private readonly emFactory;
17
+ /** Feature 054 — audits attachment writes co-transactionally when provided. */
18
+ private readonly commandBus?;
19
+ /**
20
+ * Feature 075 — `assets_library`'s read port, where `em.findOne(Asset, …)`
21
+ * used to be. The check below is the reason this read exists: an attach
22
+ * that cannot verify the asset would store a dangling id, so with
23
+ * `assets_library` off the port fails closed and the attach is refused.
24
+ */
25
+ private readonly assets?;
26
+ constructor(emFactory: () => EntityManager,
27
+ /** Feature 054 — audits attachment writes co-transactionally when provided. */
28
+ commandBus?: CommandBus | undefined,
29
+ /**
30
+ * Feature 075 — `assets_library`'s read port, where `em.findOne(Asset, …)`
31
+ * used to be. The check below is the reason this read exists: an attach
32
+ * that cannot verify the asset would store a dangling id, so with
33
+ * `assets_library` off the port fails closed and the attach is refused.
34
+ */
35
+ assets?: AssetReadPort | undefined);
36
+ listTypes(): Promise<AttachmentTypeDto[]>;
37
+ createType(req: CreateAttachmentTypeRequest): Promise<AttachmentTypeDto>;
38
+ updateType(id: string, req: UpdateAttachmentTypeRequest): Promise<AttachmentTypeDto>;
39
+ deleteType(id: string): Promise<void>;
40
+ listAttachments(productId: string): Promise<ProductAttachmentDto[]>;
41
+ createAttachment(productId: string, req: CreateAttachmentRequest): Promise<ProductAttachmentDto>;
42
+ updateAttachment(productId: string, attachmentId: string, req: UpdateAttachmentRequest): Promise<ProductAttachmentDto>;
43
+ deleteAttachment(productId: string, attachmentId: string): Promise<void>;
44
+ }
45
+ //# sourceMappingURL=attachment.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/attachment.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,IAAI,iBAAiB,EACxC,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,IAAI,oBAAoB,EAC9C,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAOrE,qBAAa,iBAAiB;;IAE1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBATP,SAAS,EAAE,MAAM,aAAa;IAC/C,+EAA+E;IAC9D,UAAU,CAAC,EAAE,UAAU,YAAA;IACxC;;;;;OAKG;IACc,MAAM,CAAC,EAAE,aAAa,YAAA;IAkCnC,SAAS,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA2BzC,UAAU,CAAC,GAAG,EAAE,2BAA2B,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkCxE,UAAU,CACd,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,2BAA2B,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IAwCvB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BrC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAWnE,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IA+C1B,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAkC1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkE/E"}