@porulle/core 0.7.0 → 0.9.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 (325) hide show
  1. package/dist/auth/middleware.d.ts.map +1 -1
  2. package/dist/auth/middleware.js +26 -7
  3. package/dist/auth/setup.d.ts.map +1 -1
  4. package/dist/auth/setup.js +8 -5
  5. package/dist/config/defaults.d.ts.map +1 -1
  6. package/dist/config/defaults.js +7 -0
  7. package/dist/config/types.d.ts +26 -1
  8. package/dist/config/types.d.ts.map +1 -1
  9. package/dist/generated/plugin-manifest.d.ts +3 -0
  10. package/dist/generated/plugin-manifest.d.ts.map +1 -1
  11. package/dist/generated/plugin-manifest.js +1 -0
  12. package/dist/hooks/checkout-completion.d.ts.map +1 -1
  13. package/dist/hooks/checkout-completion.js +2 -2
  14. package/dist/hooks/checkout.d.ts.map +1 -1
  15. package/dist/hooks/checkout.js +17 -2
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/interfaces/rest/index.d.ts.map +1 -1
  20. package/dist/interfaces/rest/index.js +12 -0
  21. package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
  22. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
  23. package/dist/interfaces/rest/routes/admin/staff.js +206 -0
  24. package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
  25. package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
  26. package/dist/interfaces/rest/routes/analytics.js +24 -0
  27. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  28. package/dist/interfaces/rest/routes/carts.js +39 -2
  29. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  30. package/dist/interfaces/rest/routes/catalog.js +17 -1
  31. package/dist/interfaces/rest/routes/checkout.d.ts +9 -0
  32. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  33. package/dist/interfaces/rest/routes/checkout.js +97 -21
  34. package/dist/interfaces/rest/routes/documents.d.ts +10 -0
  35. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
  36. package/dist/interfaces/rest/routes/documents.js +40 -0
  37. package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
  38. package/dist/interfaces/rest/routes/inventory.js +13 -0
  39. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  40. package/dist/interfaces/rest/routes/orders.js +111 -1
  41. package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
  42. package/dist/interfaces/rest/routes/pricing.js +36 -1
  43. package/dist/interfaces/rest/routes/settings.d.ts +5 -0
  44. package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
  45. package/dist/interfaces/rest/routes/settings.js +50 -0
  46. package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
  47. package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
  48. package/dist/interfaces/rest/routes/shipping.js +68 -0
  49. package/dist/interfaces/rest/routes/tax.d.ts +5 -0
  50. package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
  51. package/dist/interfaces/rest/routes/tax.js +68 -0
  52. package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
  53. package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
  54. package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
  55. package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
  56. package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
  57. package/dist/interfaces/rest/schemas/analytics.js +33 -0
  58. package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
  59. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  60. package/dist/interfaces/rest/schemas/carts.js +38 -0
  61. package/dist/interfaces/rest/schemas/catalog.d.ts +1218 -1
  62. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  63. package/dist/interfaces/rest/schemas/catalog.js +48 -0
  64. package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
  65. package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
  66. package/dist/interfaces/rest/schemas/checkout.js +4 -0
  67. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  68. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  69. package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
  70. package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
  71. package/dist/interfaces/rest/schemas/documents.js +65 -0
  72. package/dist/interfaces/rest/schemas/orders.d.ts +3110 -43
  73. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  74. package/dist/interfaces/rest/schemas/orders.js +234 -1
  75. package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
  76. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  77. package/dist/interfaces/rest/schemas/pricing.js +70 -0
  78. package/dist/interfaces/rest/schemas/responses.d.ts +190 -0
  79. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  80. package/dist/interfaces/rest/schemas/search.d.ts +17 -0
  81. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  82. package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
  83. package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
  84. package/dist/interfaces/rest/schemas/settings.js +79 -0
  85. package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
  86. package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
  87. package/dist/interfaces/rest/schemas/shipping.js +146 -0
  88. package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
  89. package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
  90. package/dist/interfaces/rest/schemas/tax.js +132 -0
  91. package/dist/kernel/database/schema.d.ts +4 -0
  92. package/dist/kernel/database/schema.d.ts.map +1 -1
  93. package/dist/kernel/database/schema.js +8 -0
  94. package/dist/kernel/database/scoped-db.d.ts +2 -1
  95. package/dist/kernel/database/scoped-db.d.ts.map +1 -1
  96. package/dist/kernel/database/scoped-db.js +62 -1
  97. package/dist/kernel/hooks/types.d.ts +1 -1
  98. package/dist/kernel/hooks/types.d.ts.map +1 -1
  99. package/dist/kernel/plugin/manifest.d.ts +26 -0
  100. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  101. package/dist/kernel/plugin/manifest.js +17 -2
  102. package/dist/modules/analytics/drizzle-adapter.d.ts.map +1 -1
  103. package/dist/modules/analytics/drizzle-adapter.js +16 -3
  104. package/dist/modules/analytics/module.d.ts.map +1 -1
  105. package/dist/modules/analytics/module.js +6 -0
  106. package/dist/modules/analytics/reports.d.ts +58 -0
  107. package/dist/modules/analytics/reports.d.ts.map +1 -0
  108. package/dist/modules/analytics/reports.js +249 -0
  109. package/dist/modules/analytics/service.d.ts +10 -0
  110. package/dist/modules/analytics/service.d.ts.map +1 -1
  111. package/dist/modules/analytics/service.js +14 -0
  112. package/dist/modules/cart/repository/index.d.ts +7 -0
  113. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  114. package/dist/modules/cart/repository/index.js +25 -1
  115. package/dist/modules/cart/schema.d.ts +17 -0
  116. package/dist/modules/cart/schema.d.ts.map +1 -1
  117. package/dist/modules/cart/schema.js +2 -0
  118. package/dist/modules/cart/schemas.d.ts +1 -0
  119. package/dist/modules/cart/schemas.d.ts.map +1 -1
  120. package/dist/modules/cart/schemas.js +1 -0
  121. package/dist/modules/cart/service.d.ts +32 -0
  122. package/dist/modules/cart/service.d.ts.map +1 -1
  123. package/dist/modules/cart/service.js +61 -2
  124. package/dist/modules/catalog/entity-service.d.ts +21 -0
  125. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  126. package/dist/modules/catalog/entity-service.js +166 -5
  127. package/dist/modules/catalog/schema.d.ts +34 -0
  128. package/dist/modules/catalog/schema.d.ts.map +1 -1
  129. package/dist/modules/catalog/schema.js +5 -0
  130. package/dist/modules/catalog/schemas.d.ts +3 -0
  131. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  132. package/dist/modules/catalog/schemas.js +3 -0
  133. package/dist/modules/catalog/service.d.ts +36 -0
  134. package/dist/modules/catalog/service.d.ts.map +1 -1
  135. package/dist/modules/catalog/service.js +6 -0
  136. package/dist/modules/customers/service.d.ts.map +1 -1
  137. package/dist/modules/customers/service.js +6 -0
  138. package/dist/modules/documents/module.d.ts +7 -0
  139. package/dist/modules/documents/module.d.ts.map +1 -0
  140. package/dist/modules/documents/module.js +12 -0
  141. package/dist/modules/documents/pdf.d.ts +25 -0
  142. package/dist/modules/documents/pdf.d.ts.map +1 -0
  143. package/dist/modules/documents/pdf.js +80 -0
  144. package/dist/modules/documents/render.d.ts +32 -0
  145. package/dist/modules/documents/render.d.ts.map +1 -0
  146. package/dist/modules/documents/render.js +190 -0
  147. package/dist/modules/documents/repository/index.d.ts +25 -0
  148. package/dist/modules/documents/repository/index.d.ts.map +1 -0
  149. package/dist/modules/documents/repository/index.js +47 -0
  150. package/dist/modules/documents/schema.d.ts +214 -0
  151. package/dist/modules/documents/schema.d.ts.map +1 -0
  152. package/dist/modules/documents/schema.js +42 -0
  153. package/dist/modules/documents/service.d.ts +51 -0
  154. package/dist/modules/documents/service.d.ts.map +1 -0
  155. package/dist/modules/documents/service.js +119 -0
  156. package/dist/modules/fulfillment/service.d.ts +18 -0
  157. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  158. package/dist/modules/fulfillment/service.js +85 -1
  159. package/dist/modules/orders/repository/index.d.ts +18 -1
  160. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  161. package/dist/modules/orders/repository/index.js +86 -1
  162. package/dist/modules/orders/schema.d.ts +391 -0
  163. package/dist/modules/orders/schema.d.ts.map +1 -1
  164. package/dist/modules/orders/schema.js +56 -1
  165. package/dist/modules/orders/service.d.ts +102 -3
  166. package/dist/modules/orders/service.d.ts.map +1 -1
  167. package/dist/modules/orders/service.js +653 -29
  168. package/dist/modules/orders/stale-order-cleanup.d.ts.map +1 -1
  169. package/dist/modules/orders/stale-order-cleanup.js +13 -1
  170. package/dist/modules/pricing/repository/index.d.ts +5 -0
  171. package/dist/modules/pricing/repository/index.d.ts.map +1 -1
  172. package/dist/modules/pricing/repository/index.js +19 -0
  173. package/dist/modules/pricing/service.d.ts +18 -0
  174. package/dist/modules/pricing/service.d.ts.map +1 -1
  175. package/dist/modules/pricing/service.js +43 -5
  176. package/dist/modules/settings/module.d.ts +6 -0
  177. package/dist/modules/settings/module.d.ts.map +1 -0
  178. package/dist/modules/settings/module.js +11 -0
  179. package/dist/modules/settings/repository/index.d.ts +16 -0
  180. package/dist/modules/settings/repository/index.d.ts.map +1 -0
  181. package/dist/modules/settings/repository/index.js +41 -0
  182. package/dist/modules/settings/schema.d.ts +121 -0
  183. package/dist/modules/settings/schema.d.ts.map +1 -0
  184. package/dist/modules/settings/schema.js +24 -0
  185. package/dist/modules/settings/service.d.ts +26 -0
  186. package/dist/modules/settings/service.d.ts.map +1 -0
  187. package/dist/modules/settings/service.js +51 -0
  188. package/dist/modules/shipping/calculator.d.ts +5 -0
  189. package/dist/modules/shipping/calculator.d.ts.map +1 -1
  190. package/dist/modules/shipping/calculator.js +10 -0
  191. package/dist/modules/shipping/module.d.ts +5 -1
  192. package/dist/modules/shipping/module.d.ts.map +1 -1
  193. package/dist/modules/shipping/module.js +4 -1
  194. package/dist/modules/shipping/repository/index.d.ts +27 -2
  195. package/dist/modules/shipping/repository/index.d.ts.map +1 -1
  196. package/dist/modules/shipping/repository/index.js +105 -1
  197. package/dist/modules/shipping/schema.d.ts +419 -0
  198. package/dist/modules/shipping/schema.d.ts.map +1 -0
  199. package/dist/modules/shipping/schema.js +56 -0
  200. package/dist/modules/shipping/service.d.ts +54 -0
  201. package/dist/modules/shipping/service.d.ts.map +1 -1
  202. package/dist/modules/shipping/service.js +161 -2
  203. package/dist/modules/tax/adapter.d.ts +13 -0
  204. package/dist/modules/tax/adapter.d.ts.map +1 -1
  205. package/dist/modules/tax/module.d.ts +5 -1
  206. package/dist/modules/tax/module.d.ts.map +1 -1
  207. package/dist/modules/tax/module.js +4 -1
  208. package/dist/modules/tax/repository/index.d.ts +38 -2
  209. package/dist/modules/tax/repository/index.d.ts.map +1 -1
  210. package/dist/modules/tax/repository/index.js +138 -1
  211. package/dist/modules/tax/schema.d.ts +355 -0
  212. package/dist/modules/tax/schema.d.ts.map +1 -0
  213. package/dist/modules/tax/schema.js +55 -0
  214. package/dist/modules/tax/service.d.ts +60 -1
  215. package/dist/modules/tax/service.d.ts.map +1 -1
  216. package/dist/modules/tax/service.js +227 -2
  217. package/dist/runtime/kernel-modules.d.ts +15 -2
  218. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  219. package/dist/runtime/kernel-modules.js +4 -0
  220. package/dist/runtime/kernel-types.d.ts +5 -1
  221. package/dist/runtime/kernel-types.d.ts.map +1 -1
  222. package/dist/runtime/kernel-types.js +2 -0
  223. package/dist/runtime/server.js +1 -1
  224. package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
  225. package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
  226. package/dist/test-utils/create-plugin-test-app.js +15 -3
  227. package/dist/test-utils/create-test-config.d.ts.map +1 -1
  228. package/dist/test-utils/create-test-config.js +2 -0
  229. package/dist/test-utils/order-test-helpers.d.ts +12 -0
  230. package/dist/test-utils/order-test-helpers.d.ts.map +1 -0
  231. package/dist/test-utils/order-test-helpers.js +18 -0
  232. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  233. package/dist/test-utils/rest-api-test-utils.js +7 -0
  234. package/dist/test-utils/test-actors.js +1 -1
  235. package/dist/testing.d.ts +1 -0
  236. package/dist/testing.d.ts.map +1 -1
  237. package/dist/testing.js +1 -0
  238. package/package.json +3 -3
  239. package/src/auth/middleware.ts +26 -7
  240. package/src/auth/setup.ts +11 -4
  241. package/src/config/defaults.ts +7 -0
  242. package/src/config/types.ts +26 -1
  243. package/src/generated/plugin-capabilities.d.ts +1 -0
  244. package/src/generated/plugin-manifest.ts +1 -0
  245. package/src/generated/plugin-repositories.d.ts +1 -0
  246. package/src/hooks/checkout-completion.ts +22 -16
  247. package/src/hooks/checkout.ts +36 -11
  248. package/src/index.ts +1 -0
  249. package/src/interfaces/rest/index.ts +12 -0
  250. package/src/interfaces/rest/routes/admin/staff.ts +260 -0
  251. package/src/interfaces/rest/routes/analytics.ts +33 -0
  252. package/src/interfaces/rest/routes/carts.ts +52 -2
  253. package/src/interfaces/rest/routes/catalog.ts +34 -0
  254. package/src/interfaces/rest/routes/checkout.ts +114 -27
  255. package/src/interfaces/rest/routes/documents.ts +61 -0
  256. package/src/interfaces/rest/routes/inventory.ts +14 -0
  257. package/src/interfaces/rest/routes/orders.ts +148 -1
  258. package/src/interfaces/rest/routes/pricing.ts +44 -1
  259. package/src/interfaces/rest/routes/settings.ts +61 -0
  260. package/src/interfaces/rest/routes/shipping.ts +92 -0
  261. package/src/interfaces/rest/routes/tax.ts +90 -0
  262. package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
  263. package/src/interfaces/rest/schemas/analytics.ts +39 -0
  264. package/src/interfaces/rest/schemas/carts.ts +40 -0
  265. package/src/interfaces/rest/schemas/catalog.ts +53 -0
  266. package/src/interfaces/rest/schemas/checkout.ts +4 -0
  267. package/src/interfaces/rest/schemas/documents.ts +72 -0
  268. package/src/interfaces/rest/schemas/orders.ts +258 -1
  269. package/src/interfaces/rest/schemas/pricing.ts +75 -0
  270. package/src/interfaces/rest/schemas/settings.ts +93 -0
  271. package/src/interfaces/rest/schemas/shipping.ts +164 -0
  272. package/src/interfaces/rest/schemas/tax.ts +148 -0
  273. package/src/kernel/database/schema.ts +12 -0
  274. package/src/kernel/database/scoped-db.ts +70 -1
  275. package/src/kernel/hooks/types.ts +1 -0
  276. package/src/kernel/plugin/manifest.ts +41 -2
  277. package/src/modules/analytics/drizzle-adapter.ts +19 -3
  278. package/src/modules/analytics/module.ts +10 -0
  279. package/src/modules/analytics/reports.ts +311 -0
  280. package/src/modules/analytics/service.ts +23 -0
  281. package/src/modules/cart/repository/index.ts +30 -1
  282. package/src/modules/cart/schema.ts +2 -0
  283. package/src/modules/cart/schemas.ts +1 -0
  284. package/src/modules/cart/service.ts +104 -2
  285. package/src/modules/catalog/entity-service.ts +196 -4
  286. package/src/modules/catalog/schema.ts +5 -0
  287. package/src/modules/catalog/schemas.ts +3 -0
  288. package/src/modules/catalog/service.ts +30 -0
  289. package/src/modules/customers/service.ts +6 -0
  290. package/src/modules/documents/module.ts +19 -0
  291. package/src/modules/documents/pdf.ts +101 -0
  292. package/src/modules/documents/render.ts +231 -0
  293. package/src/modules/documents/repository/index.ts +68 -0
  294. package/src/modules/documents/schema.ts +44 -0
  295. package/src/modules/documents/service.ts +176 -0
  296. package/src/modules/fulfillment/service.ts +139 -1
  297. package/src/modules/orders/repository/index.ts +127 -1
  298. package/src/modules/orders/schema.ts +58 -1
  299. package/src/modules/orders/service.ts +1041 -65
  300. package/src/modules/orders/stale-order-cleanup.ts +18 -4
  301. package/src/modules/pricing/repository/index.ts +34 -0
  302. package/src/modules/pricing/service.ts +76 -6
  303. package/src/modules/settings/module.ts +18 -0
  304. package/src/modules/settings/repository/index.ts +59 -0
  305. package/src/modules/settings/schema.ts +25 -0
  306. package/src/modules/settings/service.ts +82 -0
  307. package/src/modules/shipping/calculator.ts +27 -0
  308. package/src/modules/shipping/module.ts +6 -2
  309. package/src/modules/shipping/repository/index.ts +136 -2
  310. package/src/modules/shipping/schema.ts +58 -0
  311. package/src/modules/shipping/service.ts +260 -1
  312. package/src/modules/tax/adapter.ts +9 -0
  313. package/src/modules/tax/module.ts +6 -2
  314. package/src/modules/tax/repository/index.ts +169 -2
  315. package/src/modules/tax/schema.ts +57 -0
  316. package/src/modules/tax/service.ts +313 -2
  317. package/src/runtime/kernel-modules.ts +4 -0
  318. package/src/runtime/kernel-types.ts +6 -0
  319. package/src/runtime/server.ts +1 -1
  320. package/src/test-utils/create-plugin-test-app.ts +17 -4
  321. package/src/test-utils/create-test-config.ts +2 -0
  322. package/src/test-utils/order-test-helpers.ts +26 -0
  323. package/src/test-utils/rest-api-test-utils.ts +7 -0
  324. package/src/test-utils/test-actors.ts +1 -1
  325. package/src/testing.ts +1 -0
