@porulle/core 0.10.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +13 -5
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +17 -0
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +33 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +47 -4
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +14 -1
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +19 -10
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +180 -51
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +55 -3
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +335 -6
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +80 -15
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +522 -14
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +71 -23
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +18 -12
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +8 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +3 -0
- package/dist/modules/search/service.d.ts +5 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +131 -11
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +10 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +17 -5
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +17 -0
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +32 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +30 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +96 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +19 -1
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +188 -53
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +566 -4
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +766 -26
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +89 -28
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +21 -18
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +8 -0
- package/src/modules/search/module.ts +3 -0
- package/src/modules/search/service.ts +142 -12
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +16 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import { CommerceNotFoundError, CommerceValidationError, } from "../../kernel/errors.js";
|
|
3
3
|
import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
4
4
|
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
5
5
|
import { Err, Ok } from "../../kernel/result.js";
|
|
6
6
|
import { createLogger } from "../../utils/logger.js";
|
|
7
|
-
function hookContext(actor, services, database, tx) {
|
|
7
|
+
function hookContext(actor, services, database, config, tx) {
|
|
8
8
|
return createHookContext({
|
|
9
9
|
actor,
|
|
10
10
|
tx,
|
|
@@ -12,6 +12,7 @@ function hookContext(actor, services, database, tx) {
|
|
|
12
12
|
services,
|
|
13
13
|
context: { moduleName: "pricing" },
|
|
14
14
|
database: { db: database.db },
|
|
15
|
+
commerceConfig: config,
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
18
|
function matchesQuantity(min, max, quantity) {
|
|
@@ -103,7 +104,7 @@ export class PricingService {
|
|
|
103
104
|
if (input.amount < 0) {
|
|
104
105
|
return Err(new CommerceValidationError("Base price amount cannot be negative."));
|
|
105
106
|
}
|
|
106
|
-
const orgId =
|
|
107
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
107
108
|
const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
|
|
108
109
|
if (!entity) {
|
|
109
110
|
return Err(new CommerceNotFoundError("Entity not found for price assignment."));
|
|
@@ -113,6 +114,7 @@ export class PricingService {
|
|
|
113
114
|
entityId: input.entityId,
|
|
114
115
|
currency: normalizeCurrency(input.currency),
|
|
115
116
|
amount: input.amount,
|
|
117
|
+
...(input.compareAtAmount !== undefined ? { compareAtAmount: input.compareAtAmount } : {}),
|
|
116
118
|
metadata: input.metadata ?? {},
|
|
117
119
|
variantId: input.variantId ?? null,
|
|
118
120
|
customerGroupId: input.customerGroupId ?? null,
|
|
@@ -133,10 +135,14 @@ export class PricingService {
|
|
|
133
135
|
sameNaturalKeyDate(p.validFrom, priceData.validFrom ?? null) &&
|
|
134
136
|
sameNaturalKeyDate(p.validUntil, priceData.validUntil ?? null));
|
|
135
137
|
const record = existing
|
|
136
|
-
? (await this.repo.updatePrice(existing.id, {
|
|
138
|
+
? (await this.repo.updatePrice(existing.id, {
|
|
139
|
+
amount: priceData.amount,
|
|
140
|
+
...(priceData.compareAtAmount !== undefined ? { compareAtAmount: priceData.compareAtAmount } : {}),
|
|
141
|
+
metadata: priceData.metadata,
|
|
142
|
+
}, ctx)) ?? existing
|
|
137
143
|
: await this.repo.createPrice(priceData, ctx);
|
|
138
144
|
const afterHooks = this.deps.hooks.resolve("pricing.afterCreate");
|
|
139
|
-
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
145
|
+
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
140
146
|
await runAfterHooks(afterHooks, null, record, "create", hctx);
|
|
141
147
|
return Ok(record);
|
|
142
148
|
}
|
|
@@ -154,7 +160,7 @@ export class PricingService {
|
|
|
154
160
|
if (!validTypes.includes(input.type)) {
|
|
155
161
|
return Err(new CommerceValidationError(`Invalid modifier type "${input.type}". Must be one of: ${validTypes.join(", ")}`));
|
|
156
162
|
}
|
|
157
|
-
const orgId =
|
|
163
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
158
164
|
const modifierData = {
|
|
159
165
|
organizationId: orgId,
|
|
160
166
|
name: input.name,
|
|
@@ -174,12 +180,12 @@ export class PricingService {
|
|
|
174
180
|
};
|
|
175
181
|
const modifier = await this.repo.createModifier(modifierData, ctx);
|
|
176
182
|
const afterHooks = this.deps.hooks.resolve("pricing.afterCreate");
|
|
177
|
-
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
183
|
+
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
178
184
|
await runAfterHooks(afterHooks, null, modifier, "create", hctx);
|
|
179
185
|
return Ok(modifier);
|
|
180
186
|
}
|
|
181
187
|
async listModifiers(filter, actor, ctx) {
|
|
182
|
-
const orgId =
|
|
188
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
183
189
|
const rows = await this.repo.findModifiers(orgId, {
|
|
184
190
|
...(filter?.entityId !== undefined ? { entityId: filter.entityId } : {}),
|
|
185
191
|
...(filter?.currency !== undefined
|
|
@@ -190,7 +196,7 @@ export class PricingService {
|
|
|
190
196
|
return Ok(rows);
|
|
191
197
|
}
|
|
192
198
|
async updateModifier(id, patch, actor, ctx) {
|
|
193
|
-
const orgId =
|
|
199
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
194
200
|
const existing = await this.repo.findModifierById(orgId, id, ctx);
|
|
195
201
|
if (!existing) {
|
|
196
202
|
return Err(new CommerceNotFoundError("Price modifier not found."));
|
|
@@ -208,7 +214,7 @@ export class PricingService {
|
|
|
208
214
|
return Ok(updated);
|
|
209
215
|
}
|
|
210
216
|
async deleteModifier(id, actor, ctx) {
|
|
211
|
-
const orgId =
|
|
217
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
212
218
|
const existing = await this.repo.findModifierById(orgId, id, ctx);
|
|
213
219
|
if (!existing) {
|
|
214
220
|
return Err(new CommerceNotFoundError("Price modifier not found."));
|
|
@@ -217,7 +223,7 @@ export class PricingService {
|
|
|
217
223
|
return Ok({ deleted: true });
|
|
218
224
|
}
|
|
219
225
|
async listPrices(filter, actor, ctx) {
|
|
220
|
-
const orgId =
|
|
226
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
221
227
|
// Get all prices for the entity if specified, or filter after retrieval
|
|
222
228
|
let prices = [];
|
|
223
229
|
if (filter?.entityId) {
|
|
@@ -256,7 +262,7 @@ export class PricingService {
|
|
|
256
262
|
return Ok({ prices, modifiers });
|
|
257
263
|
}
|
|
258
264
|
async resolve(input, actor, ctx) {
|
|
259
|
-
const orgId =
|
|
265
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
260
266
|
const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
|
|
261
267
|
if (!entity) {
|
|
262
268
|
return Err(new CommerceNotFoundError(`Entity ${input.entityId} not found.`));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/promotions/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/promotions/module.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,gBAAgB;gBACb,OAAO,UAAU;qBAAmB,OAAO,eAAe;0CAiBxE,CAAC"}
|
|
@@ -6,6 +6,7 @@ import type { PromotionsRepository, Promotion, PromotionUsage } from "./reposito
|
|
|
6
6
|
import type { CatalogRepository } from "../catalog/repository/index.js";
|
|
7
7
|
import type { OrdersRepository } from "../orders/repository/index.js";
|
|
8
8
|
import type { Actor } from "../../auth/types.js";
|
|
9
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
9
10
|
import { type PromotionType } from "./schemas.js";
|
|
10
11
|
export type { PromotionType };
|
|
11
12
|
/** Filter status for listing promotions. Used by the REST API and service layer. */
|
|
@@ -15,6 +16,7 @@ interface PromotionServiceDeps {
|
|
|
15
16
|
catalogRepository: CatalogRepository;
|
|
16
17
|
ordersRepository: OrdersRepository;
|
|
17
18
|
hooks: HookRegistry;
|
|
19
|
+
config: CommerceConfig;
|
|
18
20
|
services: Record<string, unknown>;
|
|
19
21
|
database: DatabaseAdapter;
|
|
20
22
|
}
|
|
@@ -69,6 +71,7 @@ export declare class PromotionService {
|
|
|
69
71
|
private readonly catalogRepo;
|
|
70
72
|
private readonly ordersRepo;
|
|
71
73
|
constructor(deps: PromotionServiceDeps);
|
|
74
|
+
private resolveEvaluationOrgId;
|
|
72
75
|
create(input: CreatePromotionInput, actor?: Actor | null, ctx?: TxContext): Promise<Result<Promotion>>;
|
|
73
76
|
deactivate(orgId: string, id: string, ctx?: TxContext): Promise<Result<Promotion>>;
|
|
74
77
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/promotions/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAOrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EACV,oBAAoB,EACpB,SAAS,EACT,cAAc,EAEf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/promotions/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,11 @@ import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
|
3
3
|
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
4
4
|
import { Err, Ok } from "../../kernel/result.js";
|
|
5
5
|
import { createLogger } from "../../utils/logger.js";
|
|
6
|
-
import { resolveOrgId } from "../../auth/org.js";
|
|
6
|
+
import { resolveOrgId, resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
7
7
|
// PromotionType is derived from the zod enum (single source of truth) in
|
|
8
8
|
// ./schemas.js; re-export it here for backwards-compatible imports.
|
|
9
9
|
import { promotionTypeEnum } from "./schemas.js";
|
|
10
|
-
function hookContext(actor, services, database, tx) {
|
|
10
|
+
function hookContext(actor, services, database, config, tx) {
|
|
11
11
|
return createHookContext({
|
|
12
12
|
actor,
|
|
13
13
|
tx,
|
|
@@ -15,6 +15,7 @@ function hookContext(actor, services, database, tx) {
|
|
|
15
15
|
services,
|
|
16
16
|
context: { moduleName: "promotions" },
|
|
17
17
|
database: { db: database.db },
|
|
18
|
+
commerceConfig: config,
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
function matchesWindow(promotion, timestamp) {
|
|
@@ -70,6 +71,12 @@ export class PromotionService {
|
|
|
70
71
|
this.catalogRepo = deps.catalogRepository;
|
|
71
72
|
this.ordersRepo = deps.ordersRepository;
|
|
72
73
|
}
|
|
74
|
+
resolveEvaluationOrgId(ctx, explicitOrgId) {
|
|
75
|
+
if (explicitOrgId) {
|
|
76
|
+
return resolveOrgId(ctx?.actor ?? null, explicitOrgId);
|
|
77
|
+
}
|
|
78
|
+
return resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
|
|
79
|
+
}
|
|
73
80
|
async create(input, actor, ctx) {
|
|
74
81
|
if (!input.name) {
|
|
75
82
|
return Err(new CommerceValidationError("Promotion name is required."));
|
|
@@ -82,7 +89,7 @@ export class PromotionService {
|
|
|
82
89
|
if (!validTypes.includes(input.type)) {
|
|
83
90
|
return Err(new CommerceValidationError(`Invalid promotion type "${input.type}". Must be one of: ${validTypes.join(", ")}`));
|
|
84
91
|
}
|
|
85
|
-
const orgId =
|
|
92
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
86
93
|
if (input.code) {
|
|
87
94
|
const normalized = input.code.trim().toUpperCase();
|
|
88
95
|
const existing = await this.repo.findByCode(orgId, normalized, ctx);
|
|
@@ -109,7 +116,7 @@ export class PromotionService {
|
|
|
109
116
|
validUntil: input.validUntil ?? null,
|
|
110
117
|
}, ctx);
|
|
111
118
|
const afterHooks = this.deps.hooks.resolve("promotions.afterCreate");
|
|
112
|
-
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
119
|
+
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
113
120
|
await runAfterHooks(afterHooks, null, promotion, "create", hctx);
|
|
114
121
|
return Ok(promotion);
|
|
115
122
|
}
|
|
@@ -123,7 +130,8 @@ export class PromotionService {
|
|
|
123
130
|
return Err(new CommerceNotFoundError("Promotion not found."));
|
|
124
131
|
}
|
|
125
132
|
const afterHooks = this.deps.hooks.resolve("promotions.afterUpdate");
|
|
126
|
-
|
|
133
|
+
// Actor-less by design; resolves to the deployment's declared organization.
|
|
134
|
+
const hctx = hookContext(null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
127
135
|
await runAfterHooks(afterHooks, promotion, updated, "update", hctx);
|
|
128
136
|
return Ok(updated);
|
|
129
137
|
}
|
|
@@ -192,12 +200,12 @@ export class PromotionService {
|
|
|
192
200
|
return Err(new CommerceNotFoundError("Promotion not found."));
|
|
193
201
|
}
|
|
194
202
|
const afterHooks = this.deps.hooks.resolve("promotions.afterUpdate");
|
|
195
|
-
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
|
|
203
|
+
const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
196
204
|
await runAfterHooks(afterHooks, existing, updated, "update", hctx);
|
|
197
205
|
return Ok(updated);
|
|
198
206
|
}
|
|
199
207
|
async list(filter, actor, ctx) {
|
|
200
|
-
const orgId =
|
|
208
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
201
209
|
const all = await this.repo.findAll(orgId, ctx);
|
|
202
210
|
const now = new Date();
|
|
203
211
|
if (!filter?.status)
|
|
@@ -218,7 +226,7 @@ export class PromotionService {
|
|
|
218
226
|
return Ok(sortByPriority(filtered));
|
|
219
227
|
}
|
|
220
228
|
async listActive(actor, timestamp = new Date(), ctx) {
|
|
221
|
-
const orgId =
|
|
229
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
222
230
|
const active = await this.repo.findActive(orgId, ctx);
|
|
223
231
|
// Further filter by timestamp in case repo returns slightly stale results
|
|
224
232
|
const filtered = active.filter((p) => matchesWindow(p, timestamp));
|
|
@@ -226,7 +234,7 @@ export class PromotionService {
|
|
|
226
234
|
}
|
|
227
235
|
async validate(code, context, ctx) {
|
|
228
236
|
const normalized = code.trim().toUpperCase();
|
|
229
|
-
const evalOrgId =
|
|
237
|
+
const evalOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
|
|
230
238
|
const promotion = await this.repo.findByCode(evalOrgId, normalized, ctx);
|
|
231
239
|
if (!promotion) {
|
|
232
240
|
return Err(new CommerceNotFoundError(`Promotion code ${normalized} not found.`));
|
|
@@ -251,7 +259,7 @@ export class PromotionService {
|
|
|
251
259
|
}
|
|
252
260
|
async applyPromotions(context, ctx) {
|
|
253
261
|
const timestamp = context.timestamp ?? new Date();
|
|
254
|
-
const applyOrgId =
|
|
262
|
+
const applyOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
|
|
255
263
|
const result = {
|
|
256
264
|
totalDiscount: 0,
|
|
257
265
|
freeShipping: false,
|
|
@@ -341,7 +349,7 @@ export class PromotionService {
|
|
|
341
349
|
if (!context.customerId) {
|
|
342
350
|
return "First-order promotion requires authenticated customer.";
|
|
343
351
|
}
|
|
344
|
-
const orders = await this.ordersRepo.findByCustomerId(
|
|
352
|
+
const orders = await this.ordersRepo.findByCustomerId(this.resolveEvaluationOrgId(ctx, context.orgId), context.customerId, ctx);
|
|
345
353
|
if (orders.length > 0) {
|
|
346
354
|
return "Promotion is valid for first order only.";
|
|
347
355
|
}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import type { Result } from "../../kernel/result.js";
|
|
2
2
|
export interface SearchDocument {
|
|
3
3
|
id: string;
|
|
4
|
+
organizationId?: string;
|
|
4
5
|
type: string;
|
|
5
6
|
slug: string;
|
|
6
7
|
title: string;
|
|
7
8
|
description?: string;
|
|
8
9
|
status?: string;
|
|
10
|
+
isVisible?: boolean;
|
|
9
11
|
categories: string[];
|
|
10
12
|
brands: string[];
|
|
11
13
|
text: string;
|
|
14
|
+
attributes?: Record<string, string | string[]>;
|
|
12
15
|
payload?: Record<string, unknown>;
|
|
13
16
|
}
|
|
14
17
|
export interface SearchFilters {
|
|
18
|
+
/** Server-supplied tenant constraint; REST callers cannot choose this value. */
|
|
19
|
+
organizationId?: string;
|
|
15
20
|
type?: string;
|
|
16
21
|
category?: string;
|
|
17
22
|
brand?: string;
|
|
18
23
|
status?: string;
|
|
24
|
+
attributes?: Record<string, string | string[]>;
|
|
19
25
|
}
|
|
20
26
|
export interface SearchQueryParams {
|
|
21
27
|
query: string;
|
|
@@ -28,6 +34,8 @@ export interface SearchSuggestParams {
|
|
|
28
34
|
prefix: string;
|
|
29
35
|
type?: string;
|
|
30
36
|
limit?: number;
|
|
37
|
+
/** Server-supplied tenant constraint; REST callers cannot choose this value. */
|
|
38
|
+
organizationId?: string;
|
|
31
39
|
}
|
|
32
40
|
export interface SearchHit {
|
|
33
41
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/modules/search/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/modules/search/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CACjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/search/module.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/search/module.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,YAAY,2GAmBxB,CAAC"}
|
|
@@ -8,6 +8,9 @@ export const searchModule = defineModule({
|
|
|
8
8
|
const config = deps.config;
|
|
9
9
|
return new SearchService({
|
|
10
10
|
catalogRepository: deps.services.catalog.repository,
|
|
11
|
+
resolveEntityFieldDefinitions: deps.services.catalog.resolveEntityFieldDefinitions.bind(deps.services.catalog),
|
|
12
|
+
config,
|
|
13
|
+
...(config.entities ? { entities: config.entities } : {}),
|
|
11
14
|
...(config.search?.adapter ? { adapter: config.search.adapter } : {}),
|
|
12
15
|
...(config.search?.defaultFacets
|
|
13
16
|
? { defaultFacets: config.search.defaultFacets }
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { type Result } from "../../kernel/result.js";
|
|
2
2
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
3
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
3
4
|
import type { CatalogRepository } from "../catalog/repository/index.js";
|
|
4
5
|
import type { SearchAdapter, SearchQueryParams, SearchQueryResult, SearchSuggestParams } from "./adapter.js";
|
|
6
|
+
import type { EntityFieldDefinitionResolver } from "../catalog/service.js";
|
|
5
7
|
interface SearchServiceDeps {
|
|
6
8
|
catalogRepository: CatalogRepository;
|
|
9
|
+
config: CommerceConfig;
|
|
10
|
+
entities?: CommerceConfig["entities"];
|
|
11
|
+
resolveEntityFieldDefinitions?: EntityFieldDefinitionResolver;
|
|
7
12
|
adapter?: SearchAdapter;
|
|
8
13
|
defaultFacets?: string[];
|
|
9
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/search/service.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/search/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,aAAa,EAGb,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAG3E,UAAU,iBAAiB;IACzB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,6BAA6B,CAAC,EAAE,6BAA6B,CAAC;IAC9D,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAoDD,qBAAa,aAAa;IACZ,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,iBAAiB;YAE7B,gBAAgB;YAwBhB,YAAY;YAwBZ,aAAa;YAuEb,YAAY;IAoB1B,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,aAAa;IAoEf,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAcpE,KAAK,CACT,MAAM,EAAE,iBAAiB,EACzB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IA8F/B,OAAO,CACX,MAAM,EAAE,mBAAmB,EAC3B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;CA+C7B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Ok } from "../../kernel/result.js";
|
|
2
|
-
import {
|
|
1
|
+
import { Err, Ok } from "../../kernel/result.js";
|
|
2
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
3
|
+
import { OrgResolutionError } from "../../kernel/errors.js";
|
|
4
|
+
import { canReadUnpublishedCatalog } from "../catalog/read-policy.js";
|
|
3
5
|
function unique(values) {
|
|
4
6
|
return [...new Set(values)];
|
|
5
7
|
}
|
|
@@ -19,6 +21,22 @@ function includesAllTokens(haystack, tokens) {
|
|
|
19
21
|
const lower = haystack.toLowerCase();
|
|
20
22
|
return tokens.every((token) => lower.includes(token));
|
|
21
23
|
}
|
|
24
|
+
function customFieldValue(field) {
|
|
25
|
+
if (field.textValue != null)
|
|
26
|
+
return field.textValue;
|
|
27
|
+
if (field.numberValue != null)
|
|
28
|
+
return String(field.numberValue);
|
|
29
|
+
if (field.booleanValue != null)
|
|
30
|
+
return String(field.booleanValue);
|
|
31
|
+
if (field.dateValue != null)
|
|
32
|
+
return field.dateValue.toISOString();
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function attributeValues(value) {
|
|
36
|
+
if (value == null)
|
|
37
|
+
return [];
|
|
38
|
+
return Array.isArray(value) ? value : [value];
|
|
39
|
+
}
|
|
22
40
|
function scoreText(document, tokens) {
|
|
23
41
|
if (tokens.length === 0)
|
|
24
42
|
return 1;
|
|
@@ -55,44 +73,80 @@ export class SearchService {
|
|
|
55
73
|
return unique(slugs);
|
|
56
74
|
}
|
|
57
75
|
async buildDocument(entity, ctx) {
|
|
58
|
-
const
|
|
59
|
-
const primary =
|
|
76
|
+
const localizedAttributes = await this.deps.catalogRepository.findAttributesByEntityId(entity.id, ctx);
|
|
77
|
+
const primary = localizedAttributes[0];
|
|
60
78
|
const title = primary?.title ?? entity.slug;
|
|
61
79
|
const description = primary?.description;
|
|
62
80
|
const categories = await this.entityCategories(entity.id, ctx);
|
|
63
81
|
const brands = await this.entityBrands(entity.id, ctx);
|
|
82
|
+
const definitions = this.deps.resolveEntityFieldDefinitions
|
|
83
|
+
? await this.deps.resolveEntityFieldDefinitions(entity.type, entity.organizationId, ctx)
|
|
84
|
+
: (this.deps.entities?.[entity.type]?.fields ?? []);
|
|
85
|
+
const filterableFields = new Set(definitions
|
|
86
|
+
.filter((field) => field.filterable === true)
|
|
87
|
+
.map((field) => field.name));
|
|
88
|
+
const customFields = await this.deps.catalogRepository.findCustomFieldsByEntityId(entity.id, ctx);
|
|
89
|
+
const attributeValuesByName = new Map();
|
|
90
|
+
for (const field of customFields) {
|
|
91
|
+
if (field.locale !== "en" || !filterableFields.has(field.fieldName))
|
|
92
|
+
continue;
|
|
93
|
+
const value = customFieldValue(field);
|
|
94
|
+
if (value == null)
|
|
95
|
+
continue;
|
|
96
|
+
const values = attributeValuesByName.get(field.fieldName) ?? [];
|
|
97
|
+
if (!values.includes(value))
|
|
98
|
+
values.push(value);
|
|
99
|
+
attributeValuesByName.set(field.fieldName, values);
|
|
100
|
+
}
|
|
101
|
+
const attributes = Object.fromEntries([...attributeValuesByName.entries()].map(([name, values]) => [
|
|
102
|
+
name,
|
|
103
|
+
values.length === 1 ? values[0] : values,
|
|
104
|
+
]));
|
|
64
105
|
const textParts = [
|
|
65
106
|
entity.slug,
|
|
66
107
|
title,
|
|
67
108
|
description ?? "",
|
|
68
109
|
...categories,
|
|
69
110
|
...brands,
|
|
70
|
-
...
|
|
71
|
-
...
|
|
111
|
+
...localizedAttributes.map((attr) => attr.title),
|
|
112
|
+
...localizedAttributes.map((attr) => attr.description ?? ""),
|
|
72
113
|
];
|
|
73
114
|
return {
|
|
74
115
|
id: entity.id,
|
|
116
|
+
organizationId: entity.organizationId,
|
|
75
117
|
type: entity.type,
|
|
76
118
|
slug: entity.slug,
|
|
77
119
|
title,
|
|
78
120
|
...(description ? { description } : {}),
|
|
79
121
|
status: entity.status,
|
|
122
|
+
isVisible: entity.isVisible,
|
|
80
123
|
categories,
|
|
81
124
|
brands,
|
|
82
125
|
text: textParts.join(" ").trim(),
|
|
126
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
83
127
|
payload: {
|
|
84
128
|
metadata: entity.metadata ?? undefined,
|
|
85
129
|
},
|
|
86
130
|
};
|
|
87
131
|
}
|
|
88
132
|
async allDocuments(ctx) {
|
|
89
|
-
const
|
|
90
|
-
|
|
133
|
+
const actor = ctx?.actor;
|
|
134
|
+
if (!actor) {
|
|
135
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
136
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
137
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
138
|
+
// organization but not that.
|
|
139
|
+
throw new OrgResolutionError("Organization could not be resolved for a search read.");
|
|
140
|
+
}
|
|
141
|
+
const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
142
|
+
const entities = await this.deps.catalogRepository.findEntities(orgId, canReadUnpublishedCatalog(actor) ? undefined : { status: "active", isVisible: true }, ctx);
|
|
91
143
|
return Promise.all(entities.map((entity) => this.buildDocument(entity, ctx)));
|
|
92
144
|
}
|
|
93
145
|
matchesFilters(document, filters) {
|
|
94
146
|
if (!filters)
|
|
95
147
|
return true;
|
|
148
|
+
if (filters.organizationId && document.organizationId !== filters.organizationId)
|
|
149
|
+
return false;
|
|
96
150
|
if (filters.type && document.type !== filters.type)
|
|
97
151
|
return false;
|
|
98
152
|
if (filters.status && document.status !== filters.status)
|
|
@@ -101,6 +155,12 @@ export class SearchService {
|
|
|
101
155
|
return false;
|
|
102
156
|
if (filters.brand && !document.brands.includes(filters.brand))
|
|
103
157
|
return false;
|
|
158
|
+
for (const [name, requested] of Object.entries(filters.attributes ?? {})) {
|
|
159
|
+
const actual = attributeValues(document.attributes?.[name]);
|
|
160
|
+
const expected = attributeValues(requested);
|
|
161
|
+
if (!expected.some((value) => actual.includes(value)))
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
104
164
|
return true;
|
|
105
165
|
}
|
|
106
166
|
computeFacets(documents, requested) {
|
|
@@ -138,6 +198,24 @@ export class SearchService {
|
|
|
138
198
|
}
|
|
139
199
|
}
|
|
140
200
|
}
|
|
201
|
+
const attributeName = facet.startsWith("attributes.")
|
|
202
|
+
? facet.slice("attributes.".length)
|
|
203
|
+
: facet;
|
|
204
|
+
if (attributeName !== "type" &&
|
|
205
|
+
attributeName !== "status" &&
|
|
206
|
+
attributeName !== "category" &&
|
|
207
|
+
attributeName !== "categories" &&
|
|
208
|
+
attributeName !== "brand" &&
|
|
209
|
+
attributeName !== "brands") {
|
|
210
|
+
const values = {};
|
|
211
|
+
for (const document of documents) {
|
|
212
|
+
for (const value of new Set(attributeValues(document.attributes?.[attributeName]))) {
|
|
213
|
+
values[value] = (values[value] ?? 0) + 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (Object.keys(values).length > 0)
|
|
217
|
+
output[attributeName] = values;
|
|
218
|
+
}
|
|
141
219
|
}
|
|
142
220
|
return output;
|
|
143
221
|
}
|
|
@@ -156,17 +234,46 @@ export class SearchService {
|
|
|
156
234
|
// Cap query length to prevent abuse / excessive processing
|
|
157
235
|
const safeQuery = (params.query ?? "").slice(0, 500);
|
|
158
236
|
if (this.deps.adapter) {
|
|
159
|
-
|
|
237
|
+
const actor = ctx?.actor ?? null;
|
|
238
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
239
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
240
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
241
|
+
// organization but not that.
|
|
242
|
+
if (!actor)
|
|
243
|
+
return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
|
|
244
|
+
const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
245
|
+
const result = await this.deps.adapter.search({
|
|
160
246
|
...params,
|
|
161
247
|
query: safeQuery,
|
|
162
248
|
page,
|
|
163
249
|
limit,
|
|
250
|
+
filters: {
|
|
251
|
+
...params.filters,
|
|
252
|
+
organizationId,
|
|
253
|
+
...(!canReadUnpublishedCatalog(actor) ? { status: "active" } : {}),
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
if (!result.ok)
|
|
257
|
+
return result;
|
|
258
|
+
const hits = result.value.hits.filter((hit) => hit.document.organizationId === organizationId &&
|
|
259
|
+
(canReadUnpublishedCatalog(actor) ||
|
|
260
|
+
(hit.document.status === "active" && hit.document.isVisible !== false)));
|
|
261
|
+
return Ok({
|
|
262
|
+
...result.value,
|
|
263
|
+
hits,
|
|
264
|
+
total: hits.length,
|
|
265
|
+
facets: this.computeFacets(hits.map((hit) => hit.document), params.facets),
|
|
164
266
|
});
|
|
165
267
|
}
|
|
166
268
|
// In-memory fallback: require a non-empty query to avoid loading all entities
|
|
167
269
|
// (allDocuments triggers N+1 DB queries per entity — unsafe at scale)
|
|
168
270
|
const tokens = tokenize(safeQuery);
|
|
169
|
-
if (tokens.length === 0 &&
|
|
271
|
+
if (tokens.length === 0 &&
|
|
272
|
+
!params.filters?.type &&
|
|
273
|
+
!params.filters?.category &&
|
|
274
|
+
!params.filters?.brand &&
|
|
275
|
+
!params.filters?.status &&
|
|
276
|
+
Object.keys(params.filters?.attributes ?? {}).length === 0) {
|
|
170
277
|
return Ok({ hits: [], total: 0, page, limit, facets: {} });
|
|
171
278
|
}
|
|
172
279
|
try {
|
|
@@ -215,11 +322,24 @@ export class SearchService {
|
|
|
215
322
|
if (prefix.length === 0)
|
|
216
323
|
return Ok([]);
|
|
217
324
|
if (this.deps.adapter) {
|
|
218
|
-
|
|
325
|
+
const actor = ctx?.actor ?? null;
|
|
326
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
327
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
328
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
329
|
+
// organization but not that.
|
|
330
|
+
if (!actor)
|
|
331
|
+
return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
|
|
332
|
+
const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
333
|
+
const result = await this.deps.adapter.suggest({
|
|
219
334
|
...params,
|
|
220
335
|
prefix,
|
|
221
336
|
limit,
|
|
337
|
+
organizationId,
|
|
222
338
|
});
|
|
339
|
+
if (!result.ok)
|
|
340
|
+
return result;
|
|
341
|
+
const visibleTitles = new Set((await this.allDocuments(ctx)).map((document) => document.title));
|
|
342
|
+
return Ok(result.value.filter((title) => visibleTitles.has(title)));
|
|
223
343
|
}
|
|
224
344
|
// In-memory fallback: require prefix >= 2 chars to avoid loading all entities
|
|
225
345
|
// (allDocuments triggers N+1 DB queries per entity — unsafe at scale)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/module.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,eAAO,MAAM,cAAc;mBACR,OAAO,aAAa;0CAWrC,CAAC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Actor } from "../../auth/types.js";
|
|
2
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
2
3
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
3
4
|
import { type Result } from "../../kernel/result.js";
|
|
4
5
|
import type { SettingsRepository } from "./repository/index.js";
|
|
5
6
|
interface SettingsServiceDeps {
|
|
6
7
|
repository: SettingsRepository;
|
|
8
|
+
config: CommerceConfig;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Org-scoped runtime settings (issue #49).
|
|
@@ -14,6 +16,7 @@ interface SettingsServiceDeps {
|
|
|
14
16
|
*/
|
|
15
17
|
export declare class SettingsService {
|
|
16
18
|
private repository;
|
|
19
|
+
private config;
|
|
17
20
|
constructor(deps: SettingsServiceDeps);
|
|
18
21
|
/** Runtime read for plugins/hooks. Unset groups read as `{}`. */
|
|
19
22
|
read(orgId: string, group: string, ctx?: TxContext): Promise<Record<string, unknown>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAErE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAErE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,MAAM,CAAiB;gBAEnB,IAAI,EAAE,mBAAmB;IAKrC,iEAAiE;IAC3D,IAAI,CACR,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAK7B,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAKrC,MAAM,CACV,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAQ3D,wEAAwE;IAClE,WAAW,CACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAkB5C"}
|