@unifiedcommerce/core 0.4.4 → 0.5.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.
- package/dist/adapters/console-email.d.ts +23 -0
- package/dist/adapters/console-email.d.ts.map +1 -0
- package/dist/adapters/console-email.js +38 -0
- package/dist/auth/access.d.ts +101 -0
- package/dist/auth/access.d.ts.map +1 -0
- package/dist/auth/access.js +128 -0
- package/dist/auth/auth-schema.d.ts +1475 -0
- package/dist/auth/auth-schema.d.ts.map +1 -0
- package/dist/auth/auth-schema.js +124 -0
- package/dist/auth/middleware.d.ts +5 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +141 -0
- package/dist/auth/org.d.ts +22 -0
- package/dist/auth/org.d.ts.map +1 -0
- package/dist/auth/org.js +36 -0
- package/dist/auth/permissions.d.ts +4 -0
- package/dist/auth/permissions.d.ts.map +1 -0
- package/dist/auth/permissions.js +24 -0
- package/dist/auth/setup.d.ts +29 -0
- package/dist/auth/setup.d.ts.map +1 -0
- package/dist/auth/setup.js +144 -0
- package/dist/auth/system-actor.d.ts +7 -0
- package/dist/auth/system-actor.d.ts.map +1 -0
- package/dist/auth/system-actor.js +17 -0
- package/dist/auth/types.d.ts +11 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +80 -0
- package/dist/config/define-config.d.ts +9 -0
- package/dist/config/define-config.d.ts.map +1 -0
- package/dist/config/define-config.js +44 -0
- package/dist/config/types.d.ts +327 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +1 -0
- package/dist/generated/plugin-manifest.d.ts +48 -0
- package/dist/generated/plugin-manifest.d.ts.map +1 -0
- package/dist/generated/plugin-manifest.js +20 -0
- package/dist/hooks/checkout-completion.d.ts +58 -0
- package/dist/hooks/checkout-completion.d.ts.map +1 -0
- package/dist/hooks/checkout-completion.js +137 -0
- package/dist/hooks/checkout.d.ts +99 -0
- package/dist/hooks/checkout.d.ts.map +1 -0
- package/dist/hooks/checkout.js +317 -0
- package/dist/hooks/order-emails.d.ts +16 -0
- package/dist/hooks/order-emails.d.ts.map +1 -0
- package/dist/hooks/order-emails.js +44 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/interfaces/mcp/agent-prompt.d.ts +16 -0
- package/dist/interfaces/mcp/agent-prompt.d.ts.map +1 -0
- package/dist/interfaces/mcp/agent-prompt.js +172 -0
- package/dist/interfaces/mcp/context-enrichment.d.ts +39 -0
- package/dist/interfaces/mcp/context-enrichment.d.ts.map +1 -0
- package/dist/interfaces/mcp/context-enrichment.js +119 -0
- package/dist/interfaces/mcp/server.d.ts +5 -0
- package/dist/interfaces/mcp/server.d.ts.map +1 -0
- package/dist/interfaces/mcp/server.js +30 -0
- package/dist/interfaces/mcp/tool-builder.d.ts +120 -0
- package/dist/interfaces/mcp/tool-builder.d.ts.map +1 -0
- package/dist/interfaces/mcp/tool-builder.js +224 -0
- package/dist/interfaces/mcp/tools/analytics.d.ts +42 -0
- package/dist/interfaces/mcp/tools/analytics.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/analytics.js +70 -0
- package/dist/interfaces/mcp/tools/cart.d.ts +14 -0
- package/dist/interfaces/mcp/tools/cart.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/cart.js +47 -0
- package/dist/interfaces/mcp/tools/catalog.d.ts +53 -0
- package/dist/interfaces/mcp/tools/catalog.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/catalog.js +284 -0
- package/dist/interfaces/mcp/tools/index.d.ts +3 -0
- package/dist/interfaces/mcp/tools/index.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/index.js +20 -0
- package/dist/interfaces/mcp/tools/inventory.d.ts +27 -0
- package/dist/interfaces/mcp/tools/inventory.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/inventory.js +143 -0
- package/dist/interfaces/mcp/tools/orders.d.ts +18 -0
- package/dist/interfaces/mcp/tools/orders.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/orders.js +82 -0
- package/dist/interfaces/mcp/tools/pricing.d.ts +29 -0
- package/dist/interfaces/mcp/tools/pricing.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/pricing.js +90 -0
- package/dist/interfaces/mcp/tools/promotions.d.ts +44 -0
- package/dist/interfaces/mcp/tools/promotions.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/promotions.js +109 -0
- package/dist/interfaces/mcp/tools/registry.d.ts +32 -0
- package/dist/interfaces/mcp/tools/registry.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/registry.js +55 -0
- package/dist/interfaces/mcp/tools/search.d.ts +14 -0
- package/dist/interfaces/mcp/tools/search.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/search.js +39 -0
- package/dist/interfaces/mcp/tools/webhooks.d.ts +15 -0
- package/dist/interfaces/mcp/tools/webhooks.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/webhooks.js +48 -0
- package/dist/interfaces/mcp/transport.d.ts +20 -0
- package/dist/interfaces/mcp/transport.d.ts.map +1 -0
- package/dist/interfaces/mcp/transport.js +99 -0
- package/dist/interfaces/rest/customer-portal.d.ts +5 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -0
- package/dist/interfaces/rest/customer-portal.js +206 -0
- package/dist/interfaces/rest/router.d.ts +164 -0
- package/dist/interfaces/rest/router.d.ts.map +1 -0
- package/dist/interfaces/rest/router.js +259 -0
- package/dist/interfaces/rest/routes/admin-jobs.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin-jobs.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin-jobs.js +48 -0
- package/dist/interfaces/rest/routes/audit.d.ts +5 -0
- package/dist/interfaces/rest/routes/audit.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/audit.js +43 -0
- package/dist/interfaces/rest/routes/carts.d.ts +5 -0
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/carts.js +55 -0
- package/dist/interfaces/rest/routes/catalog.d.ts +5 -0
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/catalog.js +256 -0
- package/dist/interfaces/rest/routes/checkout.d.ts +5 -0
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/checkout.js +216 -0
- package/dist/interfaces/rest/routes/customers.d.ts +5 -0
- package/dist/interfaces/rest/routes/customers.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/customers.js +78 -0
- package/dist/interfaces/rest/routes/entity-aliases.d.ts +18 -0
- package/dist/interfaces/rest/routes/entity-aliases.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/entity-aliases.js +39 -0
- package/dist/interfaces/rest/routes/inventory.d.ts +5 -0
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/inventory.js +74 -0
- package/dist/interfaces/rest/routes/media.d.ts +5 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/media.js +65 -0
- package/dist/interfaces/rest/routes/orders.d.ts +5 -0
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/orders.js +64 -0
- package/dist/interfaces/rest/routes/payments.d.ts +5 -0
- package/dist/interfaces/rest/routes/payments.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/payments.js +45 -0
- package/dist/interfaces/rest/routes/pricing.d.ts +5 -0
- package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/pricing.js +48 -0
- package/dist/interfaces/rest/routes/promotions.d.ts +5 -0
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/promotions.js +76 -0
- package/dist/interfaces/rest/routes/search.d.ts +5 -0
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/search.js +60 -0
- package/dist/interfaces/rest/routes/webhooks.d.ts +5 -0
- package/dist/interfaces/rest/routes/webhooks.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/webhooks.js +39 -0
- package/dist/interfaces/rest/schemas/admin-jobs.d.ts +327 -0
- package/dist/interfaces/rest/schemas/admin-jobs.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-jobs.js +37 -0
- package/dist/interfaces/rest/schemas/audit.d.ts +59 -0
- package/dist/interfaces/rest/schemas/audit.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/audit.js +43 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +1456 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/carts.js +109 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts +5452 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/catalog.js +397 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts +160 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/checkout.js +60 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +2203 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/customer-portal.js +177 -0
- package/dist/interfaces/rest/schemas/customers.d.ts +422 -0
- package/dist/interfaces/rest/schemas/customers.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/customers.js +150 -0
- package/dist/interfaces/rest/schemas/inventory.d.ts +561 -0
- package/dist/interfaces/rest/schemas/inventory.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/inventory.js +148 -0
- package/dist/interfaces/rest/schemas/media.d.ts +303 -0
- package/dist/interfaces/rest/schemas/media.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/media.js +69 -0
- package/dist/interfaces/rest/schemas/orders.d.ts +1792 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/orders.js +93 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts +256 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/pricing.js +72 -0
- package/dist/interfaces/rest/schemas/promotions.d.ts +374 -0
- package/dist/interfaces/rest/schemas/promotions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/promotions.js +104 -0
- package/dist/interfaces/rest/schemas/responses.d.ts +4086 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/responses.js +74 -0
- package/dist/interfaces/rest/schemas/search.d.ts +247 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/search.js +55 -0
- package/dist/interfaces/rest/schemas/shared.d.ts +95 -0
- package/dist/interfaces/rest/schemas/shared.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/shared.js +51 -0
- package/dist/interfaces/rest/schemas/webhooks.d.ts +221 -0
- package/dist/interfaces/rest/schemas/webhooks.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/webhooks.js +62 -0
- package/dist/interfaces/rest/utils.d.ts +45 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -0
- package/dist/interfaces/rest/utils.js +71 -0
- package/dist/interfaces/rest/webhook-router.d.ts +41 -0
- package/dist/interfaces/rest/webhook-router.d.ts.map +1 -0
- package/dist/interfaces/rest/webhook-router.js +36 -0
- package/dist/kernel/compensation/executor.d.ts +21 -0
- package/dist/kernel/compensation/executor.d.ts.map +1 -0
- package/dist/kernel/compensation/executor.js +36 -0
- package/dist/kernel/compensation/types.d.ts +25 -0
- package/dist/kernel/compensation/types.d.ts.map +1 -0
- package/dist/kernel/compensation/types.js +1 -0
- package/dist/kernel/database/adapter.d.ts +18 -0
- package/dist/kernel/database/adapter.d.ts.map +1 -0
- package/dist/kernel/database/adapter.js +3 -0
- package/dist/kernel/database/drizzle-db.d.ts +49 -0
- package/dist/kernel/database/drizzle-db.d.ts.map +1 -0
- package/dist/kernel/database/drizzle-db.js +20 -0
- package/dist/kernel/database/migrate.d.ts +38 -0
- package/dist/kernel/database/migrate.d.ts.map +1 -0
- package/dist/kernel/database/migrate.js +61 -0
- package/dist/kernel/database/plugin-types.d.ts +32 -0
- package/dist/kernel/database/plugin-types.d.ts.map +1 -0
- package/dist/kernel/database/plugin-types.js +10 -0
- package/dist/kernel/database/schema.d.ts +24 -0
- package/dist/kernel/database/schema.d.ts.map +1 -0
- package/dist/kernel/database/schema.js +36 -0
- package/dist/kernel/database/scoped-db.d.ts +20 -0
- package/dist/kernel/database/scoped-db.d.ts.map +1 -0
- package/dist/kernel/database/scoped-db.js +62 -0
- package/dist/kernel/database/tx-context.d.ts +15 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -0
- package/dist/kernel/database/tx-context.js +19 -0
- package/dist/kernel/error-mapper.d.ts +3 -0
- package/dist/kernel/error-mapper.d.ts.map +1 -0
- package/dist/kernel/error-mapper.js +12 -0
- package/dist/kernel/errors.d.ts +38 -0
- package/dist/kernel/errors.d.ts.map +1 -0
- package/dist/kernel/errors.js +69 -0
- package/dist/kernel/factory/repository-factory.d.ts +71 -0
- package/dist/kernel/factory/repository-factory.d.ts.map +1 -0
- package/dist/kernel/factory/repository-factory.js +138 -0
- package/dist/kernel/hooks/create-context.d.ts +25 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -0
- package/dist/kernel/hooks/create-context.js +22 -0
- package/dist/kernel/hooks/executor.d.ts +12 -0
- package/dist/kernel/hooks/executor.d.ts.map +1 -0
- package/dist/kernel/hooks/executor.js +50 -0
- package/dist/kernel/hooks/registry.d.ts +28 -0
- package/dist/kernel/hooks/registry.d.ts.map +1 -0
- package/dist/kernel/hooks/registry.js +58 -0
- package/dist/kernel/hooks/types.d.ts +37 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -0
- package/dist/kernel/hooks/types.js +1 -0
- package/dist/kernel/http-error.d.ts +30 -0
- package/dist/kernel/http-error.d.ts.map +1 -0
- package/dist/kernel/http-error.js +35 -0
- package/dist/kernel/jobs/adapter.d.ts +25 -0
- package/dist/kernel/jobs/adapter.d.ts.map +1 -0
- package/dist/kernel/jobs/adapter.js +9 -0
- package/dist/kernel/jobs/drizzle-adapter.d.ts +15 -0
- package/dist/kernel/jobs/drizzle-adapter.d.ts.map +1 -0
- package/dist/kernel/jobs/drizzle-adapter.js +42 -0
- package/dist/kernel/jobs/runner.d.ts +24 -0
- package/dist/kernel/jobs/runner.d.ts.map +1 -0
- package/dist/kernel/jobs/runner.js +114 -0
- package/dist/kernel/jobs/schema.d.ts +280 -0
- package/dist/kernel/jobs/schema.d.ts.map +1 -0
- package/dist/kernel/jobs/schema.js +37 -0
- package/dist/kernel/jobs/types.d.ts +30 -0
- package/dist/kernel/jobs/types.d.ts.map +1 -0
- package/dist/kernel/jobs/types.js +1 -0
- package/dist/kernel/local-api.d.ts +103 -0
- package/dist/kernel/local-api.d.ts.map +1 -0
- package/dist/kernel/local-api.js +89 -0
- package/dist/kernel/plugin/manifest.d.ts +90 -0
- package/dist/kernel/plugin/manifest.d.ts.map +1 -0
- package/dist/kernel/plugin/manifest.js +169 -0
- package/dist/kernel/query/executor.d.ts +21 -0
- package/dist/kernel/query/executor.d.ts.map +1 -0
- package/dist/kernel/query/executor.js +128 -0
- package/dist/kernel/query/registry.d.ts +33 -0
- package/dist/kernel/query/registry.d.ts.map +1 -0
- package/dist/kernel/query/registry.js +20 -0
- package/dist/kernel/result.d.ts +36 -0
- package/dist/kernel/result.d.ts.map +1 -0
- package/dist/kernel/result.js +16 -0
- package/dist/kernel/schema/extra-columns.d.ts +23 -0
- package/dist/kernel/schema/extra-columns.d.ts.map +1 -0
- package/dist/kernel/schema/extra-columns.js +10 -0
- package/dist/kernel/service-registry.d.ts +109 -0
- package/dist/kernel/service-registry.d.ts.map +1 -0
- package/dist/kernel/service-registry.js +26 -0
- package/dist/kernel/service-timing.d.ts +25 -0
- package/dist/kernel/service-timing.d.ts.map +1 -0
- package/dist/kernel/service-timing.js +62 -0
- package/dist/kernel/state-machine/machine.d.ts +24 -0
- package/dist/kernel/state-machine/machine.d.ts.map +1 -0
- package/dist/kernel/state-machine/machine.js +70 -0
- package/dist/modules/analytics/drizzle-adapter.d.ts +13 -0
- package/dist/modules/analytics/drizzle-adapter.d.ts.map +1 -0
- package/dist/modules/analytics/drizzle-adapter.js +358 -0
- package/dist/modules/analytics/hooks.d.ts +13 -0
- package/dist/modules/analytics/hooks.d.ts.map +1 -0
- package/dist/modules/analytics/hooks.js +12 -0
- package/dist/modules/analytics/models.d.ts +14 -0
- package/dist/modules/analytics/models.d.ts.map +1 -0
- package/dist/modules/analytics/models.js +118 -0
- package/dist/modules/analytics/repository/index.d.ts +5 -0
- package/dist/modules/analytics/repository/index.d.ts.map +1 -0
- package/dist/modules/analytics/repository/index.js +1 -0
- package/dist/modules/analytics/service.d.ts +45 -0
- package/dist/modules/analytics/service.d.ts.map +1 -0
- package/dist/modules/analytics/service.js +196 -0
- package/dist/modules/analytics/types.d.ts +119 -0
- package/dist/modules/analytics/types.d.ts.map +1 -0
- package/dist/modules/analytics/types.js +25 -0
- package/dist/modules/audit/hooks.d.ts +7 -0
- package/dist/modules/audit/hooks.d.ts.map +1 -0
- package/dist/modules/audit/hooks.js +67 -0
- package/dist/modules/audit/schema.d.ts +178 -0
- package/dist/modules/audit/schema.d.ts.map +1 -0
- package/dist/modules/audit/schema.js +21 -0
- package/dist/modules/audit/service.d.ts +38 -0
- package/dist/modules/audit/service.d.ts.map +1 -0
- package/dist/modules/audit/service.js +109 -0
- package/dist/modules/cart/access.d.ts +11 -0
- package/dist/modules/cart/access.d.ts.map +1 -0
- package/dist/modules/cart/access.js +18 -0
- package/dist/modules/cart/matcher.d.ts +20 -0
- package/dist/modules/cart/matcher.d.ts.map +1 -0
- package/dist/modules/cart/matcher.js +2 -0
- package/dist/modules/cart/repository/index.d.ts +45 -0
- package/dist/modules/cart/repository/index.d.ts.map +1 -0
- package/dist/modules/cart/repository/index.js +158 -0
- package/dist/modules/cart/schema.d.ts +359 -0
- package/dist/modules/cart/schema.d.ts.map +1 -0
- package/dist/modules/cart/schema.js +40 -0
- package/dist/modules/cart/schemas.d.ts +29 -0
- package/dist/modules/cart/schemas.d.ts.map +1 -0
- package/dist/modules/cart/schemas.js +14 -0
- package/dist/modules/cart/service.d.ts +63 -0
- package/dist/modules/cart/service.d.ts.map +1 -0
- package/dist/modules/cart/service.js +339 -0
- package/dist/modules/catalog/repository/index.d.ts +106 -0
- package/dist/modules/catalog/repository/index.d.ts.map +1 -0
- package/dist/modules/catalog/repository/index.js +455 -0
- package/dist/modules/catalog/schema.d.ts +1193 -0
- package/dist/modules/catalog/schema.d.ts.map +1 -0
- package/dist/modules/catalog/schema.js +149 -0
- package/dist/modules/catalog/schemas.d.ts +81 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -0
- package/dist/modules/catalog/schemas.js +62 -0
- package/dist/modules/catalog/service.d.ts +165 -0
- package/dist/modules/catalog/service.d.ts.map +1 -0
- package/dist/modules/catalog/service.js +775 -0
- package/dist/modules/customers/repository/index.d.ts +47 -0
- package/dist/modules/customers/repository/index.d.ts.map +1 -0
- package/dist/modules/customers/repository/index.js +206 -0
- package/dist/modules/customers/schema.d.ts +560 -0
- package/dist/modules/customers/schema.d.ts.map +1 -0
- package/dist/modules/customers/schema.js +60 -0
- package/dist/modules/customers/service.d.ts +27 -0
- package/dist/modules/customers/service.d.ts.map +1 -0
- package/dist/modules/customers/service.js +106 -0
- package/dist/modules/fulfillment/repository/index.d.ts +63 -0
- package/dist/modules/fulfillment/repository/index.d.ts.map +1 -0
- package/dist/modules/fulfillment/repository/index.js +268 -0
- package/dist/modules/fulfillment/schema.d.ts +655 -0
- package/dist/modules/fulfillment/schema.d.ts.map +1 -0
- package/dist/modules/fulfillment/schema.js +83 -0
- package/dist/modules/fulfillment/service.d.ts +58 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -0
- package/dist/modules/fulfillment/service.js +338 -0
- package/dist/modules/fulfillment/types.d.ts +44 -0
- package/dist/modules/fulfillment/types.d.ts.map +1 -0
- package/dist/modules/fulfillment/types.js +1 -0
- package/dist/modules/inventory/repository/index.d.ts +81 -0
- package/dist/modules/inventory/repository/index.d.ts.map +1 -0
- package/dist/modules/inventory/repository/index.js +310 -0
- package/dist/modules/inventory/schema.d.ts +570 -0
- package/dist/modules/inventory/schema.d.ts.map +1 -0
- package/dist/modules/inventory/schema.js +69 -0
- package/dist/modules/inventory/schemas.d.ts +31 -0
- package/dist/modules/inventory/schemas.d.ts.map +1 -0
- package/dist/modules/inventory/schemas.js +28 -0
- package/dist/modules/inventory/service.d.ts +69 -0
- package/dist/modules/inventory/service.d.ts.map +1 -0
- package/dist/modules/inventory/service.js +283 -0
- package/dist/modules/media/adapter.d.ts +16 -0
- package/dist/modules/media/adapter.d.ts.map +1 -0
- package/dist/modules/media/adapter.js +1 -0
- package/dist/modules/media/repository/index.d.ts +35 -0
- package/dist/modules/media/repository/index.d.ts.map +1 -0
- package/dist/modules/media/repository/index.js +176 -0
- package/dist/modules/media/schema.d.ts +289 -0
- package/dist/modules/media/schema.d.ts.map +1 -0
- package/dist/modules/media/schema.js +35 -0
- package/dist/modules/media/service.d.ts +42 -0
- package/dist/modules/media/service.d.ts.map +1 -0
- package/dist/modules/media/service.js +89 -0
- package/dist/modules/orders/repository/index.d.ts +48 -0
- package/dist/modules/orders/repository/index.d.ts.map +1 -0
- package/dist/modules/orders/repository/index.js +199 -0
- package/dist/modules/orders/schema.d.ts +672 -0
- package/dist/modules/orders/schema.d.ts.map +1 -0
- package/dist/modules/orders/schema.js +63 -0
- package/dist/modules/orders/service.d.ts +85 -0
- package/dist/modules/orders/service.d.ts.map +1 -0
- package/dist/modules/orders/service.js +313 -0
- package/dist/modules/orders/stale-order-cleanup.d.ts +27 -0
- package/dist/modules/orders/stale-order-cleanup.d.ts.map +1 -0
- package/dist/modules/orders/stale-order-cleanup.js +55 -0
- package/dist/modules/organization/service.d.ts +53 -0
- package/dist/modules/organization/service.d.ts.map +1 -0
- package/dist/modules/organization/service.js +151 -0
- package/dist/modules/payments/adapter.d.ts +42 -0
- package/dist/modules/payments/adapter.d.ts.map +1 -0
- package/dist/modules/payments/adapter.js +1 -0
- package/dist/modules/payments/repository/index.d.ts +5 -0
- package/dist/modules/payments/repository/index.d.ts.map +1 -0
- package/dist/modules/payments/repository/index.js +1 -0
- package/dist/modules/payments/service.d.ts +23 -0
- package/dist/modules/payments/service.d.ts.map +1 -0
- package/dist/modules/payments/service.js +72 -0
- package/dist/modules/pricing/repository/index.d.ts +34 -0
- package/dist/modules/pricing/repository/index.d.ts.map +1 -0
- package/dist/modules/pricing/repository/index.js +176 -0
- package/dist/modules/pricing/schema.d.ts +565 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -0
- package/dist/modules/pricing/schema.js +57 -0
- package/dist/modules/pricing/schemas.d.ts +37 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -0
- package/dist/modules/pricing/schemas.js +30 -0
- package/dist/modules/pricing/service.d.ts +62 -0
- package/dist/modules/pricing/service.d.ts.map +1 -0
- package/dist/modules/pricing/service.js +308 -0
- package/dist/modules/promotions/repository/index.d.ts +41 -0
- package/dist/modules/promotions/repository/index.d.ts.map +1 -0
- package/dist/modules/promotions/repository/index.js +204 -0
- package/dist/modules/promotions/schema.d.ts +427 -0
- package/dist/modules/promotions/schema.d.ts.map +1 -0
- package/dist/modules/promotions/schema.js +52 -0
- package/dist/modules/promotions/schemas.d.ts +33 -0
- package/dist/modules/promotions/schemas.d.ts.map +1 -0
- package/dist/modules/promotions/schemas.js +32 -0
- package/dist/modules/promotions/service.d.ts +85 -0
- package/dist/modules/promotions/service.d.ts.map +1 -0
- package/dist/modules/promotions/service.js +368 -0
- package/dist/modules/search/adapter.d.ts +51 -0
- package/dist/modules/search/adapter.d.ts.map +1 -0
- package/dist/modules/search/adapter.js +1 -0
- package/dist/modules/search/hooks.d.ts +8 -0
- package/dist/modules/search/hooks.d.ts.map +1 -0
- package/dist/modules/search/hooks.js +6 -0
- package/dist/modules/search/repository/index.d.ts +5 -0
- package/dist/modules/search/repository/index.d.ts.map +1 -0
- package/dist/modules/search/repository/index.js +1 -0
- package/dist/modules/search/service.d.ts +24 -0
- package/dist/modules/search/service.d.ts.map +1 -0
- package/dist/modules/search/service.js +217 -0
- package/dist/modules/shipping/calculator.d.ts +42 -0
- package/dist/modules/shipping/calculator.d.ts.map +1 -0
- package/dist/modules/shipping/calculator.js +91 -0
- package/dist/modules/shipping/repository/index.d.ts +5 -0
- package/dist/modules/shipping/repository/index.d.ts.map +1 -0
- package/dist/modules/shipping/repository/index.js +1 -0
- package/dist/modules/shipping/service.d.ts +28 -0
- package/dist/modules/shipping/service.d.ts.map +1 -0
- package/dist/modules/shipping/service.js +20 -0
- package/dist/modules/tax/adapter.d.ts +58 -0
- package/dist/modules/tax/adapter.d.ts.map +1 -0
- package/dist/modules/tax/adapter.js +1 -0
- package/dist/modules/tax/repository/index.d.ts +5 -0
- package/dist/modules/tax/repository/index.d.ts.map +1 -0
- package/dist/modules/tax/repository/index.js +1 -0
- package/dist/modules/tax/service.d.ts +19 -0
- package/dist/modules/tax/service.d.ts.map +1 -0
- package/dist/modules/tax/service.js +34 -0
- package/dist/modules/webhooks/hook.d.ts +13 -0
- package/dist/modules/webhooks/hook.d.ts.map +1 -0
- package/dist/modules/webhooks/hook.js +29 -0
- package/dist/modules/webhooks/repository/index.d.ts +40 -0
- package/dist/modules/webhooks/repository/index.d.ts.map +1 -0
- package/dist/modules/webhooks/repository/index.js +175 -0
- package/dist/modules/webhooks/schema.d.ts +404 -0
- package/dist/modules/webhooks/schema.d.ts.map +1 -0
- package/dist/modules/webhooks/schema.js +40 -0
- package/dist/modules/webhooks/service.d.ts +23 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -0
- package/dist/modules/webhooks/service.js +92 -0
- package/dist/modules/webhooks/signing.d.ts +2 -0
- package/dist/modules/webhooks/signing.d.ts.map +1 -0
- package/dist/modules/webhooks/signing.js +5 -0
- package/dist/modules/webhooks/ssrf-guard.d.ts +19 -0
- package/dist/modules/webhooks/ssrf-guard.d.ts.map +1 -0
- package/dist/modules/webhooks/ssrf-guard.js +79 -0
- package/dist/modules/webhooks/tasks.d.ts +16 -0
- package/dist/modules/webhooks/tasks.d.ts.map +1 -0
- package/dist/modules/webhooks/tasks.js +35 -0
- package/dist/modules/webhooks/worker.d.ts +21 -0
- package/dist/modules/webhooks/worker.d.ts.map +1 -0
- package/dist/modules/webhooks/worker.js +113 -0
- package/dist/runtime/commerce.d.ts +110 -0
- package/dist/runtime/commerce.d.ts.map +1 -0
- package/dist/runtime/commerce.js +37 -0
- package/dist/runtime/kernel.d.ts +71 -0
- package/dist/runtime/kernel.d.ts.map +1 -0
- package/dist/runtime/kernel.js +306 -0
- package/dist/runtime/logger.d.ts +11 -0
- package/dist/runtime/logger.d.ts.map +1 -0
- package/dist/runtime/logger.js +32 -0
- package/dist/runtime/shutdown.d.ts +15 -0
- package/dist/runtime/shutdown.d.ts.map +1 -0
- package/dist/runtime/shutdown.js +34 -0
- package/dist/test-utils/create-pglite-adapter.d.ts +32 -0
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -0
- package/dist/test-utils/create-pglite-adapter.js +107 -0
- package/dist/test-utils/create-plugin-test-app.d.ts +50 -0
- package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -0
- package/dist/test-utils/create-plugin-test-app.js +74 -0
- package/dist/test-utils/create-repository-test-harness.d.ts +8 -0
- package/dist/test-utils/create-repository-test-harness.d.ts.map +1 -0
- package/dist/test-utils/create-repository-test-harness.js +7 -0
- package/dist/test-utils/create-test-config.d.ts +18 -0
- package/dist/test-utils/create-test-config.d.ts.map +1 -0
- package/dist/test-utils/create-test-config.js +172 -0
- package/dist/test-utils/create-test-kernel.d.ts +3 -0
- package/dist/test-utils/create-test-kernel.d.ts.map +1 -0
- package/dist/test-utils/create-test-kernel.js +5 -0
- package/dist/test-utils/create-test-plugin-context.d.ts +42 -0
- package/dist/test-utils/create-test-plugin-context.d.ts.map +1 -0
- package/dist/test-utils/create-test-plugin-context.js +46 -0
- package/dist/test-utils/rest-api-test-utils.d.ts +64 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -0
- package/dist/test-utils/rest-api-test-utils.js +207 -0
- package/dist/test-utils/test-actors.d.ts +15 -0
- package/dist/test-utils/test-actors.d.ts.map +1 -0
- package/dist/test-utils/test-actors.js +57 -0
- package/dist/test-utils/typed-hooks.d.ts +43 -0
- package/dist/test-utils/typed-hooks.d.ts.map +1 -0
- package/dist/test-utils/typed-hooks.js +35 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +13 -0
- package/dist/types/commerce-types.d.ts +34 -0
- package/dist/types/commerce-types.d.ts.map +1 -0
- package/dist/types/commerce-types.js +1 -0
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +3 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/pagination.d.ts +11 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +15 -0
- package/package.json +2 -2
- package/src/auth/setup.ts +26 -0
- package/src/interfaces/rest/routes/customers.ts +7 -1
- package/src/interfaces/rest/routes/inventory.ts +4 -1
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/schemas/promotions.ts +10 -4
- package/src/modules/catalog/service.ts +19 -0
- package/src/modules/promotions/service.ts +32 -0
- package/src/runtime/commerce.ts +1 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
import { resolveOrgId } from "../../auth/org.js";
|
|
2
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
3
|
+
import { CommerceConflictError, CommerceNotFoundError, CommerceValidationError, toCommerceError, } from "../../kernel/errors.js";
|
|
4
|
+
import { runAfterHooks, runBeforeHooks } from "../../kernel/hooks/executor.js";
|
|
5
|
+
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
6
|
+
import { Err, Ok } from "../../kernel/result.js";
|
|
7
|
+
import { createLogger } from "../../utils/logger.js";
|
|
8
|
+
import { paginate } from "../../utils/pagination.js";
|
|
9
|
+
function cartesian(arrays) {
|
|
10
|
+
return arrays.reduce((acc, values) => acc.flatMap((entry) => values.map((value) => [...entry, value])), [[]]);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Helper to extract the value from a SellableCustomField based on its type.
|
|
14
|
+
*/
|
|
15
|
+
function getCustomFieldValue(field) {
|
|
16
|
+
switch (field.fieldType) {
|
|
17
|
+
case "text":
|
|
18
|
+
case "relation":
|
|
19
|
+
return field.textValue;
|
|
20
|
+
case "number":
|
|
21
|
+
return field.numberValue;
|
|
22
|
+
case "boolean":
|
|
23
|
+
return field.booleanValue;
|
|
24
|
+
case "date":
|
|
25
|
+
return field.dateValue;
|
|
26
|
+
case "json":
|
|
27
|
+
return field.jsonValue;
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class CatalogServiceImpl {
|
|
33
|
+
deps;
|
|
34
|
+
repo;
|
|
35
|
+
constructor(deps) {
|
|
36
|
+
this.deps = deps;
|
|
37
|
+
this.repo = deps.repository;
|
|
38
|
+
}
|
|
39
|
+
async validateAndCreateCustomFields(entityId, entityType, customFields, ctx) {
|
|
40
|
+
if (!customFields)
|
|
41
|
+
return Ok(undefined);
|
|
42
|
+
const entityConfig = this.deps.config.entities?.[entityType];
|
|
43
|
+
if (!entityConfig)
|
|
44
|
+
return Ok(undefined);
|
|
45
|
+
const definitionMap = new Map(entityConfig.fields.map((f) => [f.name, f]));
|
|
46
|
+
for (const [name, value] of Object.entries(customFields)) {
|
|
47
|
+
const def = definitionMap.get(name);
|
|
48
|
+
if (!def) {
|
|
49
|
+
return Err(new CommerceValidationError(`Unknown custom field: ${name}`));
|
|
50
|
+
}
|
|
51
|
+
const type = def.type;
|
|
52
|
+
let valid = false;
|
|
53
|
+
switch (type) {
|
|
54
|
+
case "text":
|
|
55
|
+
case "relation":
|
|
56
|
+
case "select":
|
|
57
|
+
valid = typeof value === "string";
|
|
58
|
+
break;
|
|
59
|
+
case "number":
|
|
60
|
+
valid = typeof value === "number";
|
|
61
|
+
break;
|
|
62
|
+
case "boolean":
|
|
63
|
+
valid = typeof value === "boolean";
|
|
64
|
+
break;
|
|
65
|
+
case "date":
|
|
66
|
+
valid = typeof value === "string" || value instanceof Date;
|
|
67
|
+
break;
|
|
68
|
+
case "json":
|
|
69
|
+
valid = typeof value === "object";
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
valid = false;
|
|
73
|
+
}
|
|
74
|
+
if (!valid) {
|
|
75
|
+
return Err(new CommerceValidationError(`Custom field ${name} expected type ${type}.`));
|
|
76
|
+
}
|
|
77
|
+
const fieldType = (type === "select" ? "text" : type);
|
|
78
|
+
const insertData = {
|
|
79
|
+
entityId,
|
|
80
|
+
fieldName: name,
|
|
81
|
+
fieldType,
|
|
82
|
+
};
|
|
83
|
+
switch (fieldType) {
|
|
84
|
+
case "text":
|
|
85
|
+
case "relation":
|
|
86
|
+
insertData.textValue = value;
|
|
87
|
+
break;
|
|
88
|
+
case "number":
|
|
89
|
+
insertData.numberValue = value;
|
|
90
|
+
break;
|
|
91
|
+
case "boolean":
|
|
92
|
+
insertData.booleanValue = value;
|
|
93
|
+
break;
|
|
94
|
+
case "date":
|
|
95
|
+
insertData.dateValue =
|
|
96
|
+
value instanceof Date ? value : new Date(value);
|
|
97
|
+
break;
|
|
98
|
+
case "json":
|
|
99
|
+
insertData.jsonValue = value;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
await this.repo.createCustomField(insertData, ctx);
|
|
103
|
+
}
|
|
104
|
+
return Ok(undefined);
|
|
105
|
+
}
|
|
106
|
+
async hydrateEntity(entity, options, ctx) {
|
|
107
|
+
const hydrated = { ...entity };
|
|
108
|
+
if (options?.includeAttributes) {
|
|
109
|
+
const attrs = await this.repo.findAttributesByEntityId(entity.id, ctx);
|
|
110
|
+
if (typeof options.includeAttributes === "object") {
|
|
111
|
+
hydrated.attributes = attrs.filter((a) => options.includeAttributes &&
|
|
112
|
+
typeof options.includeAttributes === "object" &&
|
|
113
|
+
options.includeAttributes.locales.includes(a.locale));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
hydrated.attributes = attrs;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (options?.includeVariants) {
|
|
120
|
+
const entityVariants = await this.repo.findVariantsByEntityId(entity.id, ctx);
|
|
121
|
+
const variantsWithOptions = await Promise.all(entityVariants.map(async (variant) => {
|
|
122
|
+
const optionValues = await this.repo.findVariantOptionValues(variant.id, ctx);
|
|
123
|
+
return {
|
|
124
|
+
...variant,
|
|
125
|
+
optionValueIds: optionValues.map((vov) => vov.optionValueId),
|
|
126
|
+
};
|
|
127
|
+
}));
|
|
128
|
+
hydrated.variants = variantsWithOptions;
|
|
129
|
+
}
|
|
130
|
+
if (options?.includeOptionTypes) {
|
|
131
|
+
const entityOptionTypes = await this.repo.findOptionTypesByEntityId(entity.id, ctx);
|
|
132
|
+
hydrated.optionTypes = await Promise.all(entityOptionTypes.map(async (ot) => {
|
|
133
|
+
const values = await this.repo.findOptionValuesByTypeId(ot.id, ctx);
|
|
134
|
+
return { ...ot, values };
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
if (options?.includeCategories) {
|
|
138
|
+
hydrated.categories = await this.repo.findEntityCategories(entity.id, ctx);
|
|
139
|
+
}
|
|
140
|
+
if (options?.includeBrands) {
|
|
141
|
+
hydrated.brands = await this.repo.findEntityBrands(entity.id, ctx);
|
|
142
|
+
}
|
|
143
|
+
if (options?.includeMedia) {
|
|
144
|
+
// TODO: integrate with MediaRepository when media service is refactored
|
|
145
|
+
hydrated.media = [];
|
|
146
|
+
}
|
|
147
|
+
if (options?.includePricing) {
|
|
148
|
+
try {
|
|
149
|
+
const pricingService = this.deps.services.pricing;
|
|
150
|
+
const priceResult = await pricingService.listPrices({ entityId: entity.id });
|
|
151
|
+
if (priceResult.ok && priceResult.value) {
|
|
152
|
+
hydrated.pricing = priceResult.value.prices.map((p) => ({
|
|
153
|
+
currency: p.currency,
|
|
154
|
+
amount: p.amount,
|
|
155
|
+
compareAtAmount: p.compareAtAmount ?? null,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Pricing service may not be initialized — leave pricing undefined
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return hydrated;
|
|
164
|
+
}
|
|
165
|
+
async create(input, actor, ctx) {
|
|
166
|
+
try {
|
|
167
|
+
assertPermission(actor, "catalog:create");
|
|
168
|
+
const orgId = resolveOrgId(actor);
|
|
169
|
+
const existingBySlug = await this.repo.findEntityBySlug(orgId, input.slug, ctx);
|
|
170
|
+
if (existingBySlug) {
|
|
171
|
+
return Err(new CommerceConflictError(`Entity with slug ${input.slug} already exists.`));
|
|
172
|
+
}
|
|
173
|
+
const beforeHooks = this.deps.hooks.resolve("catalog.beforeCreate");
|
|
174
|
+
const afterHooks = this.deps.hooks.resolve("catalog.afterCreate");
|
|
175
|
+
const logger = createLogger("catalog.create");
|
|
176
|
+
const context = createHookContext({
|
|
177
|
+
actor,
|
|
178
|
+
tx: ctx?.tx ?? null,
|
|
179
|
+
logger,
|
|
180
|
+
services: this.deps.services,
|
|
181
|
+
});
|
|
182
|
+
const processedInput = await runBeforeHooks(beforeHooks, input, "create", context);
|
|
183
|
+
const entity = await this.repo.createEntity({
|
|
184
|
+
organizationId: orgId,
|
|
185
|
+
type: processedInput.type,
|
|
186
|
+
slug: processedInput.slug,
|
|
187
|
+
status: "draft",
|
|
188
|
+
isVisible: false,
|
|
189
|
+
metadata: processedInput.metadata ?? {},
|
|
190
|
+
}, ctx);
|
|
191
|
+
if (processedInput.attributes) {
|
|
192
|
+
await this.repo.createAttribute({
|
|
193
|
+
entityId: entity.id,
|
|
194
|
+
locale: processedInput.attributes.locale ?? "en",
|
|
195
|
+
title: processedInput.attributes.title,
|
|
196
|
+
subtitle: processedInput.attributes.subtitle,
|
|
197
|
+
description: processedInput.attributes.description,
|
|
198
|
+
richDescription: processedInput.attributes.richDescription,
|
|
199
|
+
seoTitle: processedInput.attributes.seoTitle,
|
|
200
|
+
seoDescription: processedInput.attributes.seoDescription,
|
|
201
|
+
}, ctx);
|
|
202
|
+
}
|
|
203
|
+
const customFieldsResult = await this.validateAndCreateCustomFields(entity.id, entity.type, processedInput.customFields, ctx);
|
|
204
|
+
if (!customFieldsResult.ok)
|
|
205
|
+
return customFieldsResult;
|
|
206
|
+
const hookReport = await runAfterHooks(afterHooks, null, entity, "create", context);
|
|
207
|
+
const hydrated = await this.hydrateEntity(entity, undefined, ctx);
|
|
208
|
+
return Ok(hydrated, hookReport.hasErrors ? { hookErrors: hookReport.errors } : undefined);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return Err(toCommerceError(error));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async update(id, input, actor, ctx) {
|
|
215
|
+
try {
|
|
216
|
+
assertPermission(actor, "catalog:update");
|
|
217
|
+
const existing = await this.repo.findEntityById(id, ctx);
|
|
218
|
+
if (!existing)
|
|
219
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
220
|
+
const beforeHooks = this.deps.hooks.resolve("catalog.beforeUpdate");
|
|
221
|
+
const afterHooks = this.deps.hooks.resolve("catalog.afterUpdate");
|
|
222
|
+
const context = createHookContext({
|
|
223
|
+
actor,
|
|
224
|
+
tx: ctx?.tx ?? null,
|
|
225
|
+
logger: createLogger("catalog.update"),
|
|
226
|
+
services: this.deps.services,
|
|
227
|
+
});
|
|
228
|
+
const processed = await runBeforeHooks(beforeHooks, input, "update", context);
|
|
229
|
+
const updated = await this.repo.updateEntity(id, {
|
|
230
|
+
...(processed.slug !== undefined ? { slug: processed.slug } : {}),
|
|
231
|
+
...(processed.status !== undefined
|
|
232
|
+
? { status: processed.status }
|
|
233
|
+
: {}),
|
|
234
|
+
...(processed.metadata !== undefined
|
|
235
|
+
? { metadata: processed.metadata }
|
|
236
|
+
: {}),
|
|
237
|
+
...(processed.isVisible !== undefined
|
|
238
|
+
? { isVisible: processed.isVisible }
|
|
239
|
+
: {}),
|
|
240
|
+
}, ctx);
|
|
241
|
+
if (!updated)
|
|
242
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
243
|
+
const hookReport = await runAfterHooks(afterHooks, existing, updated, "update", context);
|
|
244
|
+
const hydrated = await this.hydrateEntity(updated, undefined, ctx);
|
|
245
|
+
return Ok(hydrated, hookReport.hasErrors ? { hookErrors: hookReport.errors } : undefined);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return Err(toCommerceError(error));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async delete(id, actor, ctx) {
|
|
252
|
+
try {
|
|
253
|
+
assertPermission(actor, "catalog:delete");
|
|
254
|
+
const existing = await this.repo.findEntityById(id, ctx);
|
|
255
|
+
if (!existing)
|
|
256
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
257
|
+
// Clean up related data (cascade handles most, but be explicit)
|
|
258
|
+
await this.repo.deleteAttributesByEntityId(id, ctx);
|
|
259
|
+
await this.repo.deleteCustomFieldsByEntityId(id, ctx);
|
|
260
|
+
await this.repo.deleteEntityCategoriesByEntityId(id, ctx);
|
|
261
|
+
await this.repo.deleteEntityBrandsByEntityId(id, ctx);
|
|
262
|
+
await this.repo.deleteVariantOptionValuesByEntityId(id, ctx);
|
|
263
|
+
await this.repo.deleteVariantsByEntityId(id, ctx);
|
|
264
|
+
await this.repo.deleteEntity(id, ctx);
|
|
265
|
+
return Ok(undefined);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return Err(toCommerceError(error));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async getById(id, options, actor, ctx) {
|
|
272
|
+
const context = createHookContext({
|
|
273
|
+
actor: actor ?? null,
|
|
274
|
+
tx: ctx?.tx ?? null,
|
|
275
|
+
logger: createLogger("catalog.read"),
|
|
276
|
+
services: this.deps.services,
|
|
277
|
+
});
|
|
278
|
+
const beforeHooks = this.deps.hooks.resolve("catalog.beforeRead");
|
|
279
|
+
const afterHooks = this.deps.hooks.resolve("catalog.afterRead");
|
|
280
|
+
const readInput = {
|
|
281
|
+
id,
|
|
282
|
+
...(options !== undefined ? { options } : {}),
|
|
283
|
+
};
|
|
284
|
+
const processed = await runBeforeHooks(beforeHooks, readInput, "read", context);
|
|
285
|
+
const entity = await this.repo.findEntityById(processed.id ?? id, ctx);
|
|
286
|
+
if (!entity)
|
|
287
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
288
|
+
// Org boundary check — prevent cross-org access
|
|
289
|
+
if (actor && entity.organizationId) {
|
|
290
|
+
const orgId = resolveOrgId(actor);
|
|
291
|
+
if (entity.organizationId !== orgId) {
|
|
292
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const result = await this.hydrateEntity(entity, processed.options ?? options, ctx);
|
|
296
|
+
const report = await runAfterHooks(afterHooks, null, result, "read", context);
|
|
297
|
+
return Ok(result, report.hasErrors ? { hookErrors: report.errors } : undefined);
|
|
298
|
+
}
|
|
299
|
+
async getBySlug(slug, options, actor, ctx) {
|
|
300
|
+
const context = createHookContext({
|
|
301
|
+
actor: actor ?? null,
|
|
302
|
+
tx: ctx?.tx ?? null,
|
|
303
|
+
logger: createLogger("catalog.read"),
|
|
304
|
+
services: this.deps.services,
|
|
305
|
+
});
|
|
306
|
+
const beforeHooks = this.deps.hooks.resolve("catalog.beforeRead");
|
|
307
|
+
const afterHooks = this.deps.hooks.resolve("catalog.afterRead");
|
|
308
|
+
const readInput = {
|
|
309
|
+
slug,
|
|
310
|
+
...(options !== undefined ? { options } : {}),
|
|
311
|
+
};
|
|
312
|
+
const processed = await runBeforeHooks(beforeHooks, readInput, "read", context);
|
|
313
|
+
const resolvedSlug = processed.slug ?? slug;
|
|
314
|
+
const orgId = resolveOrgId(actor ?? null);
|
|
315
|
+
const entity = await this.repo.findEntityBySlug(orgId, resolvedSlug, ctx);
|
|
316
|
+
if (!entity)
|
|
317
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
318
|
+
const result = await this.hydrateEntity(entity, processed.options ?? options, ctx);
|
|
319
|
+
const report = await runAfterHooks(afterHooks, null, result, "read", context);
|
|
320
|
+
return Ok(result, report.hasErrors ? { hookErrors: report.errors } : undefined);
|
|
321
|
+
}
|
|
322
|
+
async list(params, actor, ctx) {
|
|
323
|
+
const resolvedActor = actor ?? ctx?.actor ?? null;
|
|
324
|
+
const context = createHookContext({
|
|
325
|
+
actor: resolvedActor,
|
|
326
|
+
tx: ctx?.tx ?? null,
|
|
327
|
+
logger: createLogger("catalog.list"),
|
|
328
|
+
services: this.deps.services,
|
|
329
|
+
});
|
|
330
|
+
const beforeHooks = this.deps.hooks.resolve("catalog.beforeList");
|
|
331
|
+
const afterHooks = this.deps.hooks.resolve("catalog.afterList");
|
|
332
|
+
const processed = await runBeforeHooks(beforeHooks, params, "list", context);
|
|
333
|
+
const listOrgId = resolveOrgId(resolvedActor);
|
|
334
|
+
let entities = await this.repo.findEntities(listOrgId, {
|
|
335
|
+
...(processed.filter?.type ? { type: processed.filter.type } : {}),
|
|
336
|
+
...(processed.filter?.status
|
|
337
|
+
? { status: processed.filter.status }
|
|
338
|
+
: {}),
|
|
339
|
+
}, ctx);
|
|
340
|
+
// Category filter (M1 fix: validate input format, return error if not found)
|
|
341
|
+
if (processed.filter?.category) {
|
|
342
|
+
const catInput = processed.filter.category;
|
|
343
|
+
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
344
|
+
let category = await this.repo.findCategoryBySlug(listOrgId, catInput, ctx);
|
|
345
|
+
if (!category && isUUID.test(catInput)) {
|
|
346
|
+
category = await this.repo.findCategoryById(catInput, ctx);
|
|
347
|
+
}
|
|
348
|
+
if (!category) {
|
|
349
|
+
return Err(new CommerceValidationError(`Category not found: "${catInput}".`));
|
|
350
|
+
}
|
|
351
|
+
const entityIds = await this.repo.findEntitiesByCategory(category.id, ctx);
|
|
352
|
+
const entityIdSet = new Set(entityIds);
|
|
353
|
+
entities = entities.filter((e) => entityIdSet.has(e.id));
|
|
354
|
+
}
|
|
355
|
+
// Brand filter
|
|
356
|
+
if (processed.filter?.brand) {
|
|
357
|
+
let brand = await this.repo.findBrandBySlug(listOrgId, processed.filter.brand, ctx);
|
|
358
|
+
if (!brand) {
|
|
359
|
+
brand = await this.repo.findBrandById(processed.filter.brand, ctx);
|
|
360
|
+
}
|
|
361
|
+
if (brand) {
|
|
362
|
+
const brandEntityIds = [];
|
|
363
|
+
for (const entity of entities) {
|
|
364
|
+
const entityBrands = await this.repo.findEntityBrands(entity.id, ctx);
|
|
365
|
+
if (entityBrands.some((eb) => eb.brandId === brand.id)) {
|
|
366
|
+
brandEntityIds.push(entity.id);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const brandEntitySet = new Set(brandEntityIds);
|
|
370
|
+
entities = entities.filter((e) => brandEntitySet.has(e.id));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Custom field filter
|
|
374
|
+
if (processed.filter?.customField) {
|
|
375
|
+
const filteredIds = [];
|
|
376
|
+
for (const entity of entities) {
|
|
377
|
+
const fields = await this.repo.findCustomFieldsByEntityId(entity.id, ctx);
|
|
378
|
+
const matches = fields.some((field) => {
|
|
379
|
+
if (field.fieldName !== processed.filter?.customField?.fieldName)
|
|
380
|
+
return false;
|
|
381
|
+
return (getCustomFieldValue(field) === processed.filter.customField.value);
|
|
382
|
+
});
|
|
383
|
+
if (matches)
|
|
384
|
+
filteredIds.push(entity.id);
|
|
385
|
+
}
|
|
386
|
+
const filteredSet = new Set(filteredIds);
|
|
387
|
+
entities = entities.filter((e) => filteredSet.has(e.id));
|
|
388
|
+
}
|
|
389
|
+
// Sorting
|
|
390
|
+
if (processed.sort) {
|
|
391
|
+
const direction = processed.sort.direction === "asc" ? 1 : -1;
|
|
392
|
+
entities.sort((a, b) => {
|
|
393
|
+
const first = a[processed.sort.field];
|
|
394
|
+
const second = b[processed.sort.field];
|
|
395
|
+
if (first instanceof Date && second instanceof Date) {
|
|
396
|
+
return (first.getTime() - second.getTime()) * direction;
|
|
397
|
+
}
|
|
398
|
+
return String(first).localeCompare(String(second)) * direction;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const page = processed.pagination?.page ?? 1;
|
|
402
|
+
const limit = processed.pagination?.limit ?? 20;
|
|
403
|
+
const paged = paginate(entities, page, limit);
|
|
404
|
+
const hydratedItems = await Promise.all(paged.items.map((entity) => this.hydrateEntity(entity, undefined, ctx)));
|
|
405
|
+
const result = { items: hydratedItems, pagination: paged.pagination };
|
|
406
|
+
const report = await runAfterHooks(afterHooks, null, result, "list", context);
|
|
407
|
+
return Ok(result, report.hasErrors ? { hookErrors: report.errors } : undefined);
|
|
408
|
+
}
|
|
409
|
+
async changeStatus(id, status, actor, ctx) {
|
|
410
|
+
const entity = await this.repo.findEntityById(id, ctx);
|
|
411
|
+
if (!entity)
|
|
412
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
413
|
+
assertPermission(actor, "catalog:update");
|
|
414
|
+
const updateData = { status };
|
|
415
|
+
if (status === "active") {
|
|
416
|
+
updateData.publishedAt = new Date();
|
|
417
|
+
updateData.isVisible = true;
|
|
418
|
+
}
|
|
419
|
+
const updated = await this.repo.updateEntity(id, updateData, ctx);
|
|
420
|
+
if (!updated)
|
|
421
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
422
|
+
return Ok(await this.hydrateEntity(updated, undefined, ctx));
|
|
423
|
+
}
|
|
424
|
+
publish(id, actor, ctx) {
|
|
425
|
+
return this.changeStatus(id, "active", actor, ctx);
|
|
426
|
+
}
|
|
427
|
+
archive(id, actor, ctx) {
|
|
428
|
+
return this.changeStatus(id, "archived", actor, ctx);
|
|
429
|
+
}
|
|
430
|
+
discontinue(id, actor, ctx) {
|
|
431
|
+
return this.changeStatus(id, "discontinued", actor, ctx);
|
|
432
|
+
}
|
|
433
|
+
async setAttributes(entityId, locale, attrs, ctx) {
|
|
434
|
+
const entity = await this.repo.findEntityById(entityId, ctx);
|
|
435
|
+
if (!entity)
|
|
436
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
437
|
+
await this.repo.upsertAttribute(entityId, locale, {
|
|
438
|
+
title: attrs.title,
|
|
439
|
+
subtitle: attrs.subtitle,
|
|
440
|
+
description: attrs.description,
|
|
441
|
+
richDescription: attrs.richDescription,
|
|
442
|
+
seoTitle: attrs.seoTitle,
|
|
443
|
+
seoDescription: attrs.seoDescription,
|
|
444
|
+
}, ctx);
|
|
445
|
+
return Ok(undefined);
|
|
446
|
+
}
|
|
447
|
+
async getAttributes(entityId, locale, ctx) {
|
|
448
|
+
const attr = await this.repo.findAttributeByLocale(entityId, locale, ctx);
|
|
449
|
+
if (!attr)
|
|
450
|
+
return Err(new CommerceNotFoundError(`Attributes for locale ${locale} not found.`));
|
|
451
|
+
return Ok(attr);
|
|
452
|
+
}
|
|
453
|
+
async listCategories(ctx) {
|
|
454
|
+
const allCategories = await this.repo.findAllCategories(resolveOrgId(ctx?.actor ?? null), ctx);
|
|
455
|
+
const sorted = allCategories.sort((a, b) => a.sortOrder - b.sortOrder || a.slug.localeCompare(b.slug));
|
|
456
|
+
return Ok(sorted.map((c) => ({
|
|
457
|
+
id: c.id,
|
|
458
|
+
parentId: c.parentId,
|
|
459
|
+
slug: c.slug,
|
|
460
|
+
sortOrder: c.sortOrder,
|
|
461
|
+
metadata: c.metadata ?? {},
|
|
462
|
+
})));
|
|
463
|
+
}
|
|
464
|
+
async createCategory(input, actor, ctx) {
|
|
465
|
+
assertPermission(actor, "catalog:update");
|
|
466
|
+
if (input.id) {
|
|
467
|
+
const existingById = await this.repo.findCategoryById(input.id, ctx);
|
|
468
|
+
if (existingById) {
|
|
469
|
+
return Err(new CommerceConflictError(`Category with id ${input.id} already exists.`));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const orgId = resolveOrgId(actor);
|
|
473
|
+
const existingBySlug = await this.repo.findCategoryBySlug(orgId, input.slug, ctx);
|
|
474
|
+
if (existingBySlug) {
|
|
475
|
+
return Err(new CommerceConflictError(`Category with slug ${input.slug} already exists.`));
|
|
476
|
+
}
|
|
477
|
+
const category = await this.repo.createCategory({
|
|
478
|
+
organizationId: orgId,
|
|
479
|
+
...(input.id ? { id: input.id } : {}),
|
|
480
|
+
slug: input.slug,
|
|
481
|
+
sortOrder: input.sortOrder ?? 0,
|
|
482
|
+
metadata: input.metadata ?? {},
|
|
483
|
+
...(input.parentId !== undefined ? { parentId: input.parentId } : {}),
|
|
484
|
+
}, ctx);
|
|
485
|
+
return Ok({
|
|
486
|
+
id: category.id,
|
|
487
|
+
parentId: category.parentId,
|
|
488
|
+
slug: category.slug,
|
|
489
|
+
sortOrder: category.sortOrder,
|
|
490
|
+
metadata: category.metadata ?? {},
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
async updateCategory(id, input, actor, ctx) {
|
|
494
|
+
assertPermission(actor, "catalog:update");
|
|
495
|
+
const existing = await this.repo.findCategoryById(id, ctx);
|
|
496
|
+
if (!existing)
|
|
497
|
+
return Err(new CommerceNotFoundError("Category not found."));
|
|
498
|
+
if (input.slug) {
|
|
499
|
+
const catOrgId = resolveOrgId(actor);
|
|
500
|
+
const existingBySlug = await this.repo.findCategoryBySlug(catOrgId, input.slug, ctx);
|
|
501
|
+
if (existingBySlug && existingBySlug.id !== id) {
|
|
502
|
+
return Err(new CommerceConflictError(`Category with slug ${input.slug} already exists.`));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const updated = await this.repo.updateCategory(id, {
|
|
506
|
+
...(input.slug !== undefined ? { slug: input.slug } : {}),
|
|
507
|
+
...(input.sortOrder !== undefined
|
|
508
|
+
? { sortOrder: input.sortOrder }
|
|
509
|
+
: {}),
|
|
510
|
+
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
511
|
+
...(input.parentId !== undefined ? { parentId: input.parentId } : {}),
|
|
512
|
+
}, ctx);
|
|
513
|
+
if (!updated)
|
|
514
|
+
return Err(new CommerceNotFoundError("Category not found."));
|
|
515
|
+
return Ok({
|
|
516
|
+
id: updated.id,
|
|
517
|
+
parentId: updated.parentId,
|
|
518
|
+
slug: updated.slug,
|
|
519
|
+
sortOrder: updated.sortOrder,
|
|
520
|
+
metadata: updated.metadata ?? {},
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async deleteCategory(id, actor, ctx) {
|
|
524
|
+
assertPermission(actor, "catalog:update");
|
|
525
|
+
const existing = await this.repo.findCategoryById(id, ctx);
|
|
526
|
+
if (!existing)
|
|
527
|
+
return Err(new CommerceNotFoundError("Category not found."));
|
|
528
|
+
await this.repo.deleteEntityCategoriesByCategoryId(id, ctx);
|
|
529
|
+
await this.repo.deleteCategory(id, ctx);
|
|
530
|
+
return Ok(undefined);
|
|
531
|
+
}
|
|
532
|
+
async addToCategory(entityId, categoryId, ctx) {
|
|
533
|
+
const entity = await this.repo.findEntityById(entityId, ctx);
|
|
534
|
+
if (!entity)
|
|
535
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
536
|
+
const addCatOrgId = resolveOrgId(ctx?.actor ?? null);
|
|
537
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(categoryId);
|
|
538
|
+
let category = isUuid ? await this.repo.findCategoryById(categoryId, ctx) : null;
|
|
539
|
+
if (!category) {
|
|
540
|
+
category = await this.repo.findCategoryBySlug(addCatOrgId, categoryId, ctx);
|
|
541
|
+
}
|
|
542
|
+
if (!category) {
|
|
543
|
+
category = await this.repo.createCategory({
|
|
544
|
+
organizationId: addCatOrgId,
|
|
545
|
+
slug: categoryId,
|
|
546
|
+
sortOrder: 0,
|
|
547
|
+
metadata: {},
|
|
548
|
+
}, ctx);
|
|
549
|
+
}
|
|
550
|
+
await this.repo.addEntityToCategory(entityId, category.id, 0, ctx);
|
|
551
|
+
return Ok(undefined);
|
|
552
|
+
}
|
|
553
|
+
async removeFromCategory(entityId, categoryId, ctx) {
|
|
554
|
+
const isCatUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(categoryId);
|
|
555
|
+
let category = isCatUuid ? await this.repo.findCategoryById(categoryId, ctx) : null;
|
|
556
|
+
if (!category) {
|
|
557
|
+
category = await this.repo.findCategoryBySlug(resolveOrgId(ctx?.actor ?? null), categoryId, ctx);
|
|
558
|
+
}
|
|
559
|
+
const resolvedCategoryId = category?.id ?? categoryId;
|
|
560
|
+
const removed = await this.repo.removeEntityFromCategory(entityId, resolvedCategoryId, ctx);
|
|
561
|
+
if (!removed)
|
|
562
|
+
return Err(new CommerceNotFoundError("Category assignment not found."));
|
|
563
|
+
return Ok(undefined);
|
|
564
|
+
}
|
|
565
|
+
async listBrands(ctx) {
|
|
566
|
+
const allBrands = await this.repo.findAllBrands(resolveOrgId(ctx?.actor ?? null), ctx);
|
|
567
|
+
return Ok(allBrands.sort((a, b) => a.displayName.localeCompare(b.displayName)));
|
|
568
|
+
}
|
|
569
|
+
async createBrand(input, actor, ctx) {
|
|
570
|
+
assertPermission(actor, "catalog:update");
|
|
571
|
+
if (input.id) {
|
|
572
|
+
const existingById = await this.repo.findBrandById(input.id, ctx);
|
|
573
|
+
if (existingById) {
|
|
574
|
+
return Err(new CommerceConflictError(`Brand with id ${input.id} already exists.`));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const orgId = resolveOrgId(actor);
|
|
578
|
+
const existingBySlug = await this.repo.findBrandBySlug(orgId, input.slug, ctx);
|
|
579
|
+
if (existingBySlug) {
|
|
580
|
+
return Err(new CommerceConflictError(`Brand with slug ${input.slug} already exists.`));
|
|
581
|
+
}
|
|
582
|
+
return Ok(await this.repo.createBrand({
|
|
583
|
+
organizationId: orgId,
|
|
584
|
+
...(input.id ? { id: input.id } : {}),
|
|
585
|
+
slug: input.slug,
|
|
586
|
+
displayName: input.displayName,
|
|
587
|
+
metadata: input.metadata ?? {},
|
|
588
|
+
}, ctx));
|
|
589
|
+
}
|
|
590
|
+
async updateBrand(id, input, actor, ctx) {
|
|
591
|
+
assertPermission(actor, "catalog:update");
|
|
592
|
+
const existing = await this.repo.findBrandById(id, ctx);
|
|
593
|
+
if (!existing)
|
|
594
|
+
return Err(new CommerceNotFoundError("Brand not found."));
|
|
595
|
+
if (input.slug) {
|
|
596
|
+
const brandOrgId = resolveOrgId(actor);
|
|
597
|
+
const existingBySlug = await this.repo.findBrandBySlug(brandOrgId, input.slug, ctx);
|
|
598
|
+
if (existingBySlug && existingBySlug.id !== id) {
|
|
599
|
+
return Err(new CommerceConflictError(`Brand with slug ${input.slug} already exists.`));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const updated = await this.repo.updateBrand(id, {
|
|
603
|
+
...(input.slug !== undefined ? { slug: input.slug } : {}),
|
|
604
|
+
...(input.displayName !== undefined
|
|
605
|
+
? { displayName: input.displayName }
|
|
606
|
+
: {}),
|
|
607
|
+
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
608
|
+
}, ctx);
|
|
609
|
+
if (!updated)
|
|
610
|
+
return Err(new CommerceNotFoundError("Brand not found."));
|
|
611
|
+
return Ok(updated);
|
|
612
|
+
}
|
|
613
|
+
async deleteBrand(id, actor, ctx) {
|
|
614
|
+
assertPermission(actor, "catalog:update");
|
|
615
|
+
const existing = await this.repo.findBrandById(id, ctx);
|
|
616
|
+
if (!existing)
|
|
617
|
+
return Err(new CommerceNotFoundError("Brand not found."));
|
|
618
|
+
await this.repo.deleteEntityBrandsByBrandId(id, ctx);
|
|
619
|
+
await this.repo.deleteBrand(id, ctx);
|
|
620
|
+
return Ok(undefined);
|
|
621
|
+
}
|
|
622
|
+
async addToBrand(entityId, brandId, ctx) {
|
|
623
|
+
const entity = await this.repo.findEntityById(entityId, ctx);
|
|
624
|
+
if (!entity)
|
|
625
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
626
|
+
const addBrandOrgId = resolveOrgId(ctx?.actor ?? null);
|
|
627
|
+
const isBrandUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(brandId);
|
|
628
|
+
let brand = isBrandUuid ? await this.repo.findBrandById(brandId, ctx) : null;
|
|
629
|
+
if (!brand) {
|
|
630
|
+
brand = await this.repo.findBrandBySlug(addBrandOrgId, brandId, ctx);
|
|
631
|
+
}
|
|
632
|
+
if (!brand) {
|
|
633
|
+
brand = await this.repo.createBrand({
|
|
634
|
+
organizationId: addBrandOrgId,
|
|
635
|
+
slug: brandId,
|
|
636
|
+
displayName: brandId,
|
|
637
|
+
metadata: {},
|
|
638
|
+
}, ctx);
|
|
639
|
+
}
|
|
640
|
+
await this.repo.addEntityToBrand(entityId, brand.id, 0, ctx);
|
|
641
|
+
return Ok(undefined);
|
|
642
|
+
}
|
|
643
|
+
async removeFromBrand(entityId, brandId, ctx) {
|
|
644
|
+
const isBrUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(brandId);
|
|
645
|
+
let brand = isBrUuid ? await this.repo.findBrandById(brandId, ctx) : null;
|
|
646
|
+
if (!brand) {
|
|
647
|
+
brand = await this.repo.findBrandBySlug(resolveOrgId(ctx?.actor ?? null), brandId, ctx);
|
|
648
|
+
}
|
|
649
|
+
const resolvedBrandId = brand?.id ?? brandId;
|
|
650
|
+
const removed = await this.repo.removeEntityFromBrand(entityId, resolvedBrandId, ctx);
|
|
651
|
+
if (!removed)
|
|
652
|
+
return Err(new CommerceNotFoundError("Brand assignment not found."));
|
|
653
|
+
return Ok(undefined);
|
|
654
|
+
}
|
|
655
|
+
async createOptionType(input, actor, ctx) {
|
|
656
|
+
assertPermission(actor, "catalog:update");
|
|
657
|
+
const entity = await this.repo.findEntityById(input.entityId, ctx);
|
|
658
|
+
if (!entity)
|
|
659
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
660
|
+
const optionType = await this.repo.createOptionType({
|
|
661
|
+
entityId: input.entityId,
|
|
662
|
+
name: input.name,
|
|
663
|
+
displayName: input.name,
|
|
664
|
+
sortOrder: 0,
|
|
665
|
+
}, ctx);
|
|
666
|
+
// Create initial option values if provided
|
|
667
|
+
if (input.values) {
|
|
668
|
+
for (const value of input.values) {
|
|
669
|
+
await this.repo.createOptionValue({
|
|
670
|
+
optionTypeId: optionType.id,
|
|
671
|
+
value,
|
|
672
|
+
displayValue: value,
|
|
673
|
+
sortOrder: 0,
|
|
674
|
+
metadata: {},
|
|
675
|
+
}, ctx);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return Ok(optionType);
|
|
679
|
+
}
|
|
680
|
+
async createOptionValue(input, actor, ctx) {
|
|
681
|
+
assertPermission(actor, "catalog:update");
|
|
682
|
+
const optionType = await this.repo.findOptionTypeById(input.optionTypeId, ctx);
|
|
683
|
+
if (!optionType)
|
|
684
|
+
return Err(new CommerceNotFoundError("Option type not found."));
|
|
685
|
+
return Ok(await this.repo.createOptionValue({
|
|
686
|
+
optionTypeId: input.optionTypeId,
|
|
687
|
+
value: input.value,
|
|
688
|
+
displayValue: input.value,
|
|
689
|
+
sortOrder: 0,
|
|
690
|
+
metadata: {},
|
|
691
|
+
}, ctx));
|
|
692
|
+
}
|
|
693
|
+
async createVariant(input, actor, ctx) {
|
|
694
|
+
assertPermission(actor, "catalog:update");
|
|
695
|
+
const entity = await this.repo.findEntityById(input.entityId, ctx);
|
|
696
|
+
if (!entity)
|
|
697
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
698
|
+
// Resolve options record (e.g. { Color: "Red" }) into option value IDs
|
|
699
|
+
const entityOptionTypes = await this.repo.findOptionTypesByEntityId(input.entityId, ctx);
|
|
700
|
+
const optionValueIds = [];
|
|
701
|
+
for (const [optName, optVal] of Object.entries(input.options)) {
|
|
702
|
+
const ot = entityOptionTypes.find((t) => t.name === optName);
|
|
703
|
+
if (!ot) {
|
|
704
|
+
return Err(new CommerceValidationError(`Option type "${optName}" does not exist on this entity.`));
|
|
705
|
+
}
|
|
706
|
+
const typeValues = await this.repo.findOptionValuesByTypeId(ot.id, ctx);
|
|
707
|
+
const ov = typeValues.find((v) => v.value === optVal);
|
|
708
|
+
if (!ov) {
|
|
709
|
+
return Err(new CommerceValidationError(`Option value "${optVal}" does not exist for option type "${optName}".`));
|
|
710
|
+
}
|
|
711
|
+
optionValueIds.push(ov.id);
|
|
712
|
+
}
|
|
713
|
+
const variant = await this.repo.createVariant({
|
|
714
|
+
entityId: input.entityId,
|
|
715
|
+
status: "active",
|
|
716
|
+
sortOrder: 0,
|
|
717
|
+
metadata: {},
|
|
718
|
+
...(input.sku !== undefined ? { sku: input.sku } : {}),
|
|
719
|
+
}, ctx);
|
|
720
|
+
await this.repo.createVariantOptionValues(optionValueIds.map((optionValueId) => ({
|
|
721
|
+
variantId: variant.id,
|
|
722
|
+
optionValueId,
|
|
723
|
+
})), ctx);
|
|
724
|
+
return Ok(variant);
|
|
725
|
+
}
|
|
726
|
+
async generateVariants(entityId, strategy, actor, ctx) {
|
|
727
|
+
assertPermission(actor, "catalog:update");
|
|
728
|
+
const entity = await this.repo.findEntityById(entityId, ctx);
|
|
729
|
+
if (!entity)
|
|
730
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
731
|
+
const entityOptionTypes = await this.repo.findOptionTypesByEntityId(entityId, ctx);
|
|
732
|
+
const sortedOptionTypes = entityOptionTypes.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
733
|
+
const optionValueGroups = [];
|
|
734
|
+
for (const optionType of sortedOptionTypes) {
|
|
735
|
+
const values = await this.repo.findOptionValuesByTypeId(optionType.id, ctx);
|
|
736
|
+
const sortedValues = values.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
737
|
+
optionValueGroups.push(sortedValues.map((v) => v.id));
|
|
738
|
+
}
|
|
739
|
+
let combinations = [];
|
|
740
|
+
if (strategy.mode === "all") {
|
|
741
|
+
combinations = cartesian(optionValueGroups);
|
|
742
|
+
}
|
|
743
|
+
else if (strategy.mode === "manual") {
|
|
744
|
+
combinations = strategy.combinations;
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
const base = cartesian(optionValueGroups);
|
|
748
|
+
const include = strategy.matrix.include;
|
|
749
|
+
const exclude = strategy.matrix.exclude;
|
|
750
|
+
combinations = base.filter((combo) => {
|
|
751
|
+
const isExcluded = (exclude ?? []).some((pattern) => pattern.every((val) => combo.includes(val)));
|
|
752
|
+
if (isExcluded)
|
|
753
|
+
return false;
|
|
754
|
+
if (!include || include.length === 0)
|
|
755
|
+
return true;
|
|
756
|
+
return include.some((pattern) => pattern.every((val) => combo.includes(val)));
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
const created = [];
|
|
760
|
+
for (const combo of combinations) {
|
|
761
|
+
const variant = await this.repo.createVariant({
|
|
762
|
+
entityId,
|
|
763
|
+
status: "active",
|
|
764
|
+
sortOrder: 0,
|
|
765
|
+
metadata: { generatedBy: strategy.mode },
|
|
766
|
+
}, ctx);
|
|
767
|
+
await this.repo.createVariantOptionValues(combo.map((optionValueId) => ({
|
|
768
|
+
variantId: variant.id,
|
|
769
|
+
optionValueId,
|
|
770
|
+
})), ctx);
|
|
771
|
+
created.push(variant);
|
|
772
|
+
}
|
|
773
|
+
return Ok(created);
|
|
774
|
+
}
|
|
775
|
+
}
|