@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,6 +1,7 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
3
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
4
|
+
import { CommerceNotFoundError, CommerceValidationError, toCommerceError } from "../../kernel/errors.js";
|
|
4
5
|
import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
5
6
|
import { createHookContext } from "../../kernel/hooks/create-context.js";
|
|
6
7
|
import { Err, Ok } from "../../kernel/result.js";
|
|
@@ -133,7 +134,7 @@ export class FulfillmentService {
|
|
|
133
134
|
this.strategies.set("appointment", new AppointmentFulfillmentStrategy());
|
|
134
135
|
}
|
|
135
136
|
async fulfillOrder(orderId, actor, ctx) {
|
|
136
|
-
const orgId =
|
|
137
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
137
138
|
const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
|
|
138
139
|
if (!order)
|
|
139
140
|
return Err(new CommerceNotFoundError("Order not found."));
|
|
@@ -185,6 +186,7 @@ export class FulfillmentService {
|
|
|
185
186
|
services: this.deps.services,
|
|
186
187
|
context: { moduleName: "fulfillment" },
|
|
187
188
|
database: { db: this.deps.database.db },
|
|
189
|
+
commerceConfig: this.deps.config,
|
|
188
190
|
});
|
|
189
191
|
await runAfterHooks(afterHooks, null, record, "create", hookCtx);
|
|
190
192
|
// Create a fulfillment line item linking this fulfillment to the order line item
|
|
@@ -204,7 +206,13 @@ export class FulfillmentService {
|
|
|
204
206
|
* fulfillment (per-line quantities) and multiple fulfillments per order.
|
|
205
207
|
*/
|
|
206
208
|
async createFulfillment(input, actor, ctx) {
|
|
207
|
-
|
|
209
|
+
try {
|
|
210
|
+
assertPermission(actor ?? null, "orders:update");
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return Err(toCommerceError(error));
|
|
214
|
+
}
|
|
215
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
208
216
|
const order = await this.deps.ordersRepository.findById(orgId, input.orderId, ctx);
|
|
209
217
|
if (!order)
|
|
210
218
|
return Err(new CommerceNotFoundError("Order not found."));
|
|
@@ -246,7 +254,7 @@ export class FulfillmentService {
|
|
|
246
254
|
fulfillmentId: created.id,
|
|
247
255
|
eventType: "created",
|
|
248
256
|
toStatus: status,
|
|
249
|
-
...(actor?.userId
|
|
257
|
+
...(actor?.userId != null ? { actorId: actor.userId } : {}),
|
|
250
258
|
description: input.trackingNumber
|
|
251
259
|
? `Shipment recorded (${input.trackingNumber})`
|
|
252
260
|
: "Fulfillment recorded",
|
|
@@ -278,6 +286,7 @@ export class FulfillmentService {
|
|
|
278
286
|
services: this.deps.services,
|
|
279
287
|
context: { moduleName: "fulfillment" },
|
|
280
288
|
database: { db: this.deps.database.db },
|
|
289
|
+
commerceConfig: this.deps.config,
|
|
281
290
|
});
|
|
282
291
|
await runAfterHooks(afterHooks, null, record, "create", hookCtx);
|
|
283
292
|
return Ok(record);
|
|
@@ -328,7 +337,7 @@ export class FulfillmentService {
|
|
|
328
337
|
if (!matchedRecord) {
|
|
329
338
|
return Err(new CommerceNotFoundError("Digital download not found."));
|
|
330
339
|
}
|
|
331
|
-
const orgId =
|
|
340
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
332
341
|
const order = await this.deps.ordersRepository.findById(orgId, orderId, ctx);
|
|
333
342
|
if (!order || order.customerId !== userId) {
|
|
334
343
|
return Err(new CommerceNotFoundError("Order not found."));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/inventory/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAW5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EACL,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACvG,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAEvG,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,mBAAmB,CAAC;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,qBAAa,gBAAgB;IAGf,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;gBAEvB,IAAI,EAAE,oBAAoB;YAIhC,eAAe;YAQf,aAAa;IA6BrB,eAAe,CACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,EACzB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/inventory/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAW5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EACL,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACvG,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAEvG,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,mBAAmB,CAAC;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,qBAAa,gBAAgB;IAGf,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;gBAEvB,IAAI,EAAE,oBAAoB;YAIhC,eAAe;YAQf,aAAa;IA6BrB,eAAe,CACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,EACzB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IA4BvB,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IASnF,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,SAAS,EACf,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAcpB,UAAU,CACd,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EACpD,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAyB9B,aAAa,CACjB,SAAS,EAAE,MAAM,EAAE,EACnB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IASpC,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,SAAS,EACf,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,GACnB,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAS9B,OAAO,CACX,KAAK,EAAE,qBAAqB,EAC5B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAyDlB,OAAO,CACX,KAAK,EAAE,qBAAqB,EAC5B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAqDxB;;;;;;OAMG;IACG,cAAc,CAClB,KAAK,EAAE,oBAAoB,EAC3B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CACR,MAAM,CAAC;QACL,KAAK,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CACH;IA8HD;;;OAGG;IACG,MAAM,CACV,KAAK,EAAE,oBAAoB,EAC3B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAKlC;;;;;;OAMG;IACG,WAAW,CACf,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,EACD,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAgClC;;;;;;;;;;OAUG;IACG,oBAAoB,CACxB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,EACD,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IA+ClB,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;CA2BnC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import { assertPermission } from "../../auth/permissions.js";
|
|
3
3
|
import { CommerceInventoryRecordNotFoundError, CommerceNotFoundError, CommerceValidationError, INVENTORY_RECORD_NOT_FOUND_MESSAGE, toCommerceError, } from "../../kernel/errors.js";
|
|
4
4
|
import { runAfterHooks } from "../../kernel/hooks/executor.js";
|
|
@@ -20,7 +20,7 @@ export class InventoryService {
|
|
|
20
20
|
}
|
|
21
21
|
async pickWarehouse(actor, ctx, orgIdOverride) {
|
|
22
22
|
const orgId = orgIdOverride ??
|
|
23
|
-
|
|
23
|
+
resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
24
24
|
const warehouses = await this.repo.findAllWarehouses(orgId, ctx);
|
|
25
25
|
const sorted = warehouses.sort((a, b) => a.priority - b.priority);
|
|
26
26
|
if (sorted.length > 0) {
|
|
@@ -41,7 +41,7 @@ export class InventoryService {
|
|
|
41
41
|
if (!input.name || !input.code) {
|
|
42
42
|
return Err(new CommerceValidationError("Warehouse name and code are required."));
|
|
43
43
|
}
|
|
44
|
-
const orgId =
|
|
44
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
45
45
|
const warehouse = await this.repo.createWarehouse({
|
|
46
46
|
organizationId: orgId,
|
|
47
47
|
name: input.name,
|
|
@@ -54,17 +54,17 @@ export class InventoryService {
|
|
|
54
54
|
return Ok(warehouse);
|
|
55
55
|
}
|
|
56
56
|
async listWarehouses(actor, ctx) {
|
|
57
|
-
const orgId =
|
|
57
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
58
58
|
const warehouses = await this.repo.findAllWarehouses(orgId, ctx);
|
|
59
59
|
return Ok(warehouses.sort((a, b) => a.priority - b.priority));
|
|
60
60
|
}
|
|
61
61
|
async getAvailable(entityId, variantId, ctx, actor) {
|
|
62
|
-
const orgId =
|
|
62
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
63
63
|
const available = await this.repo.getAvailableQuantity(orgId, entityId, variantId, ctx);
|
|
64
64
|
return Ok(available);
|
|
65
65
|
}
|
|
66
66
|
async listLevels(params, actor, ctx) {
|
|
67
|
-
const orgId =
|
|
67
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
68
68
|
if (params?.entityId) {
|
|
69
69
|
const levels = await this.repo.findLevelsByEntityId(orgId, params.entityId, ctx);
|
|
70
70
|
return Ok(levels);
|
|
@@ -77,12 +77,12 @@ export class InventoryService {
|
|
|
77
77
|
return Ok(levels);
|
|
78
78
|
}
|
|
79
79
|
async checkMultiple(entityIds, actor, ctx) {
|
|
80
|
-
const orgId =
|
|
80
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
81
81
|
const data = await this.repo.getAvailableQuantities(orgId, entityIds, ctx);
|
|
82
82
|
return Ok(data);
|
|
83
83
|
}
|
|
84
84
|
async getLevelsByEntityId(entityId, ctx, actor) {
|
|
85
|
-
const orgId =
|
|
85
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
86
86
|
const levels = await this.repo.findLevelsByEntityId(orgId, entityId, ctx);
|
|
87
87
|
return Ok(levels);
|
|
88
88
|
}
|
|
@@ -90,7 +90,7 @@ export class InventoryService {
|
|
|
90
90
|
if (input.quantity <= 0) {
|
|
91
91
|
return Err(new CommerceValidationError("Reservation quantity must be greater than zero."));
|
|
92
92
|
}
|
|
93
|
-
const orgId =
|
|
93
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
94
94
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
95
95
|
const performedBy = input.performedBy ?? actor?.userId ?? "system";
|
|
96
96
|
const variantId = input.variantId ?? null;
|
|
@@ -119,7 +119,7 @@ export class InventoryService {
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
async release(input, actor, ctx) {
|
|
122
|
-
const orgId =
|
|
122
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
123
123
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
124
124
|
const performedBy = input.performedBy ?? actor?.userId ?? "system";
|
|
125
125
|
const variantId = input.variantId ?? null;
|
|
@@ -177,7 +177,7 @@ export class InventoryService {
|
|
|
177
177
|
const variantId = input.variantId ?? null;
|
|
178
178
|
const performedBy = input.performedBy ?? actor?.userId ?? "system";
|
|
179
179
|
const doAdjust = async (txCtx) => {
|
|
180
|
-
const orgId =
|
|
180
|
+
const orgId = resolveOrgIdForCommerce(actor ?? txCtx.actor ?? null, this.deps.config);
|
|
181
181
|
// Lock the level first so `before` and the write are atomic.
|
|
182
182
|
const existing = await this.repo.findLevelForUpdate(orgId, input.entityId, variantId, warehouseId, txCtx);
|
|
183
183
|
const before = existing?.quantityOnHand ?? 0;
|
|
@@ -237,6 +237,7 @@ export class InventoryService {
|
|
|
237
237
|
services: this.deps.services,
|
|
238
238
|
context: { moduleName: "inventory" },
|
|
239
239
|
database: { db: this.deps.database.db },
|
|
240
|
+
commerceConfig: this.deps.config,
|
|
240
241
|
});
|
|
241
242
|
const afterHooks = this.deps.hooks.resolve("inventory.afterAdjust");
|
|
242
243
|
await runAfterHooks(afterHooks, null, level, "update", hookCtx);
|
|
@@ -264,7 +265,7 @@ export class InventoryService {
|
|
|
264
265
|
*/
|
|
265
266
|
async setAbsolute(input, actor, ctx) {
|
|
266
267
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
267
|
-
const orgId =
|
|
268
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
268
269
|
const existingLevel = await this.repo.findLevelByKey(orgId, input.entityId, warehouseId, input.variantId, ctx);
|
|
269
270
|
const currentOnHand = existingLevel?.quantityOnHand ?? 0;
|
|
270
271
|
const delta = input.quantity - currentOnHand;
|
|
@@ -290,7 +291,7 @@ export class InventoryService {
|
|
|
290
291
|
*/
|
|
291
292
|
async deductForFulfillment(input, ctx) {
|
|
292
293
|
const orgId = input.orgId ??
|
|
293
|
-
|
|
294
|
+
resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
|
|
294
295
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(null, ctx, orgId));
|
|
295
296
|
const variantId = input.variantId ?? null;
|
|
296
297
|
const level = await this.repo.findLevelByKey(orgId, input.entityId, warehouseId, variantId != null ? variantId : undefined, ctx);
|
|
@@ -314,7 +315,7 @@ export class InventoryService {
|
|
|
314
315
|
return Ok(undefined);
|
|
315
316
|
}
|
|
316
317
|
async setUnitCost(entityId, warehouseId, unitCost, variantId, ctx) {
|
|
317
|
-
const orgId =
|
|
318
|
+
const orgId = resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
|
|
318
319
|
const level = await this.repo.findLevelByKey(orgId, entityId, warehouseId, variantId, ctx);
|
|
319
320
|
if (!level) {
|
|
320
321
|
return Err(new CommerceNotFoundError(`Inventory level not found for entity ${entityId} at warehouse ${warehouseId}.`));
|
|
@@ -176,6 +176,57 @@ export declare const mediaModule: import("../../kernel/module/define.js").AppMod
|
|
|
176
176
|
}, {}, {
|
|
177
177
|
$type: Record<string, unknown>;
|
|
178
178
|
}>;
|
|
179
|
+
origin: import("drizzle-orm/pg-core").PgColumn<{
|
|
180
|
+
name: "origin";
|
|
181
|
+
tableName: "media_assets";
|
|
182
|
+
dataType: "string";
|
|
183
|
+
columnType: "PgEnumColumn";
|
|
184
|
+
data: "generated" | "merchant" | "imported";
|
|
185
|
+
driverParam: string;
|
|
186
|
+
notNull: true;
|
|
187
|
+
hasDefault: true;
|
|
188
|
+
isPrimaryKey: false;
|
|
189
|
+
isAutoincrement: false;
|
|
190
|
+
hasRuntimeDefault: false;
|
|
191
|
+
enumValues: ["merchant", "generated", "imported"];
|
|
192
|
+
baseColumn: never;
|
|
193
|
+
identity: undefined;
|
|
194
|
+
generated: undefined;
|
|
195
|
+
}, {}, {}>;
|
|
196
|
+
confidence: import("drizzle-orm/pg-core").PgColumn<{
|
|
197
|
+
name: "confidence";
|
|
198
|
+
tableName: "media_assets";
|
|
199
|
+
dataType: "string";
|
|
200
|
+
columnType: "PgNumeric";
|
|
201
|
+
data: string;
|
|
202
|
+
driverParam: string;
|
|
203
|
+
notNull: false;
|
|
204
|
+
hasDefault: false;
|
|
205
|
+
isPrimaryKey: false;
|
|
206
|
+
isAutoincrement: false;
|
|
207
|
+
hasRuntimeDefault: false;
|
|
208
|
+
enumValues: undefined;
|
|
209
|
+
baseColumn: never;
|
|
210
|
+
identity: undefined;
|
|
211
|
+
generated: undefined;
|
|
212
|
+
}, {}, {}>;
|
|
213
|
+
derivedFromAssetId: import("drizzle-orm/pg-core").PgColumn<{
|
|
214
|
+
name: "derived_from_asset_id";
|
|
215
|
+
tableName: "media_assets";
|
|
216
|
+
dataType: "string";
|
|
217
|
+
columnType: "PgUUID";
|
|
218
|
+
data: string;
|
|
219
|
+
driverParam: string;
|
|
220
|
+
notNull: false;
|
|
221
|
+
hasDefault: false;
|
|
222
|
+
isPrimaryKey: false;
|
|
223
|
+
isAutoincrement: false;
|
|
224
|
+
hasRuntimeDefault: false;
|
|
225
|
+
enumValues: undefined;
|
|
226
|
+
baseColumn: never;
|
|
227
|
+
identity: undefined;
|
|
228
|
+
generated: undefined;
|
|
229
|
+
}, {}, {}>;
|
|
179
230
|
uploadedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
180
231
|
name: "uploaded_at";
|
|
181
232
|
tableName: "media_assets";
|
|
@@ -285,6 +336,23 @@ export declare const mediaModule: import("../../kernel/module/define.js").AppMod
|
|
|
285
336
|
identity: undefined;
|
|
286
337
|
generated: undefined;
|
|
287
338
|
}, {}, {}>;
|
|
339
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
340
|
+
name: "created_at";
|
|
341
|
+
tableName: "entity_media";
|
|
342
|
+
dataType: "date";
|
|
343
|
+
columnType: "PgTimestamp";
|
|
344
|
+
data: Date;
|
|
345
|
+
driverParam: string;
|
|
346
|
+
notNull: true;
|
|
347
|
+
hasDefault: true;
|
|
348
|
+
isPrimaryKey: false;
|
|
349
|
+
isAutoincrement: false;
|
|
350
|
+
hasRuntimeDefault: false;
|
|
351
|
+
enumValues: undefined;
|
|
352
|
+
baseColumn: never;
|
|
353
|
+
identity: undefined;
|
|
354
|
+
generated: undefined;
|
|
355
|
+
}, {}, {}>;
|
|
288
356
|
};
|
|
289
357
|
dialect: "pg";
|
|
290
358
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/media/module.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,eAAO,MAAM,WAAW
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/media/module.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yCAkBtB,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const mediaAssetOrigin: import("drizzle-orm/pg-core").PgEnum<["merchant", "generated", "imported"]>;
|
|
2
|
+
export type MediaAssetOrigin = (typeof mediaAssetOrigin.enumValues)[number];
|
|
1
3
|
export declare const mediaAssets: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
2
4
|
name: "media_assets";
|
|
3
5
|
schema: undefined;
|
|
@@ -174,6 +176,57 @@ export declare const mediaAssets: import("drizzle-orm/pg-core").PgTableWithColum
|
|
|
174
176
|
}, {}, {
|
|
175
177
|
$type: Record<string, unknown>;
|
|
176
178
|
}>;
|
|
179
|
+
origin: import("drizzle-orm/pg-core").PgColumn<{
|
|
180
|
+
name: "origin";
|
|
181
|
+
tableName: "media_assets";
|
|
182
|
+
dataType: "string";
|
|
183
|
+
columnType: "PgEnumColumn";
|
|
184
|
+
data: "generated" | "merchant" | "imported";
|
|
185
|
+
driverParam: string;
|
|
186
|
+
notNull: true;
|
|
187
|
+
hasDefault: true;
|
|
188
|
+
isPrimaryKey: false;
|
|
189
|
+
isAutoincrement: false;
|
|
190
|
+
hasRuntimeDefault: false;
|
|
191
|
+
enumValues: ["merchant", "generated", "imported"];
|
|
192
|
+
baseColumn: never;
|
|
193
|
+
identity: undefined;
|
|
194
|
+
generated: undefined;
|
|
195
|
+
}, {}, {}>;
|
|
196
|
+
confidence: import("drizzle-orm/pg-core").PgColumn<{
|
|
197
|
+
name: "confidence";
|
|
198
|
+
tableName: "media_assets";
|
|
199
|
+
dataType: "string";
|
|
200
|
+
columnType: "PgNumeric";
|
|
201
|
+
data: string;
|
|
202
|
+
driverParam: string;
|
|
203
|
+
notNull: false;
|
|
204
|
+
hasDefault: false;
|
|
205
|
+
isPrimaryKey: false;
|
|
206
|
+
isAutoincrement: false;
|
|
207
|
+
hasRuntimeDefault: false;
|
|
208
|
+
enumValues: undefined;
|
|
209
|
+
baseColumn: never;
|
|
210
|
+
identity: undefined;
|
|
211
|
+
generated: undefined;
|
|
212
|
+
}, {}, {}>;
|
|
213
|
+
derivedFromAssetId: import("drizzle-orm/pg-core").PgColumn<{
|
|
214
|
+
name: "derived_from_asset_id";
|
|
215
|
+
tableName: "media_assets";
|
|
216
|
+
dataType: "string";
|
|
217
|
+
columnType: "PgUUID";
|
|
218
|
+
data: string;
|
|
219
|
+
driverParam: string;
|
|
220
|
+
notNull: false;
|
|
221
|
+
hasDefault: false;
|
|
222
|
+
isPrimaryKey: false;
|
|
223
|
+
isAutoincrement: false;
|
|
224
|
+
hasRuntimeDefault: false;
|
|
225
|
+
enumValues: undefined;
|
|
226
|
+
baseColumn: never;
|
|
227
|
+
identity: undefined;
|
|
228
|
+
generated: undefined;
|
|
229
|
+
}, {}, {}>;
|
|
177
230
|
uploadedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
178
231
|
name: "uploaded_at";
|
|
179
232
|
tableName: "media_assets";
|
|
@@ -283,6 +336,23 @@ export declare const entityMedia: import("drizzle-orm/pg-core").PgTableWithColum
|
|
|
283
336
|
identity: undefined;
|
|
284
337
|
generated: undefined;
|
|
285
338
|
}, {}, {}>;
|
|
339
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
340
|
+
name: "created_at";
|
|
341
|
+
tableName: "entity_media";
|
|
342
|
+
dataType: "date";
|
|
343
|
+
columnType: "PgTimestamp";
|
|
344
|
+
data: Date;
|
|
345
|
+
driverParam: string;
|
|
346
|
+
notNull: true;
|
|
347
|
+
hasDefault: true;
|
|
348
|
+
isPrimaryKey: false;
|
|
349
|
+
isAutoincrement: false;
|
|
350
|
+
hasRuntimeDefault: false;
|
|
351
|
+
enumValues: undefined;
|
|
352
|
+
baseColumn: never;
|
|
353
|
+
identity: undefined;
|
|
354
|
+
generated: undefined;
|
|
355
|
+
}, {}, {}>;
|
|
286
356
|
};
|
|
287
357
|
dialect: "pg";
|
|
288
358
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/modules/media/schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/modules/media/schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,6EAAsE,CAAC;AACpG,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBvB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBrB,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { index, integer, jsonb, numeric, pgEnum, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
2
3
|
import { organization } from "../../auth/auth-schema.js";
|
|
3
4
|
import { sellableEntities, variants } from "../catalog/schema.js";
|
|
5
|
+
export const mediaAssetOrigin = pgEnum("media_asset_origin", ["merchant", "generated", "imported"]);
|
|
4
6
|
export const mediaAssets = pgTable("media_assets", {
|
|
5
7
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
6
8
|
organizationId: text("organization_id")
|
|
@@ -14,6 +16,11 @@ export const mediaAssets = pgTable("media_assets", {
|
|
|
14
16
|
height: integer("height"),
|
|
15
17
|
alt: text("alt"),
|
|
16
18
|
metadata: jsonb("metadata").$type().default({}),
|
|
19
|
+
origin: mediaAssetOrigin("origin").notNull().default("merchant"),
|
|
20
|
+
confidence: numeric("confidence", { precision: 4, scale: 3 }),
|
|
21
|
+
derivedFromAssetId: uuid("derived_from_asset_id").references(() => mediaAssets.id, {
|
|
22
|
+
onDelete: "set null",
|
|
23
|
+
}),
|
|
17
24
|
uploadedAt: timestamp("uploaded_at", { withTimezone: true }).defaultNow().notNull(),
|
|
18
25
|
}, (table) => ({
|
|
19
26
|
orgIdx: index("idx_media_assets_org").on(table.organizationId),
|
|
@@ -32,4 +39,15 @@ export const entityMedia = pgTable("entity_media", {
|
|
|
32
39
|
enum: ["primary", "gallery", "thumbnail", "video", "document"],
|
|
33
40
|
}).notNull(),
|
|
34
41
|
sortOrder: integer("sort_order").notNull().default(0),
|
|
35
|
-
})
|
|
42
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
43
|
+
}, (table) => ({
|
|
44
|
+
// Two partial indexes instead of one NULLS DISTINCT index: Postgres treats
|
|
45
|
+
// null variant ids as distinct, so a single index cannot reject duplicate
|
|
46
|
+
// entity-level rows.
|
|
47
|
+
entityLevelUnique: uniqueIndex("entity_media_entity_level_unique")
|
|
48
|
+
.on(table.entityId, table.mediaAssetId)
|
|
49
|
+
.where(sql `${table.variantId} IS NULL`),
|
|
50
|
+
variantLevelUnique: uniqueIndex("entity_media_variant_level_unique")
|
|
51
|
+
.on(table.entityId, table.variantId, table.mediaAssetId)
|
|
52
|
+
.where(sql `${table.variantId} IS NOT NULL`),
|
|
53
|
+
}));
|
|
@@ -2,15 +2,18 @@ import type { Actor } from "../../auth/types.js";
|
|
|
2
2
|
import type { CommerceConfig } from "../../config/types.js";
|
|
3
3
|
import type { StorageAdapter, StoredFile } from "./adapter.js";
|
|
4
4
|
import { type Result } from "../../kernel/result.js";
|
|
5
|
-
import type { MediaRepository } from "./repository/index.js";
|
|
5
|
+
import type { MediaRepository, MediaAsset } from "./repository/index.js";
|
|
6
6
|
import type { CatalogRepository } from "../catalog/repository/index.js";
|
|
7
7
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
8
|
+
import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
|
|
9
|
+
import type { MediaAssetOrigin } from "./schema.js";
|
|
8
10
|
export interface UploadMediaInput {
|
|
9
11
|
filename: string;
|
|
10
12
|
contentType: string;
|
|
11
13
|
data: ArrayBuffer;
|
|
12
14
|
alt?: string;
|
|
13
15
|
metadata?: Record<string, unknown>;
|
|
16
|
+
origin?: MediaAssetOrigin;
|
|
14
17
|
}
|
|
15
18
|
export interface AttachMediaInput {
|
|
16
19
|
entityId: string;
|
|
@@ -33,13 +36,17 @@ interface MediaServiceDeps {
|
|
|
33
36
|
catalogRepository: CatalogRepository;
|
|
34
37
|
storage: StorageAdapter;
|
|
35
38
|
config: CommerceConfig;
|
|
39
|
+
database?: DatabaseAdapter;
|
|
40
|
+
services?: Record<string, unknown>;
|
|
36
41
|
}
|
|
42
|
+
export declare function assertMediaWritable(asset: Pick<MediaAsset, "origin">, actor: Actor | null): void;
|
|
37
43
|
export declare function detectMimeFromBuffer(buffer: Uint8Array): string | null;
|
|
38
44
|
export declare class MediaService {
|
|
39
45
|
private deps;
|
|
40
46
|
private readonly repo;
|
|
41
47
|
private readonly catalogRepo;
|
|
42
48
|
constructor(deps: MediaServiceDeps);
|
|
49
|
+
private withTransaction;
|
|
43
50
|
upload(input: UploadMediaInput, actor?: Actor | null, ctx?: TxContext): Promise<Result<{
|
|
44
51
|
id: string;
|
|
45
52
|
url: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/media/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/media/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
2
3
|
import { Err, Ok } from "../../kernel/result.js";
|
|
3
|
-
import { CommerceNotFoundError, CommerceValidationError, } from "../../kernel/errors.js";
|
|
4
|
+
import { CommerceConflictError, CommerceNotFoundError, CommerceValidationError, toCommerceError, } from "../../kernel/errors.js";
|
|
5
|
+
import { createTxContext } from "../../kernel/database/tx-context.js";
|
|
4
6
|
import { makeId } from "../../utils/id.js";
|
|
5
7
|
const DEFAULT_ALLOWED_MIME_TYPES = [
|
|
6
8
|
"image/png",
|
|
@@ -9,6 +11,18 @@ const DEFAULT_ALLOWED_MIME_TYPES = [
|
|
|
9
11
|
"image/webp",
|
|
10
12
|
"application/pdf",
|
|
11
13
|
];
|
|
14
|
+
export function assertMediaWritable(asset, actor) {
|
|
15
|
+
if (asset.origin === "merchant")
|
|
16
|
+
assertPermission(actor, "media:write");
|
|
17
|
+
}
|
|
18
|
+
function isUniqueViolation(error) {
|
|
19
|
+
if (error == null || typeof error !== "object")
|
|
20
|
+
return false;
|
|
21
|
+
const value = error;
|
|
22
|
+
if (value.code === "23505")
|
|
23
|
+
return true;
|
|
24
|
+
return isUniqueViolation(value.cause);
|
|
25
|
+
}
|
|
12
26
|
function isSvgOrXml(buffer) {
|
|
13
27
|
const prefix = new TextDecoder()
|
|
14
28
|
.decode(buffer.slice(0, 256))
|
|
@@ -46,11 +60,18 @@ export class MediaService {
|
|
|
46
60
|
this.repo = deps.repository;
|
|
47
61
|
this.catalogRepo = deps.catalogRepository;
|
|
48
62
|
}
|
|
63
|
+
async withTransaction(actor, ctx, fn) {
|
|
64
|
+
if (ctx?.tx != null)
|
|
65
|
+
return fn(ctx);
|
|
66
|
+
if (this.deps.database == null)
|
|
67
|
+
throw new Error("Database is required for media mutations.");
|
|
68
|
+
return this.deps.database.transaction(async (tx) => fn(createTxContext(tx, { actor })));
|
|
69
|
+
}
|
|
49
70
|
async upload(input, actor, ctx) {
|
|
50
71
|
if (!input.filename || !input.contentType) {
|
|
51
72
|
return Err(new CommerceValidationError("filename and contentType are required."));
|
|
52
73
|
}
|
|
53
|
-
const orgId =
|
|
74
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
54
75
|
const bytes = new Uint8Array(input.data);
|
|
55
76
|
const detectedMime = detectMimeFromBuffer(bytes);
|
|
56
77
|
const allowSvg = this.deps.config.media?.allowSvg === true;
|
|
@@ -78,6 +99,7 @@ export class MediaService {
|
|
|
78
99
|
contentType: effectiveMime,
|
|
79
100
|
size: input.data.byteLength,
|
|
80
101
|
metadata: input.metadata ?? {},
|
|
102
|
+
origin: input.origin ?? "merchant",
|
|
81
103
|
uploadedAt: new Date(),
|
|
82
104
|
...(input.alt !== undefined ? { alt: input.alt } : {}),
|
|
83
105
|
}, ctx);
|
|
@@ -94,24 +116,30 @@ export class MediaService {
|
|
|
94
116
|
// and delete additionally requires the actor to pass cross-tenant
|
|
95
117
|
// ownership (any actor without *:* must be in the asset's org).
|
|
96
118
|
async getUrl(id, actor, ctx) {
|
|
97
|
-
const orgId =
|
|
119
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
98
120
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
99
121
|
if (!asset)
|
|
100
122
|
return Err(new CommerceNotFoundError("Media asset not found."));
|
|
101
123
|
return this.deps.storage.getUrl(asset.storageKey);
|
|
102
124
|
}
|
|
103
125
|
async getSignedUrl(id, expiresIn = 60 * 15, actor, ctx) {
|
|
104
|
-
const orgId =
|
|
126
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
105
127
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
106
128
|
if (!asset)
|
|
107
129
|
return Err(new CommerceNotFoundError("Media asset not found."));
|
|
108
130
|
return this.deps.storage.getSignedUrl(asset.storageKey, expiresIn);
|
|
109
131
|
}
|
|
110
132
|
async delete(id, actor, ctx) {
|
|
111
|
-
const orgId =
|
|
133
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
112
134
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
113
135
|
if (!asset)
|
|
114
136
|
return Err(new CommerceNotFoundError("Media asset not found."));
|
|
137
|
+
try {
|
|
138
|
+
assertMediaWritable(asset, actor ?? ctx?.actor ?? null);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return Err(toCommerceError(error));
|
|
142
|
+
}
|
|
115
143
|
const deleted = await this.deps.storage.delete(asset.storageKey);
|
|
116
144
|
if (!deleted.ok)
|
|
117
145
|
return deleted;
|
|
@@ -126,25 +154,45 @@ export class MediaService {
|
|
|
126
154
|
return Ok(listed.value);
|
|
127
155
|
}
|
|
128
156
|
async attachToEntity(input, actor, ctx) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
try {
|
|
158
|
+
return await this.withTransaction(actor ?? ctx?.actor ?? null, ctx, async (txCtx) => {
|
|
159
|
+
const orgId = resolveOrgIdForCommerce(actor ?? txCtx.actor, this.deps.config);
|
|
160
|
+
const entity = await this.catalogRepo.findEntityById(input.entityId, txCtx, orgId);
|
|
161
|
+
if (!entity)
|
|
162
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
163
|
+
const asset = await this.repo.findAssetById(input.mediaAssetId, txCtx, orgId);
|
|
164
|
+
if (!asset)
|
|
165
|
+
return Err(new CommerceNotFoundError("Media asset not found."));
|
|
166
|
+
const existing = await this.repo.findEntityMedia(input.entityId, input.variantId, txCtx);
|
|
167
|
+
if (existing.some((link) => link.mediaAssetId === input.mediaAssetId)) {
|
|
168
|
+
return Err(new CommerceConflictError("Media asset is already attached at this level."));
|
|
169
|
+
}
|
|
170
|
+
await this.repo.createEntityMedia({
|
|
171
|
+
entityId: input.entityId,
|
|
172
|
+
mediaAssetId: input.mediaAssetId,
|
|
173
|
+
role: input.role,
|
|
174
|
+
sortOrder: input.sortOrder ?? 0,
|
|
175
|
+
...(input.variantId !== undefined
|
|
176
|
+
? { variantId: input.variantId }
|
|
177
|
+
: {}),
|
|
178
|
+
}, txCtx);
|
|
179
|
+
const catalog = this.deps.services?.catalog;
|
|
180
|
+
if (catalog == null || typeof catalog !== "object") {
|
|
181
|
+
throw new Error("Catalog service is required for media revision capture.");
|
|
182
|
+
}
|
|
183
|
+
const catalogService = catalog;
|
|
184
|
+
const revision = await catalogService.recordEntityRevision(input.entityId, actor ?? txCtx.actor, "update", txCtx);
|
|
185
|
+
if (!revision.ok)
|
|
186
|
+
throw revision.error;
|
|
187
|
+
return Ok(undefined);
|
|
188
|
+
});
|
|
133
189
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (isUniqueViolation(error)) {
|
|
192
|
+
return Err(new CommerceConflictError("Media asset is already attached at this level."));
|
|
193
|
+
}
|
|
194
|
+
return Err(toCommerceError(error));
|
|
137
195
|
}
|
|
138
|
-
await this.repo.createEntityMedia({
|
|
139
|
-
entityId: input.entityId,
|
|
140
|
-
mediaAssetId: input.mediaAssetId,
|
|
141
|
-
role: input.role,
|
|
142
|
-
sortOrder: input.sortOrder ?? 0,
|
|
143
|
-
...(input.variantId !== undefined
|
|
144
|
-
? { variantId: input.variantId }
|
|
145
|
-
: {}),
|
|
146
|
-
}, ctx);
|
|
147
|
-
return Ok(undefined);
|
|
148
196
|
}
|
|
149
197
|
/**
|
|
150
198
|
* Media attached to an entity, resolved to public URLs and ordered by
|