@@ -212,7 +212,7 @@ export class EntityService {
212
212
  const afterHooks = this.deps.hooks.resolve("catalog.afterCreate") as CatalogCreateAfterHook[];
213
213
  const context: HookContext = createHookContext({ actor, tx: ctx?.tx ?? null, logger: createLogger("catalog.create"), services: this.deps.services, context: { moduleName: "catalog" }, ...hookDatabaseArg(this.deps.database) });
214
214
  const processedInput = await runBeforeHooks(beforeHooks, input, "create", context);
215
- const entity = await this.repo.createEntity({ organizationId: orgId, type: processedInput.type, slug: processedInput.slug, status: "draft", isVisible: false, metadata: processedInput.metadata ?? {} }, ctx);
215
+ const entity = await this.repo.createEntity({ organizationId: orgId, type: processedInput.type, slug: processedInput.slug, status: "draft", isVisible: false, ...(processedInput.taxClass !== undefined ? { taxClass: processedInput.taxClass } : {}), metadata: processedInput.metadata ?? {} }, ctx);
216
216
  if (processedInput.attributes) {
217
217
  await this.repo.createAttribute({ entityId: entity.id, locale: processedInput.attributes.locale ?? "en", title: processedInput.attributes.title, subtitle: processedInput.attributes.subtitle, description: processedInput.attributes.description, richDescription: processedInput.attributes.richDescription, seoTitle: processedInput.attributes.seoTitle, seoDescription: processedInput.attributes.seoDescription }, ctx);
218
218
  }
@@ -232,7 +232,7 @@ export class EntityService {
232
232
  const afterHooks = this.deps.hooks.resolve("catalog.afterUpdate") as CatalogUpdateAfterHook[];
233
233
  const context: HookContext = createHookContext({ actor, tx: ctx?.tx ?? null, logger: createLogger("catalog.update"), services: this.deps.services, context: { moduleName: "catalog" }, ...hookDatabaseArg(this.deps.database) });
234
234
  const processed = await runBeforeHooks(beforeHooks, input, "update", context);
235
- const updated = await this.repo.updateEntity(id, { ...(processed.slug !== undefined ? { slug: processed.slug } : {}), ...(processed.status !== undefined ? { status: processed.status as SellableEntity["status"] } : {}), ...(processed.metadata !== undefined ? { metadata: processed.metadata } : {}), ...(processed.isVisible !== undefined ? { isVisible: processed.isVisible } : {}) }, ctx);
235
+ const updated = await this.repo.updateEntity(id, { ...(processed.slug !== undefined ? { slug: processed.slug } : {}), ...(processed.status !== undefined ? { status: processed.status as SellableEntity["status"] } : {}), ...(processed.taxClass !== undefined ? { taxClass: processed.taxClass } : {}), ...(processed.metadata !== undefined ? { metadata: processed.metadata } : {}), ...(processed.isVisible !== undefined ? { isVisible: processed.isVisible } : {}) }, ctx);
236
236
  if (!updated) return Err(new CommerceNotFoundError("Entity not found."));
237
237
  const hookReport = await runAfterHooks(afterHooks, existing, updated, "update", context);
238
238
  const hydrated = await this.hydrateEntity(updated, undefined, ctx);
@@ -271,7 +271,12 @@ export class EntityService {
271
271
  entity = refetched;
272
272
  }
273
273
  }
274
- if (actor && entity.organizationId && entity.organizationId !== resolveOrgId(actor)) return Err(new CommerceNotFoundError("Entity not found."));
274
+ const resolvedActor = actor ?? ctx?.actor ?? null;
275
+ try {
276
+ this.assertSameOrg(entity, resolvedActor);
277
+ } catch (error) {
278
+ return Err(toCommerceError(error));
279
+ }
275
280
  const result = await this.hydrateEntity(entity, processed.options ?? options, ctx);
276
281
  const entityAfterHooks = this.deps.hooks.resolve(`catalog.${entity.type}.afterRead`) as CatalogReadAfterHook[];
277
282
  const report = mergeHookReports(await runAfterHooks(globalAfterHooks, null, result, "read", context), await runAfterHooks(entityAfterHooks, null, result, "read", context));
@@ -407,6 +412,11 @@ export class EntityService {
407
412
  assertPermission(actor, "catalog:update");
408
413
  const entity = await this.repo.findEntityById(input.entityId, ctx);
409
414
  if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
415
+ try {
416
+ this.assertSameOrg(entity, actor);
417
+ } catch (error) {
418
+ return Err(toCommerceError(error));
419
+ }
410
420
  const optionType = await this.repo.createOptionType({ entityId: input.entityId, name: input.name, displayName: input.name, sortOrder: 0 }, ctx);
411
421
  if (input.values) {
412
422
  for (const value of input.values) {
@@ -420,6 +430,13 @@ export class EntityService {
420
430
  assertPermission(actor, "catalog:update");
421
431
  const optionType = await this.repo.findOptionTypeById(input.optionTypeId, ctx);
422
432
  if (!optionType) return Err(new CommerceNotFoundError("Option type not found."));
433
+ const entity = await this.repo.findEntityById(optionType.entityId, ctx);
434
+ if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
435
+ try {
436
+ this.assertSameOrg(entity, actor);
437
+ } catch (error) {
438
+ return Err(toCommerceError(error));
439
+ }
423
440
  return Ok(await this.repo.createOptionValue({ optionTypeId: input.optionTypeId, value: input.value, displayValue: input.value, sortOrder: 0, metadata: {} }, ctx));
424
441
  }
425
442
 
@@ -427,6 +444,11 @@ export class EntityService {
427
444
  assertPermission(actor, "catalog:update");
428
445
  const entity = await this.repo.findEntityById(input.entityId, ctx);
429
446
  if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
447
+ try {
448
+ this.assertSameOrg(entity, actor);
449
+ } catch (error) {
450
+ return Err(toCommerceError(error));
451
+ }
430
452
  const entityOptionTypes = await this.repo.findOptionTypesByEntityId(input.entityId, ctx);
431
453
  const optionValueIds: string[] = [];
432
454
  for (const [optName, optVal] of Object.entries(input.options)) {
@@ -437,7 +459,7 @@ export class EntityService {
437
459
  if (!ov) return Err(new CommerceValidationError(`Option value "${optVal}" does not exist for option type "${optName}".`));
438
460
  optionValueIds.push(ov.id);
439
461
  }
440
- const variant = await this.repo.createVariant({ entityId: input.entityId, status: "active", sortOrder: 0, metadata: {}, ...(input.sku !== undefined ? { sku: input.sku } : {}) }, ctx);
462
+ const variant = await this.repo.createVariant({ entityId: input.entityId, status: "active", sortOrder: 0, metadata: {}, ...(input.sku !== undefined ? { sku: input.sku } : {}), ...(input.taxClass !== undefined ? { taxClass: input.taxClass } : {}) }, ctx);
441
463
  await this.repo.createVariantOptionValues(optionValueIds.map((optionValueId) => ({ variantId: variant.id, optionValueId })), ctx);
442
464
  return Ok(variant);
443
465
  }
@@ -461,6 +483,11 @@ export class EntityService {
461
483
  }
462
484
  const entity = await this.repo.findEntityById(entityId, ctx);
463
485
  if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
486
+ try {
487
+ this.assertSameOrg(entity, actor);
488
+ } catch (error) {
489
+ return Err(toCommerceError(error));
490
+ }
464
491
  const entityOptionTypes = await this.repo.findOptionTypesByEntityId(entityId, ctx);
465
492
  const sortedOptionTypes = entityOptionTypes.sort((a, b) => a.sortOrder - b.sortOrder);
466
493
  const optionValueGroups: string[][] = [];
@@ -490,4 +517,169 @@ export class EntityService {
490
517
  }
491
518
  return Ok(created);
492
519
  }
520
+
521
+ // ── One-call variant creation (issue #50) ────────────────────────────────
522
+ // Upserts option axes inline, creates variants, and seeds a zero-stock
523
+ // inventory level per variant so it is sellable immediately. Retry-safe:
524
+ // axes upsert by name/value and existing combinations are skipped.
525
+
526
+ private async upsertAxes(
527
+ entityId: string,
528
+ axes: Array<{ name: string; values: string[] }>,
529
+ ctx?: TxContext,
530
+ ): Promise<Map<string, Map<string, string>>> {
531
+ const existingTypes = await this.repo.findOptionTypesByEntityId(entityId, ctx);
532
+ const result = new Map<string, Map<string, string>>();
533
+ for (const axis of axes) {
534
+ let optionType = existingTypes.find((t) => t.name === axis.name);
535
+ if (!optionType) {
536
+ optionType = await this.repo.createOptionType(
537
+ { entityId, name: axis.name, displayName: axis.name, sortOrder: existingTypes.length },
538
+ ctx,
539
+ );
540
+ existingTypes.push(optionType);
541
+ }
542
+ const existingValues = await this.repo.findOptionValuesByTypeId(optionType.id, ctx);
543
+ const valueIds = new Map<string, string>();
544
+ for (const value of axis.values) {
545
+ let optionValue = existingValues.find((v) => v.value === value);
546
+ if (!optionValue) {
547
+ optionValue = await this.repo.createOptionValue(
548
+ { optionTypeId: optionType.id, value, displayValue: value, sortOrder: existingValues.length, metadata: {} },
549
+ ctx,
550
+ );
551
+ existingValues.push(optionValue);
552
+ }
553
+ valueIds.set(value, optionValue.id);
554
+ }
555
+ result.set(axis.name, valueIds);
556
+ }
557
+ return result;
558
+ }
559
+
560
+ private async existingComboKeys(entityId: string, ctx?: TxContext): Promise<Map<string, string>> {
561
+ const existing = new Map<string, string>();
562
+ for (const variant of await this.repo.findVariantsByEntityId(entityId, ctx)) {
563
+ const rows = await this.repo.findVariantOptionValues(variant.id, ctx);
564
+ const key = rows.map((r) => r.optionValueId).sort().join("|");
565
+ existing.set(key, variant.id);
566
+ }
567
+ return existing;
568
+ }
569
+
570
+ private async seedInventoryLevel(
571
+ entityId: string,
572
+ variantId: string,
573
+ actor: Actor | null,
574
+ ctx?: TxContext,
575
+ ): Promise<Result<unknown>> {
576
+ const inventory = this.deps.services.inventory as {
577
+ adjustDetailed: (
578
+ input: { entityId: string; variantId?: string; adjustment: number; reason: string },
579
+ actor?: Actor | null,
580
+ ctx?: TxContext,
581
+ ) => Promise<Result<unknown>>;
582
+ };
583
+ return inventory.adjustDetailed(
584
+ { entityId, variantId, adjustment: 0, reason: "Variant created — seed zero-stock level" },
585
+ actor,
586
+ ctx,
587
+ );
588
+ }
589
+
590
+ async quickCreateVariant(
591
+ entityId: string,
592
+ input: { options: Record<string, string>; sku?: string | undefined; barcode?: string | undefined },
593
+ actor: Actor | null,
594
+ ctx?: TxContext,
595
+ ): Promise<Result<{ variant: Variant; created: boolean }>> {
596
+ try { assertPermission(actor, "catalog:update"); } catch (error) { return Err(toCommerceError(error)); }
597
+ const entity = await this.repo.findEntityById(entityId, ctx);
598
+ if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
599
+ try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
600
+
601
+ const axes = Object.entries(input.options).map(([name, value]) => ({ name, values: [value] }));
602
+ const axisValueIds = await this.upsertAxes(entityId, axes, ctx);
603
+ const optionValueIds = Object.entries(input.options)
604
+ .map(([name, value]) => axisValueIds.get(name)!.get(value)!);
605
+
606
+ const key = [...optionValueIds].sort().join("|");
607
+ const existing = await this.existingComboKeys(entityId, ctx);
608
+ const existingVariantId = existing.get(key);
609
+ if (existingVariantId !== undefined) {
610
+ const variant = await this.repo.findVariantById(existingVariantId, ctx);
611
+ return Ok({ variant: variant!, created: false });
612
+ }
613
+
614
+ const variant = await this.repo.createVariant(
615
+ {
616
+ entityId,
617
+ status: "active",
618
+ sortOrder: 0,
619
+ metadata: { generatedBy: "quick" },
620
+ ...(input.sku !== undefined ? { sku: input.sku } : {}),
621
+ ...(input.barcode !== undefined ? { barcode: input.barcode } : {}),
622
+ },
623
+ ctx,
624
+ );
625
+ await this.repo.createVariantOptionValues(
626
+ optionValueIds.map((optionValueId) => ({ variantId: variant.id, optionValueId })),
627
+ ctx,
628
+ );
629
+ const seeded = await this.seedInventoryLevel(entityId, variant.id, actor, ctx);
630
+ if (!seeded.ok) return seeded;
631
+ return Ok({ variant, created: true });
632
+ }
633
+
634
+ async bulkCreateVariants(
635
+ entityId: string,
636
+ input: { axes: Array<{ name: string; values: string[] }>; skuPrefix?: string | undefined },
637
+ actor: Actor | null,
638
+ ctx?: TxContext,
639
+ ): Promise<Result<{ created: Variant[]; skipped: number }>> {
640
+ try { assertPermission(actor, "catalog:update"); } catch (error) { return Err(toCommerceError(error)); }
641
+ const entity = await this.repo.findEntityById(entityId, ctx);
642
+ if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
643
+ try { this.assertSameOrg(entity, actor); } catch (error) { return Err(toCommerceError(error)); }
644
+
645
+ const axisValueIds = await this.upsertAxes(entityId, input.axes, ctx);
646
+ // Empty axis list → one option-less variant.
647
+ const groups = input.axes.map((axis) =>
648
+ axis.values.map((value) => ({ value, id: axisValueIds.get(axis.name)!.get(value)! })),
649
+ );
650
+ const combinations = cartesian(groups);
651
+
652
+ const existing = await this.existingComboKeys(entityId, ctx);
653
+ const created: Variant[] = [];
654
+ let skipped = 0;
655
+ for (const combo of combinations) {
656
+ const key = combo.map((c) => c.id).sort().join("|");
657
+ if (existing.has(key)) {
658
+ skipped += 1;
659
+ continue;
660
+ }
661
+ const sku = input.skuPrefix !== undefined
662
+ ? [input.skuPrefix, ...combo.map((c) => c.value)].join("-").toUpperCase().replace(/\s+/g, "-")
663
+ : undefined;
664
+ const variant = await this.repo.createVariant(
665
+ {
666
+ entityId,
667
+ status: "active",
668
+ sortOrder: 0,
669
+ metadata: { generatedBy: "bulk" },
670
+ ...(sku !== undefined ? { sku } : {}),
671
+ },
672
+ ctx,
673
+ );
674
+ await this.repo.createVariantOptionValues(
675
+ combo.map((c) => ({ variantId: variant.id, optionValueId: c.id })),
676
+ ctx,
677
+ );
678
+ const seeded = await this.seedInventoryLevel(entityId, variant.id, actor, ctx);
679
+ if (!seeded.ok) return seeded;
680
+ existing.set(key, variant.id);
681
+ created.push(variant);
682
+ }
683
+ return Ok({ created, skipped });
684
+ }
493
685
  }
@@ -25,6 +25,9 @@ export const sellableEntities = pgTable(
25
25
  .notNull()
26
26
  .default("draft"),
27
27
  isVisible: boolean("is_visible").notNull().default(false),
28
+ // Tax class name (issue #57) — maps to tax_classes.name at checkout;
29
+ // a variant-level taxClass overrides this. Null = the org's default class.
30
+ taxClass: text("tax_class"),
28
31
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
29
32
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
30
33
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
@@ -183,6 +186,8 @@ export const variants = pgTable("variants", {
183
186
  .notNull(),
184
187
  sku: text("sku").unique(),
185
188
  barcode: text("barcode"),
189
+ // Overrides the entity's taxClass when set (issue #57).
190
+ taxClass: text("tax_class"),
186
191
  status: text("status", { enum: ["active", "discontinued"] })
187
192
  .notNull()
188
193
  .default("active"),
@@ -6,6 +6,7 @@ export const CreateEntityBodySchema = z.object({
6
6
  type: z.string().openapi({ example: "physicalGood" }),
7
7
  slug: z.string().openapi({ example: "my-product" }),
8
8
  status: z.string().optional().openapi({ example: "draft" }),
9
+ taxClass: z.string().optional().openapi({ example: "standard", description: "Product tax class name (issue #57)." }),
9
10
  basePrice: z.number().optional().openapi({ example: 29.99 }),
10
11
  currency: z.string().optional().openapi({ example: "USD" }),
11
12
  metadata: z.record(z.string(), z.unknown()).optional().openapi({ example: { title: "My Product" } }),
@@ -24,6 +25,7 @@ export const CreateEntityBodySchema = z.object({
24
25
  export const UpdateEntityBodySchema = z.object({
25
26
  slug: z.string().optional(),
26
27
  status: z.string().optional(),
28
+ taxClass: z.string().nullable().optional(),
27
29
  metadata: z.record(z.string(), z.unknown()).optional(),
28
30
  isVisible: z.boolean().optional(),
29
31
  }).openapi("UpdateEntityBody");
@@ -69,6 +71,7 @@ export const CreateVariantBodySchema = z.object({
69
71
  sku: z.string().optional().openapi({ example: "SKU-001" }),
70
72
  options: z.record(z.string(), z.string()).openapi({ example: { Color: "Red" } }),
71
73
  price: z.number().optional().openapi({ example: 34.99 }),
74
+ taxClass: z.string().optional().openapi({ example: "zero", description: "Overrides the entity's tax class (issue #57)." }),
72
75
  }).openapi("CreateVariantBody");
73
76
 
74
77
  const VariantMatrixRuleSchema = z.object({
@@ -259,6 +259,18 @@ export interface CatalogService {
259
259
  actor: Actor | null,
260
260
  ctx?: TxContext,
261
261
  ): Promise<Result<Variant[]>>;
262
+ quickCreateVariant(
263
+ entityId: string,
264
+ input: { options: Record<string, string>; sku?: string | undefined; barcode?: string | undefined },
265
+ actor: Actor | null,
266
+ ctx?: TxContext,
267
+ ): Promise<Result<{ variant: Variant; created: boolean }>>;
268
+ bulkCreateVariants(
269
+ entityId: string,
270
+ input: { axes: Array<{ name: string; values: string[] }>; skuPrefix?: string | undefined },
271
+ actor: Actor | null,
272
+ ctx?: TxContext,
273
+ ): Promise<Result<{ created: Variant[]; skipped: number }>>;
262
274
  }
263
275
 
264
276
  export interface CatalogServiceDeps {
@@ -411,4 +423,22 @@ export class CatalogServiceImpl implements CatalogService {
411
423
  generateVariants(entityId: string, strategy: VariantGenerationStrategy, actor: Actor | null, ctx?: TxContext): Promise<Result<Variant[]>> {
412
424
  return this.entities.generateVariants(entityId, strategy, actor, ctx);
413
425
  }
426
+
427
+ quickCreateVariant(
428
+ entityId: string,
429
+ input: { options: Record<string, string>; sku?: string | undefined; barcode?: string | undefined },
430
+ actor: Actor | null,
431
+ ctx?: TxContext,
432
+ ): Promise<Result<{ variant: Variant; created: boolean }>> {
433
+ return this.entities.quickCreateVariant(entityId, input, actor, ctx);
434
+ }
435
+
436
+ bulkCreateVariants(
437
+ entityId: string,
438
+ input: { axes: Array<{ name: string; values: string[] }>; skuPrefix?: string | undefined },
439
+ actor: Actor | null,
440
+ ctx?: TxContext,
441
+ ): Promise<Result<{ created: Variant[]; skipped: number }>> {
442
+ return this.entities.bulkCreateVariants(entityId, input, actor, ctx);
443
+ }
414
444
  }
@@ -241,6 +241,12 @@ export class CustomerService {
241
241
  ctx?: TxContext,
242
242
  options?: { replaceMetadata?: boolean },
243
243
  ): Promise<Result<Customer>> {
244
+ try {
245
+ assertPermission(actor ?? null, "customers:update");
246
+ } catch (error) {
247
+ return Err(toCommerceError(error));
248
+ }
249
+
244
250
  const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
245
251
  const existing = await this.repo.findById(orgId, id, ctx);
246
252
  if (!existing) return Err(new CommerceNotFoundError("Customer not found."));
@@ -0,0 +1,19 @@
1
+ import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
2
+ import { defineModule } from "../../kernel/module/index.js";
3
+ import { DocumentsRepository } from "./repository/index.js";
4
+ import { invoiceSequences, orderDocuments } from "./schema.js";
5
+ import { DocumentsService } from "./service.js";
6
+
7
+ export const documentsModule = defineModule<
8
+ { invoiceSequences: typeof invoiceSequences; orderDocuments: typeof orderDocuments },
9
+ DocumentsService,
10
+ Record<string, never>
11
+ >({
12
+ id: "documents",
13
+ schema: () => ({ invoiceSequences, orderDocuments }),
14
+ service: (deps) =>
15
+ new DocumentsService({
16
+ repository: new DocumentsRepository(deps.db.db as DrizzleDatabase),
17
+ services: deps.services as Record<string, unknown>,
18
+ }),
19
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Minimal dependency-free PDF writer (issue #47).
3
+ *
4
+ * Emits PDF 1.4 with Helvetica / Helvetica-Bold (built-in base-14 fonts, no
5
+ * embedding) and uncompressed content streams. Pure string/Uint8Array work —
6
+ * no Node-only APIs, so it runs on Cloudflare Workers and other serverless
7
+ * runtimes. Enough for text documents like invoices; not a general renderer.
8
+ */
9
+
10
+ export interface PdfTextOp {
11
+ x: number;
12
+ y: number;
13
+ size: number;
14
+ bold?: boolean | undefined;
15
+ text: string;
16
+ }
17
+
18
+ /** A4 in PDF points. */
19
+ export const A4 = { width: 595.28, height: 841.89 } as const;
20
+
21
+ // Escape to a PDF literal string: backslash, parens, and non-ASCII bytes as
22
+ // octal escapes (Latin-1 / WinAnsi range; anything beyond becomes "?").
23
+ function escapePdfText(text: string): string {
24
+ let out = "";
25
+ for (const ch of text) {
26
+ const code = ch.codePointAt(0)!;
27
+ if (ch === "\\" || ch === "(" || ch === ")") out += `\\${ch}`;
28
+ else if (code >= 32 && code <= 126) out += ch;
29
+ else if (code > 126 && code <= 255) out += `\\${code.toString(8).padStart(3, "0")}`;
30
+ else out += "?";
31
+ }
32
+ return out;
33
+ }
34
+
35
+ function contentStream(ops: PdfTextOp[]): string {
36
+ const parts: string[] = [];
37
+ for (const op of ops) {
38
+ const font = op.bold ? "/F2" : "/F1";
39
+ parts.push(
40
+ `BT ${font} ${op.size} Tf ${op.x.toFixed(2)} ${op.y.toFixed(2)} Td (${escapePdfText(op.text)}) Tj ET`,
41
+ );
42
+ }
43
+ return parts.join("\n");
44
+ }
45
+
46
+ /**
47
+ * Builds a complete PDF document; one inner array of text ops per page.
48
+ */
49
+ export function buildPdf(pages: PdfTextOp[][]): Uint8Array {
50
+ // Object numbering: 1 Catalog, 2 Pages, 3 F1, 4 F2, then per page: Page, Contents.
51
+ const objects: string[] = [];
52
+ const pageObjectNumbers: number[] = [];
53
+ const firstPageObj = 5;
54
+
55
+ for (let i = 0; i < pages.length; i++) {
56
+ pageObjectNumbers.push(firstPageObj + i * 2);
57
+ }
58
+
59
+ objects.push(`1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n`);
60
+ objects.push(
61
+ `2 0 obj\n<< /Type /Pages /Kids [${pageObjectNumbers.map((n) => `${n} 0 R`).join(" ")}] /Count ${pages.length} >>\nendobj\n`,
62
+ );
63
+ objects.push(`3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n`);
64
+ objects.push(`4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\nendobj\n`);
65
+
66
+ for (let i = 0; i < pages.length; i++) {
67
+ const pageNum = pageObjectNumbers[i]!;
68
+ const contentNum = pageNum + 1;
69
+ const stream = contentStream(pages[i]!);
70
+ objects.push(
71
+ `${pageNum} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${A4.width} ${A4.height}] ` +
72
+ `/Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents ${contentNum} 0 R >>\nendobj\n`,
73
+ );
74
+ objects.push(
75
+ `${contentNum} 0 obj\n<< /Length ${stream.length} >>\nstream\n${stream}\nendstream\nendobj\n`,
76
+ );
77
+ }
78
+
79
+ const header = "%PDF-1.4\n";
80
+ let body = header;
81
+ const offsets: number[] = [];
82
+ for (const obj of objects) {
83
+ offsets.push(body.length);
84
+ body += obj;
85
+ }
86
+
87
+ const xrefOffset = body.length;
88
+ const count = objects.length + 1;
89
+ let xref = `xref\n0 ${count}\n0000000000 65535 f \n`;
90
+ for (const offset of offsets) {
91
+ xref += `${offset.toString().padStart(10, "0")} 00000 n \n`;
92
+ }
93
+ const trailer = `trailer\n<< /Size ${count} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
94
+
95
+ const full = body + xref + trailer;
96
+ // All bytes are ASCII/Latin-1 by construction (escapePdfText), so a
97
+ // byte-per-char encoding is exact.
98
+ const bytes = new Uint8Array(full.length);
99
+ for (let i = 0; i < full.length; i++) bytes[i] = full.charCodeAt(i) & 0xff;
100
+ return bytes;
101
+ }