@aglyn/plugins-commerce 1.0.0-beta.233 → 1.0.0-beta.235
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/package.json +13 -13
- package/src/lib/components/cart.js +3 -2
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +3 -6
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/commerce-glance-card.component.js +9 -3
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -1
- package/src/lib/components/console/customer-notifications-card.component.js +3 -1
- package/src/lib/components/console/customer-notifications-card.component.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +2 -3
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +3 -4
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.js +2 -2
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
- package/src/lib/components/console/locations-card.component.js +1 -1
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +2 -2
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +35 -30
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-fulfillment-panel.component.d.ts +11 -1
- package/src/lib/components/console/order-fulfillment-panel.component.js +75 -4
- package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
- package/src/lib/components/console/order-zones.d.ts +45 -1
- package/src/lib/components/console/order-zones.js +8 -0
- package/src/lib/components/console/order-zones.js.map +1 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +15 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +3 -2
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.d.ts +2 -0
- package/src/lib/components/console/product-editor-dialog.component.js +30 -12
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +13 -4
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +2 -1
- package/src/lib/components/console/products-hub-zone.component.js +1 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/reviews-moderation-card.component.js +1 -2
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +2 -2
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
- package/src/lib/components/console/shipstation-card.component.js +1 -2
- package/src/lib/components/console/shipstation-card.component.js.map +1 -1
- package/src/lib/components/console/store-settings-card.component.js +3 -1
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/order-status.js +24 -1
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +30 -16
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +7 -0
- package/src/lib/components/product-grid.js +50 -7
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/components/related-products.js +1 -1
- package/src/lib/components/related-products.js.map +1 -1
- package/src/lib/components/wishlist.js +1 -1
- package/src/lib/components/wishlist.js.map +1 -1
- package/src/lib/declarations.server.js +11 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +8 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce.d.ts +21 -0
- package/src/lib/model/commerce.js +39 -1
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/order-annotations.d.ts +54 -0
- package/src/lib/model/order-annotations.js +76 -0
- package/src/lib/model/order-annotations.js.map +1 -0
- package/src/lib/model/order-events.d.ts +2 -0
- package/src/lib/model/order-events.js.map +1 -1
- package/src/lib/model/order-fulfillment.d.ts +29 -0
- package/src/lib/model/order-fulfillment.js +63 -0
- package/src/lib/model/order-fulfillment.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +36 -0
- package/src/lib/model/order-local-fulfillment.js +19 -1
- package/src/lib/model/order-local-fulfillment.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +11 -0
- package/src/lib/model/order-status-view.js +20 -3
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/model/product-write.js +8 -4
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/server/api-v1/openapi.js +1 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +1 -0
- package/src/lib/server/api-v1/order-view.js +10 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/cart-checkout.js +9 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +3 -1
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout.js +11 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +12 -0
- package/src/lib/server/fulfill-order.js +13 -0
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/local-delivery-records.d.ts +30 -0
- package/src/lib/server/local-delivery-records.js +284 -0
- package/src/lib/server/local-delivery-records.js.map +1 -0
- package/src/lib/server/order-annotations.d.ts +28 -0
- package/src/lib/server/order-annotations.js +143 -0
- package/src/lib/server/order-annotations.js.map +1 -0
- package/src/lib/server/order-events.js +2 -1
- package/src/lib/server/order-events.js.map +1 -1
- package/src/lib/server/order-notifications.js +39 -8
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-payment.js +9 -3
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/product-catalog.js +6 -2
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product-drafts.d.ts +36 -9
- package/src/lib/server/product-drafts.js +24 -5
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product.d.ts +6 -1
- package/src/lib/server/product.js +1 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.d.ts +29 -0
- package/src/lib/server/products-write.js +7 -7
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/shipment-records.js +2 -0
- package/src/lib/server/shipment-records.js.map +1 -1
- package/src/lib/server/site-page-resolver.d.ts +13 -0
- package/src/lib/server/site-page-resolver.js +138 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +5 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/utils/use-storefront-purchase-event.js +8 -2
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -1
|
@@ -180,7 +180,9 @@ const compareValues = (a, b)=>{
|
|
|
180
180
|
soldOut: CommerceModel.productStockFields(product).soldOut
|
|
181
181
|
}, ((_product_tags = product.tags) == null ? void 0 : _product_tags.length) ? {
|
|
182
182
|
tags: product.tags
|
|
183
|
-
} : {})
|
|
183
|
+
} : {}, product.variants.some((variant)=>CommerceModel.variantHasPrice(variant)) ? {} : {
|
|
184
|
+
priceComingSoon: true
|
|
185
|
+
});
|
|
184
186
|
}
|
|
185
187
|
/**
|
|
186
188
|
* The plan's refusals, as the plan made them; the grid words each one. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/catalog.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { nameSearchNormalizers } from '@aglyn/aglyn/app-utils/name-search'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n type ListQueryFilter,\n type ListQueryPlan,\n type ListQueryRefusal,\n type ListQueryRequest,\n type ListQuerySort,\n planListQuery,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { applyListQuery } from '@aglyn/tenant-data-admin/server/list-query'\nimport {\n smartCollectionScope,\n STOREFRONT_CATALOG_BASE,\n STOREFRONT_CATALOG_DECLARATION,\n STOREFRONT_CATALOG_SORTS,\n STOREFRONT_PRODUCT_TYPES,\n storefrontCatalogSort,\n} from '../constants/storefront-catalog-query'\nimport * as CommerceModel from '../model'\n\nexport interface PublicCatalogItem {\n id: string\n name: string\n slug: string\n type: CommerceModel.ProductType\n priceUsd: number\n maxPriceUsd: number\n compareAtPriceUsd?: number\n imageUrl?: string\n soldOut: boolean\n tags?: string[]\n}\n\n/** Host category surfaced to grids for filter chips (AGL-561). */\nexport interface PublicCatalogCategory {\n id: string\n name: string\n slug: string\n}\n\n/** Price facet surfaced to grids to bound a range slider (AGL-564). */\nexport interface PublicCatalogPriceBounds {\n minCents: number\n maxCents: number\n}\n\n/**\n * A control the query could not apply, and why — the plan's own refusal,\n * which the grid names through `listQueryRefusals` with the declaration's\n * fields and the categories it shows.\n */\nexport type PublicCatalogRefusal = ListQueryRefusal\n\n/** Non-negative integer cents from a query param; undefined otherwise. */\nfunction parsePriceCents(value: unknown): number | undefined {\n const raw = String(value ?? '').trim()\n if (!raw) return undefined\n const cents = Number(raw)\n if (!Number.isFinite(cents) || cents < 0) return undefined\n return Math.round(cents)\n}\n\n/** A catalog query, already parsed and bounded. */\nexport interface PublicCatalogQuery {\n hostId: string\n collectionId?: string\n collectionSlug?: string\n categoryId?: string\n categorySlug?: string\n tag?: string\n /** Search: the first word, matched as a word prefix of the product name. */\n query?: string\n type?: CommerceModel.ProductType | ''\n /** The In stock chip: only products a card would not mark sold out. */\n inStock?: boolean\n /** Explicit id list in the given order (wishlist rendering, AGL-297). */\n ids?: string[]\n minPriceCents?: number\n maxPriceCents?: number\n sort?: string\n /** Page size, 1–100. */\n limit?: number\n /** The `nextCursor` a previous page returned — continue after it. */\n after?: string\n /** Include category + price facets in the result. */\n facets?: boolean\n /**\n * Optional per-render dedupe (see {@link CatalogReadScope}). Absent for a\n * one-off call such as the API route, where there is nothing to share with.\n */\n reads?: CatalogReadScope\n}\n\n/**\n * Reads shared by every catalog query in ONE render, for ONE host.\n *\n * Two grids on one page asking the SAME question share one answer, and every\n * grid that shows category chips shares one read of the taxonomy. A grid\n * asking a different question — another collection, another sort — runs its\n * own query, because each query now reads only the page it returns rather\n * than the whole catalog (AGL-3321).\n *\n * Scoped to one host and one render deliberately: it holds promises, not\n * data, so nothing is cached across requests and no staleness window opens.\n * Reusing a scope for a second host would serve that host the first one's\n * catalog, which is why it is created per render rather than per module.\n */\nexport interface CatalogReadScope {\n categories?: Promise<FirebaseFirestore.QuerySnapshot>\n results?: Map<string, Promise<PublicCatalogResult>>\n}\n\n/** A fresh read scope. One per render, never reused across hosts. */\nexport function createCatalogReadScope(): CatalogReadScope {\n return {}\n}\n\n/** What both the API route and the SSR seed hand to a product grid. */\nexport interface PublicCatalogResult {\n items: PublicCatalogItem[]\n /** Pass back as `after` for the next page; absent on the last one. */\n nextCursor?: string\n categories?: PublicCatalogCategory[]\n priceBounds?: PublicCatalogPriceBounds\n /** Controls in force that the query could not apply (AGL-3321). */\n refused?: PublicCatalogRefusal[]\n /** Said about what WAS applied, e.g. that search reads one word. */\n notices?: string[]\n}\n\n/** Most products one page returns. */\nconst CATALOG_PAGE_MAX = 100\n/** Page size when a grid names none. */\nconst CATALOG_PAGE_DEFAULT = 24\n/**\n * Members of a MANUAL collection read by id. A hand-curated list is its own\n * complete set, so it is read whole and every visitor control is answered over\n * all of it rather than over a window of the catalog.\n */\nconst MANUAL_COLLECTION_MAX = 1000\n/** Ids per `getAll`, well inside the Admin SDK's comfortable batch. */\nconst GET_ALL_CHUNK = 100\n\n/** One product as the catalog reads it: stored, lifted, and keyed. */\ninterface CatalogRow {\n id: string\n product: CommerceModel.HostProduct\n /**\n * The fields the catalog query reads, as the writers stamp them — derived\n * through the same library functions the writers spread, so a held row\n * (a manual collection's member, an id list's entry) answers a predicate\n * exactly as the stored document will once it is backfilled.\n */\n values: Record<string, unknown>\n}\n\nfunction toRow(docSnapshot: FirebaseFirestore.DocumentSnapshot): CatalogRow | null {\n const raw = docSnapshot.data() as Record<string, unknown> | undefined\n if (!raw) return null\n const product = CommerceModel.liftLegacyProduct(raw as never)\n return {\n id: docSnapshot.id,\n product,\n values: {\n ...raw,\n status: product.status,\n type: product.type,\n deletedAt: raw['deletedAt'] ?? null,\n ...CommerceModel.productSearchFields({\n name: String(product.name ?? ''),\n variants: product.variants,\n }),\n ...CommerceModel.productStockFields(product),\n },\n }\n}\n\n/** A row's value at a queried path, as the query sees it. */\nfunction rowValue(row: CatalogRow, path: string): unknown {\n return row.values[path]\n}\n\n/** A held row a shopper may be shown: live and active, as the query's scope. */\nconst onSale = (row: CatalogRow) =>\n STOREFRONT_CATALOG_BASE.every((filter) => rowMatches(row, filter))\n\n/** One plan predicate over one row, with Firestore's meaning of each operator. */\nfunction rowMatches(row: CatalogRow, filter: ListQueryFilter): boolean {\n const actual = rowValue(row, filter.path)\n if (actual === undefined) return false\n const value = filter.value\n const list = Array.isArray(value) ? (value as readonly unknown[]) : [value]\n switch (filter.op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== null && actual !== value\n case 'in':\n return list.includes(actual)\n case 'array-contains':\n return Array.isArray(actual) && actual.includes(value)\n case 'array-contains-any':\n return Array.isArray(actual) && actual.some((member) => list.includes(member))\n case '<':\n return typeof actual === typeof value && (actual as number) < (value as number)\n case '<=':\n return typeof actual === typeof value && (actual as number) <= (value as number)\n case '>':\n return typeof actual === typeof value && (actual as number) > (value as number)\n case '>=':\n return typeof actual === typeof value && (actual as number) >= (value as number)\n default:\n return false\n }\n}\n\n/** A cursor: the last row's order value and id, opaque to the grid. */\nfunction encodeCursor(value: unknown, id: string): string {\n return Buffer.from(JSON.stringify([value ?? null, id])).toString('base64url')\n}\n\nfunction decodeCursor(raw: string | undefined): { value: unknown; id: string } | null {\n if (!raw) return null\n try {\n const parsed = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'))\n if (!Array.isArray(parsed) || parsed.length !== 2) return null\n const [value, id] = parsed\n if (typeof id !== 'string' || !id) return null\n if (value !== null && typeof value !== 'string' && typeof value !== 'number') return null\n return { value, id }\n } catch {\n return null\n }\n}\n\nconst compareValues = (a: unknown, b: unknown): number => {\n if (a === b) return 0\n // A row without the ordered value sorts after every row with one.\n if (a === undefined || a === null) return 1\n if (b === undefined || b === null) return -1\n return a < b ? -1 : a > b ? 1 : 0\n}\n\n/**\n * One page of rows already held WHOLE — a manual collection, an id list, a\n * smart collection's scan — ordered by `key` then id, after the cursor.\n */\nfunction pageHeldRows(\n rows: readonly CatalogRow[],\n key: (row: CatalogRow) => unknown,\n direction: 'asc' | 'desc',\n after: string | undefined,\n max: number,\n): { rows: CatalogRow[]; nextCursor?: string } {\n const signed = (value: number) => (direction === 'desc' ? -value : value)\n const ordered = [...rows].sort(\n (a, b) =>\n signed(compareValues(key(a), key(b))) || signed(compareValues(a.id, b.id)),\n )\n const cursor = decodeCursor(after)\n const start = cursor\n ? ordered.findIndex(\n (row) =>\n signed(compareValues(key(row), cursor.value)) > 0 ||\n (compareValues(key(row), cursor.value) === 0 &&\n signed(compareValues(row.id, cursor.id)) > 0),\n )\n : 0\n if (start === -1) return { rows: [] }\n const page = ordered.slice(start, start + max)\n const last = page[page.length - 1]\n return {\n rows: page,\n ...(last && start + max < ordered.length\n ? { nextCursor: encodeCursor(key(last), last.id) }\n : {}),\n }\n}\n\n/** Documents by id, in chunks, dropping the ones that do not exist. */\nasync function readProductsById(\n productsRef: FirebaseFirestore.CollectionReference,\n ids: readonly string[],\n): Promise<CatalogRow[]> {\n const firestore = firebaseAdmin.app().firestore()\n const rows: CatalogRow[] = []\n for (let start = 0; start < ids.length; start += GET_ALL_CHUNK) {\n const refs = ids.slice(start, start + GET_ALL_CHUNK).map((id) => productsRef.doc(id))\n if (!refs.length) continue\n const snapshots = await firestore.getAll(...refs)\n for (const snapshot of snapshots) {\n if (!snapshot.exists) continue\n const row = toRow(snapshot)\n if (row) rows.push(row)\n }\n }\n return rows\n}\n\n/** The card a grid renders for one product. */\nfunction toItem(row: CatalogRow): PublicCatalogItem {\n const { product } = row\n const [minPrice, maxPrice] = CommerceModel.productPriceRange(product)\n const primary = product.variants[0]\n return {\n id: row.id,\n name: product.name,\n slug: product.slug,\n type: product.type,\n priceUsd: minPrice,\n maxPriceUsd: maxPrice,\n ...(primary?.compareAtPriceUsd\n ? { compareAtPriceUsd: primary.compareAtPriceUsd }\n : {}),\n ...(product.mediaUrls?.[0] || product.imageUrl\n ? { imageUrl: product.mediaUrls?.[0] ?? product.imageUrl }\n : {}),\n // The same verdict the In stock chip queries (`soldOut`), from the one\n // function every stock writer stores it through.\n soldOut: CommerceModel.productStockFields(product).soldOut,\n ...(product.tags?.length ? { tags: product.tags } : {}),\n }\n}\n\n/**\n * The plan's refusals, as the plan made them; the grid words each one. A\n * search refused because a smart collection already holds the query's one\n * array clause gets a reason a shopper can read.\n */\nfunction refusalsOf(plan: ListQueryPlan): PublicCatalogRefusal[] {\n return plan.refused.map((refusal) =>\n refusal.clause === 'search' && refusal.reason.startsWith('this list is already narrowed')\n ? {\n ...refusal,\n reason: 'this collection is already narrowed by a tag or category, which search cannot combine with',\n }\n : refusal,\n )\n}\n\n/** How a sort reads in the grid's select, for the notice below. */\nconst SORT_LABELS: Readonly<Record<string, string>> = {\n nameLower: 'Name',\n createdAtMs: 'Newest',\n}\n\n/** The operators that make a field a range, which must lead the order. */\nconst RANGE_OPS: ReadonlySet<string> = new Set(['<', '<=', '>', '>=', '!='])\n\n/**\n * The plan, ordered the way the visitor asked when a range forced its field.\n *\n * A range leads the order, so while the price slider (or a smart collection's\n * \"price under\" rule) is set the query is ordered by price: \"high to low\"\n * stays high to low, and any other sort — Name, Newest — becomes low to high\n * and is SAID to, rather than silently replaced (AGL-3321).\n *\n * The notice names the cause. The slider is the visitor's to clear, so the\n * sort they chose returns when they do; a collection's rule is in the scope\n * (`request.base`), which no visitor can clear, so that notice says the\n * collection is always ordered by price.\n */\nfunction planFor(request: ListQueryRequest, sort: ListQuerySort): ListQueryPlan {\n const plan = planListQuery(STOREFRONT_CATALOG_DECLARATION, request, nameSearchNormalizers)\n if (plan.orderBy.path !== sort.path) {\n const asked = SORT_LABELS[sort.path]\n if (!asked) return plan\n const byRule = (request.base ?? []).some(\n (filter) => filter.path === plan.orderBy.path && RANGE_OPS.has(filter.op),\n )\n return {\n ...plan,\n notices: [\n ...plan.notices,\n byRule\n ? `Sorted by price, low to high — this collection picks its products by price, so it cannot be sorted by ${asked}.`\n : `Sorted by price, low to high, while a price range is set — ${asked} applies again when it is cleared.`,\n ],\n }\n }\n return plan.orderBy.direction !== sort.direction ? { ...plan, orderBy: sort } : plan\n}\n\n/** A price bound as the grid's slider shows it, for a refusal's sentence. */\nconst dollars = (cents: number) => `$${(cents / 100).toFixed(cents % 100 ? 2 : 0)}`\n\n/**\n * Public catalog listing (AGL-291): active products for storefront blocks —\n * a collection, a category or a tag, searched, filtered by type and price,\n * sorted, and paged by cursor (AGL-561, AGL-564).\n *\n * EVERY CONTROL IS ON THE QUERY (AGL-3321). This used to read `limit(500)`\n * products and narrow them in memory, so a catalog past five hundred answered\n * every search, chip and slider from an arbitrary five hundred of them. Now:\n *\n * - the catalog, a category or a tag — ONE query built by the list query\n * plan (`STOREFRONT_CATALOG_DECLARATION`), paged by its own cursor;\n * - a SMART collection whose rules a query can hold — the same query, with\n * the rules as its scope (`smartCollectionScope`);\n * - a MANUAL collection, or an explicit id list — read whole by id, which\n * is a complete set, and answered over all of it;\n * - a smart collection whose rules no query can hold (a NOT, an OR, a name\n * rule, a price over) — the same query, scoped by the membership every\n * product writer stores (`collectionIds array-contains <id>`), which the\n * collection-membership route re-stamps whenever the rules change.\n *\n * What one query cannot hold — two array clauses, two ranges — is refused by\n * name in `refused` and not applied, never applied to some rows and not\n * others. Read-only public data; prices here are display-only (charges\n * always come from the docs server-side).\n *\n * Extracted from the handler (AGL-659) so the site-page enricher seeds a\n * grid's first page through THIS function rather than a reimplementation\n * of it. A seed that filters or sorts differently from the API would\n * server-render one set of products and hydrate to another.\n */\nexport async function queryPublicCatalog(\n params: PublicCatalogQuery,\n): Promise<PublicCatalogResult> {\n const { reads, ...question } = params\n if (!reads) return runPublicCatalog(question)\n const key = JSON.stringify(question)\n reads.results ??= new Map()\n const shared = reads.results.get(key)\n if (shared) return shared\n // Stored BEFORE it is awaited, so grids running together under one\n // `Promise.all` share the one in-flight read rather than racing to store it.\n const pending = runPublicCatalog(question, reads)\n reads.results.set(key, pending)\n return pending\n}\n\nasync function runPublicCatalog(\n params: Omit<PublicCatalogQuery, 'reads'>,\n reads?: CatalogReadScope,\n): Promise<PublicCatalogResult> {\n const { hostId } = params\n const collectionSlug = params.collectionSlug ?? ''\n const categorySlug = params.categorySlug ?? ''\n // Id-based filters are preferred (rename-safe, AGL-343); slugs remain\n // for legacy screens and template-URL resolution.\n const collectionIdParam = params.collectionId ?? ''\n const categoryIdParam = params.categoryId ?? ''\n const tag = (params.tag ?? '').trim()\n const query = (params.query ?? '').trim()\n const type = STOREFRONT_PRODUCT_TYPES.includes(params.type as CommerceModel.ProductType)\n ? (params.type as CommerceModel.ProductType)\n : ''\n const ids = (params.ids ?? []).slice(0, CATALOG_PAGE_MAX)\n const asked = Math.floor(Number(params.limit ?? CATALOG_PAGE_DEFAULT))\n const max =\n Number.isFinite(asked) && asked > 0\n ? Math.min(CATALOG_PAGE_MAX, asked)\n : CATALOG_PAGE_DEFAULT\n const sort = storefrontCatalogSort(params.sort)\n const wantFacets = Boolean(params.facets)\n\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const productsRef = hostRef.collection('products')\n const sharedCategories = () =>\n (reads\n ? (reads.categories ??=\n hostRef.collection('productCategories').limit(200).get())\n : hostRef.collection('productCategories').limit(200).get())\n\n // Wishlist and related rendering (AGL-297): an explicit id list is its own\n // complete set — read it by id, keep what is on sale, in the given order.\n if (ids.length) {\n const order = new Map(ids.map((id, index) => [id, index]))\n const rows = (await readProductsById(productsRef, ids)).filter(onSale)\n rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))\n return { items: rows.slice(0, max).map(toItem) }\n }\n\n const [collectionsSnapshot, categoriesSnapshot, collectionByIdSnapshot, facetsSnapshot] =\n await Promise.all([\n // limit(5) not 1: content collections share this path and a slug is\n // unique only within a kind (AGL-954) — the catalog match is picked\n // out below rather than trusting whichever doc came back first.\n !collectionIdParam && collectionSlug\n ? hostRef\n .collection('collections')\n .where('slug', '==', collectionSlug)\n .limit(5)\n .get()\n : null,\n !categoryIdParam && categorySlug\n ? hostRef\n .collection('productCategories')\n .where('slug', '==', categorySlug)\n .limit(1)\n .get()\n : null,\n collectionIdParam\n ? hostRef.collection('collections').doc(collectionIdParam).get()\n : null,\n wantFacets ? sharedCategories() : null,\n ])\n const collectionById =\n collectionByIdSnapshot?.exists &&\n Aglyn.hostCollectionKind(collectionByIdSnapshot.data()) === 'catalog'\n ? collectionByIdSnapshot\n : undefined\n const collectionDoc =\n collectionById ??\n collectionsSnapshot?.docs.find(\n (docSnapshot) => Aglyn.hostCollectionKind(docSnapshot.data()) === 'catalog',\n )\n const collection = collectionDoc\n ? (collectionDoc.data() as CommerceModel.HostCollection)\n : undefined\n const categoryId = categoryIdParam || categoriesSnapshot?.docs[0]?.id || ''\n\n // Category facets (AGL-561): id/name/slug for grid filter chips,\n // parent-tree order preserved (order, then name). The taxonomy is a\n // bounded picker, read whole.\n const categories: PublicCatalogCategory[] | undefined = facetsSnapshot\n ? facetsSnapshot.docs\n .map((docSnapshot) => {\n const data = docSnapshot.data() as CommerceModel.ProductCategory\n return {\n id: docSnapshot.id,\n name: String(data.name ?? ''),\n slug: String(data.slug ?? ''),\n order: Number(data.order ?? 0),\n }\n })\n .sort((a, b) => a.order - b.order || a.name.localeCompare(b.name))\n .map(({ id, name, slug }) => ({ id, name, slug }))\n : undefined\n\n // The visitor's controls, as clauses on the one declaration. A price\n // clause carries the dollars the slider showed, for a refusal's sentence.\n const priceClauses: ListFilterClause[] = [\n ...(params.minPriceCents != null\n ? [{\n field: 'price',\n op: '>=',\n value: String(params.minPriceCents),\n label: dollars(params.minPriceCents),\n }]\n : []),\n ...(params.maxPriceCents != null\n ? [{\n field: 'price',\n op: '<=',\n value: String(params.maxPriceCents),\n label: dollars(params.maxPriceCents),\n }]\n : []),\n ]\n const otherClauses: ListFilterClause[] = [\n ...(categoryId ? [{ field: 'category', op: 'contains', value: categoryId }] : []),\n ...(tag ? [{ field: 'tag', op: 'contains', value: tag }] : []),\n ...(type ? [{ field: 'type', op: 'equals', value: type }] : []),\n ...(params.inStock ? [{ field: 'stock', op: 'is', value: 'false' }] : []),\n ]\n // The words typed, as a quick search hands them over: the plan searches by\n // the first and says so when there were more.\n const search = query.split(/\\s+/).filter(Boolean)\n\n const result = (\n rows: CatalogRow[],\n plan: ListQueryPlan,\n extra: Partial<PublicCatalogResult> = {},\n ): PublicCatalogResult => {\n const refused = refusalsOf(plan)\n const notices = [...plan.notices, ...(extra.notices ?? [])]\n return {\n items: rows.map(toItem),\n ...(extra.nextCursor ? { nextCursor: extra.nextCursor } : {}),\n ...(categories ? { categories } : {}),\n ...(extra.priceBounds ? { priceBounds: extra.priceBounds } : {}),\n ...(refused.length ? { refused } : {}),\n ...(notices.length ? { notices } : {}),\n }\n }\n\n /*\n * A collection held WHOLE: a manual one's members, or a smart one whose\n * rules no query can hold. The plan still decides what is applied — the\n * same refusals as the query path, so a grid behaves the same whatever it\n * lists — and its predicates are answered over every held row.\n */\n const answerHeld = (\n held: CatalogRow[],\n options: { manualOrder?: Map<string, number>; notices?: string[] },\n ): PublicCatalogResult => {\n const request: ListQueryRequest = {\n base: STOREFRONT_CATALOG_BASE,\n clauses: [...otherClauses, ...priceClauses],\n search,\n sort,\n }\n const plan = planFor(request, sort)\n const unpriced = planFor({ ...request, clauses: otherClauses }, sort)\n const matched = held.filter((row) => plan.filters.every((filter) => rowMatches(row, filter)))\n // Bounds span every OTHER control, so the slider stays anchored while the\n // visitor narrows it.\n const bounding = held.filter((row) =>\n unpriced.filters.every((filter) => rowMatches(row, filter)),\n )\n const priceBounds =\n wantFacets && bounding.length\n ? {\n minCents: Math.min(...bounding.map((row) => Number(rowValue(row, 'priceFromCents')))),\n maxCents: Math.max(...bounding.map((row) => Number(rowValue(row, 'priceFromCents')))),\n }\n : undefined\n // A manual collection keeps the merchant's order unless the visitor (or\n // the author) chose a price or recency sort.\n const keepManual =\n options.manualOrder && plan.orderBy.path === STOREFRONT_CATALOG_SORTS.name.path\n const page = keepManual\n ? pageHeldRows(\n matched,\n (row) => options.manualOrder?.get(row.id) ?? Number.MAX_SAFE_INTEGER,\n 'asc',\n params.after,\n max,\n )\n : pageHeldRows(\n matched,\n (row) => rowValue(row, plan.orderBy.path),\n plan.orderBy.direction,\n params.after,\n max,\n )\n return result(page.rows, plan, {\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n ...(priceBounds ? { priceBounds } : {}),\n ...(options.notices ? { notices: options.notices } : {}),\n })\n }\n\n let scope: ListQueryFilter[] = []\n if (collection) {\n if (collection.mode === 'manual') {\n const memberIds = (collection.productIds ?? []).slice(0, MANUAL_COLLECTION_MAX)\n const manualOrder = new Map(memberIds.map((id, index) => [id, index]))\n return answerHeld(await readProductsById(productsRef, memberIds), { manualOrder })\n }\n const smart = smartCollectionScope(collection)\n if (!(collection.rules ?? []).length) {\n // A smart collection with no rules holds nothing (`matchesCollection`).\n return result([], planFor({ base: STOREFRONT_CATALOG_BASE, clauses: [], sort }, sort))\n }\n if ('unservable' in smart) {\n // Rules no query can express (a NOT, an OR, a name rule, a price over)\n // are answered by the membership every product writer stores\n // (`collectionIds`, AGL-3321): one array clause on the same query, so\n // the grid's other array controls are refused by name beside it.\n scope = [\n {\n path: CommerceModel.PRODUCT_COLLECTION_IDS,\n op: 'array-contains',\n value: String(collectionDoc?.id ?? ''),\n },\n ]\n } else {\n scope = smart.filters\n // A \"type is not\" rule is `type in […]`; the visitor's Type chip is the\n // narrower question, answered inside it or not at all.\n const typeRule = scope.find((filter) => filter.path === 'type')\n if (typeRule && type) {\n const allowed = Array.isArray(typeRule.value) ? typeRule.value : [typeRule.value]\n if (!allowed.includes(type)) {\n return result([], planFor({ base: STOREFRONT_CATALOG_BASE, clauses: [], sort }, sort))\n }\n scope = scope.filter((filter) => filter !== typeRule)\n }\n }\n }\n\n // THE QUERY: every predicate, one order, a page and one row to spare.\n const request: ListQueryRequest = {\n base: [...STOREFRONT_CATALOG_BASE, ...scope],\n clauses: [...otherClauses, ...priceClauses],\n search,\n sort,\n }\n const plan = planFor(request, sort)\n const byId = firebaseAdmin.firestore.FieldPath.documentId()\n const ordered = (target: ListQueryPlan) =>\n applyListQuery(productsRef, target).orderBy(byId, target.orderBy.direction)\n let pageQuery = ordered(plan)\n const cursor = decodeCursor(params.after)\n if (cursor) pageQuery = pageQuery.startAfter(cursor.value, cursor.id)\n\n // Bounds span every OTHER control, so the slider stays anchored while the\n // visitor narrows it: the lowest and highest price the rest allows, as two\n // one-row reads ordered by price.\n const byPrice = STOREFRONT_CATALOG_SORTS['price-asc']\n const unpriced = wantFacets\n ? planFor({ ...request, clauses: otherClauses, sort: byPrice }, byPrice)\n : null\n const pricedBy = (target: ListQueryPlan, direction: 'asc' | 'desc') =>\n ordered({ ...target, orderBy: { path: 'priceFromCents', direction } }).limit(1).get()\n\n const [snapshot, lowest, highest] = await Promise.all([\n pageQuery.limit(max + 1).get(),\n unpriced ? pricedBy(unpriced, 'asc') : null,\n unpriced ? pricedBy(unpriced, 'desc') : null,\n ])\n const docs = snapshot.docs.slice(0, max)\n const rows = docs.map(toRow).filter((row): row is CatalogRow => Boolean(row))\n const last = docs[docs.length - 1]\n const low = lowest?.docs[0]?.get('priceFromCents')\n const high = highest?.docs[0]?.get('priceFromCents')\n return result(rows, plan, {\n ...(last && snapshot.docs.length > max\n ? { nextCursor: encodeCursor(last.get(plan.orderBy.path), last.id) }\n : {}),\n ...(typeof low === 'number' && typeof high === 'number'\n ? { priceBounds: { minCents: low, maxCents: high } }\n : {}),\n })\n}\n\n/** Public catalog listing endpoint — a thin parse over queryPublicCatalog. */\nexport const catalogHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n\n try {\n const result = await queryPublicCatalog({\n hostId,\n collectionSlug: String(req.query.collection ?? ''),\n categorySlug: String(req.query.category ?? ''),\n collectionId: String(req.query.collectionId ?? ''),\n categoryId: String(req.query.categoryId ?? ''),\n tag: String(req.query.tag ?? ''),\n query: String(req.query.q ?? ''),\n type: String(req.query.type ?? '') as CommerceModel.ProductType | '',\n inStock: String(req.query.inStock ?? '') === '1',\n ids: String(req.query.ids ?? '')\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean),\n // Price-range filter (AGL-564): integer cents on the displayed price.\n minPriceCents: parsePriceCents(req.query.minPriceCents),\n maxPriceCents: parsePriceCents(req.query.maxPriceCents),\n sort: String(req.query.sort ?? 'name'),\n limit: Number(req.query.limit ?? CATALOG_PAGE_DEFAULT),\n after: String(req.query.after ?? ''),\n facets: String(req.query.facets ?? '') === '1',\n })\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=60, stale-while-revalidate=300',\n )\n return res.status(200).json(result)\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Catalog unavailable' })\n }\n}\n"],"names":["nameSearchNormalizers","Aglyn","planListQuery","firebaseAdmin","applyListQuery","smartCollectionScope","STOREFRONT_CATALOG_BASE","STOREFRONT_CATALOG_DECLARATION","STOREFRONT_CATALOG_SORTS","STOREFRONT_PRODUCT_TYPES","storefrontCatalogSort","CommerceModel","parsePriceCents","value","raw","String","trim","undefined","cents","Number","isFinite","Math","round","createCatalogReadScope","CATALOG_PAGE_MAX","CATALOG_PAGE_DEFAULT","MANUAL_COLLECTION_MAX","GET_ALL_CHUNK","toRow","docSnapshot","product","data","liftLegacyProduct","id","values","status","type","deletedAt","productSearchFields","name","variants","productStockFields","rowValue","row","path","onSale","every","filter","rowMatches","actual","list","Array","isArray","op","includes","some","member","encodeCursor","Buffer","from","JSON","stringify","toString","decodeCursor","parsed","parse","length","compareValues","a","b","pageHeldRows","rows","key","direction","after","max","signed","ordered","sort","cursor","start","findIndex","page","slice","last","nextCursor","readProductsById","productsRef","ids","firestore","app","refs","map","doc","snapshots","getAll","snapshot","exists","push","toItem","minPrice","maxPrice","productPriceRange","primary","slug","priceUsd","maxPriceUsd","compareAtPriceUsd","mediaUrls","imageUrl","soldOut","tags","refusalsOf","plan","refused","refusal","clause","reason","startsWith","SORT_LABELS","nameLower","createdAtMs","RANGE_OPS","Set","planFor","request","orderBy","asked","byRule","base","has","notices","dollars","toFixed","queryPublicCatalog","params","reads","question","runPublicCatalog","results","Map","shared","get","pending","set","categoriesSnapshot","lowest","highest","hostId","collectionSlug","categorySlug","collectionIdParam","collectionId","categoryIdParam","categoryId","tag","query","floor","limit","min","wantFacets","Boolean","facets","hostRef","collection","sharedCategories","categories","order","index","items","collectionsSnapshot","collectionByIdSnapshot","facetsSnapshot","Promise","all","where","collectionById","hostCollectionKind","collectionDoc","docs","find","localeCompare","priceClauses","minPriceCents","field","label","maxPriceCents","otherClauses","inStock","search","split","result","extra","priceBounds","answerHeld","held","options","clauses","unpriced","matched","filters","bounding","minCents","maxCents","keepManual","manualOrder","MAX_SAFE_INTEGER","scope","mode","memberIds","productIds","smart","rules","PRODUCT_COLLECTION_IDS","typeRule","allowed","byId","FieldPath","documentId","target","pageQuery","startAfter","byPrice","pricedBy","low","high","catalogHandler","req","res","json","error","category","q","setHeader","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/catalog.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { nameSearchNormalizers } from '@aglyn/aglyn/app-utils/name-search'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n type ListQueryFilter,\n type ListQueryPlan,\n type ListQueryRefusal,\n type ListQueryRequest,\n type ListQuerySort,\n planListQuery,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { applyListQuery } from '@aglyn/tenant-data-admin/server/list-query'\nimport {\n smartCollectionScope,\n STOREFRONT_CATALOG_BASE,\n STOREFRONT_CATALOG_DECLARATION,\n STOREFRONT_CATALOG_SORTS,\n STOREFRONT_PRODUCT_TYPES,\n storefrontCatalogSort,\n} from '../constants/storefront-catalog-query'\nimport * as CommerceModel from '../model'\n\nexport interface PublicCatalogItem {\n id: string\n name: string\n slug: string\n type: CommerceModel.ProductType\n priceUsd: number\n maxPriceUsd: number\n compareAtPriceUsd?: number\n imageUrl?: string\n soldOut: boolean\n tags?: string[]\n /**\n * Listed before any variant has a price (AGL-3676): the card says \"Price\n * coming soon\" instead of a price, and nothing sells it until one is set.\n * Absent for every product with a price.\n */\n priceComingSoon?: true\n}\n\n/** Host category surfaced to grids for filter chips (AGL-561). */\nexport interface PublicCatalogCategory {\n id: string\n name: string\n slug: string\n}\n\n/** Price facet surfaced to grids to bound a range slider (AGL-564). */\nexport interface PublicCatalogPriceBounds {\n minCents: number\n maxCents: number\n}\n\n/**\n * A control the query could not apply, and why — the plan's own refusal,\n * which the grid names through `listQueryRefusals` with the declaration's\n * fields and the categories it shows.\n */\nexport type PublicCatalogRefusal = ListQueryRefusal\n\n/** Non-negative integer cents from a query param; undefined otherwise. */\nfunction parsePriceCents(value: unknown): number | undefined {\n const raw = String(value ?? '').trim()\n if (!raw) return undefined\n const cents = Number(raw)\n if (!Number.isFinite(cents) || cents < 0) return undefined\n return Math.round(cents)\n}\n\n/** A catalog query, already parsed and bounded. */\nexport interface PublicCatalogQuery {\n hostId: string\n collectionId?: string\n collectionSlug?: string\n categoryId?: string\n categorySlug?: string\n tag?: string\n /** Search: the first word, matched as a word prefix of the product name. */\n query?: string\n type?: CommerceModel.ProductType | ''\n /** The In stock chip: only products a card would not mark sold out. */\n inStock?: boolean\n /** Explicit id list in the given order (wishlist rendering, AGL-297). */\n ids?: string[]\n minPriceCents?: number\n maxPriceCents?: number\n sort?: string\n /** Page size, 1–100. */\n limit?: number\n /** The `nextCursor` a previous page returned — continue after it. */\n after?: string\n /** Include category + price facets in the result. */\n facets?: boolean\n /**\n * Optional per-render dedupe (see {@link CatalogReadScope}). Absent for a\n * one-off call such as the API route, where there is nothing to share with.\n */\n reads?: CatalogReadScope\n}\n\n/**\n * Reads shared by every catalog query in ONE render, for ONE host.\n *\n * Two grids on one page asking the SAME question share one answer, and every\n * grid that shows category chips shares one read of the taxonomy. A grid\n * asking a different question — another collection, another sort — runs its\n * own query, because each query now reads only the page it returns rather\n * than the whole catalog (AGL-3321).\n *\n * Scoped to one host and one render deliberately: it holds promises, not\n * data, so nothing is cached across requests and no staleness window opens.\n * Reusing a scope for a second host would serve that host the first one's\n * catalog, which is why it is created per render rather than per module.\n */\nexport interface CatalogReadScope {\n categories?: Promise<FirebaseFirestore.QuerySnapshot>\n results?: Map<string, Promise<PublicCatalogResult>>\n}\n\n/** A fresh read scope. One per render, never reused across hosts. */\nexport function createCatalogReadScope(): CatalogReadScope {\n return {}\n}\n\n/** What both the API route and the SSR seed hand to a product grid. */\nexport interface PublicCatalogResult {\n items: PublicCatalogItem[]\n /** Pass back as `after` for the next page; absent on the last one. */\n nextCursor?: string\n categories?: PublicCatalogCategory[]\n priceBounds?: PublicCatalogPriceBounds\n /** Controls in force that the query could not apply (AGL-3321). */\n refused?: PublicCatalogRefusal[]\n /** Said about what WAS applied, e.g. that search reads one word. */\n notices?: string[]\n}\n\n/** Most products one page returns. */\nconst CATALOG_PAGE_MAX = 100\n/** Page size when a grid names none. */\nconst CATALOG_PAGE_DEFAULT = 24\n/**\n * Members of a MANUAL collection read by id. A hand-curated list is its own\n * complete set, so it is read whole and every visitor control is answered over\n * all of it rather than over a window of the catalog.\n */\nconst MANUAL_COLLECTION_MAX = 1000\n/** Ids per `getAll`, well inside the Admin SDK's comfortable batch. */\nconst GET_ALL_CHUNK = 100\n\n/** One product as the catalog reads it: stored, lifted, and keyed. */\ninterface CatalogRow {\n id: string\n product: CommerceModel.HostProduct\n /**\n * The fields the catalog query reads, as the writers stamp them — derived\n * through the same library functions the writers spread, so a held row\n * (a manual collection's member, an id list's entry) answers a predicate\n * exactly as the stored document will once it is backfilled.\n */\n values: Record<string, unknown>\n}\n\nfunction toRow(docSnapshot: FirebaseFirestore.DocumentSnapshot): CatalogRow | null {\n const raw = docSnapshot.data() as Record<string, unknown> | undefined\n if (!raw) return null\n const product = CommerceModel.liftLegacyProduct(raw as never)\n return {\n id: docSnapshot.id,\n product,\n values: {\n ...raw,\n status: product.status,\n type: product.type,\n deletedAt: raw['deletedAt'] ?? null,\n ...CommerceModel.productSearchFields({\n name: String(product.name ?? ''),\n variants: product.variants,\n }),\n ...CommerceModel.productStockFields(product),\n },\n }\n}\n\n/** A row's value at a queried path, as the query sees it. */\nfunction rowValue(row: CatalogRow, path: string): unknown {\n return row.values[path]\n}\n\n/** A held row a shopper may be shown: live and active, as the query's scope. */\nconst onSale = (row: CatalogRow) =>\n STOREFRONT_CATALOG_BASE.every((filter) => rowMatches(row, filter))\n\n/** One plan predicate over one row, with Firestore's meaning of each operator. */\nfunction rowMatches(row: CatalogRow, filter: ListQueryFilter): boolean {\n const actual = rowValue(row, filter.path)\n if (actual === undefined) return false\n const value = filter.value\n const list = Array.isArray(value) ? (value as readonly unknown[]) : [value]\n switch (filter.op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== null && actual !== value\n case 'in':\n return list.includes(actual)\n case 'array-contains':\n return Array.isArray(actual) && actual.includes(value)\n case 'array-contains-any':\n return Array.isArray(actual) && actual.some((member) => list.includes(member))\n case '<':\n return typeof actual === typeof value && (actual as number) < (value as number)\n case '<=':\n return typeof actual === typeof value && (actual as number) <= (value as number)\n case '>':\n return typeof actual === typeof value && (actual as number) > (value as number)\n case '>=':\n return typeof actual === typeof value && (actual as number) >= (value as number)\n default:\n return false\n }\n}\n\n/** A cursor: the last row's order value and id, opaque to the grid. */\nfunction encodeCursor(value: unknown, id: string): string {\n return Buffer.from(JSON.stringify([value ?? null, id])).toString('base64url')\n}\n\nfunction decodeCursor(raw: string | undefined): { value: unknown; id: string } | null {\n if (!raw) return null\n try {\n const parsed = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'))\n if (!Array.isArray(parsed) || parsed.length !== 2) return null\n const [value, id] = parsed\n if (typeof id !== 'string' || !id) return null\n if (value !== null && typeof value !== 'string' && typeof value !== 'number') return null\n return { value, id }\n } catch {\n return null\n }\n}\n\nconst compareValues = (a: unknown, b: unknown): number => {\n if (a === b) return 0\n // A row without the ordered value sorts after every row with one.\n if (a === undefined || a === null) return 1\n if (b === undefined || b === null) return -1\n return a < b ? -1 : a > b ? 1 : 0\n}\n\n/**\n * One page of rows already held WHOLE — a manual collection, an id list, a\n * smart collection's scan — ordered by `key` then id, after the cursor.\n */\nfunction pageHeldRows(\n rows: readonly CatalogRow[],\n key: (row: CatalogRow) => unknown,\n direction: 'asc' | 'desc',\n after: string | undefined,\n max: number,\n): { rows: CatalogRow[]; nextCursor?: string } {\n const signed = (value: number) => (direction === 'desc' ? -value : value)\n const ordered = [...rows].sort(\n (a, b) =>\n signed(compareValues(key(a), key(b))) || signed(compareValues(a.id, b.id)),\n )\n const cursor = decodeCursor(after)\n const start = cursor\n ? ordered.findIndex(\n (row) =>\n signed(compareValues(key(row), cursor.value)) > 0 ||\n (compareValues(key(row), cursor.value) === 0 &&\n signed(compareValues(row.id, cursor.id)) > 0),\n )\n : 0\n if (start === -1) return { rows: [] }\n const page = ordered.slice(start, start + max)\n const last = page[page.length - 1]\n return {\n rows: page,\n ...(last && start + max < ordered.length\n ? { nextCursor: encodeCursor(key(last), last.id) }\n : {}),\n }\n}\n\n/** Documents by id, in chunks, dropping the ones that do not exist. */\nasync function readProductsById(\n productsRef: FirebaseFirestore.CollectionReference,\n ids: readonly string[],\n): Promise<CatalogRow[]> {\n const firestore = firebaseAdmin.app().firestore()\n const rows: CatalogRow[] = []\n for (let start = 0; start < ids.length; start += GET_ALL_CHUNK) {\n const refs = ids.slice(start, start + GET_ALL_CHUNK).map((id) => productsRef.doc(id))\n if (!refs.length) continue\n const snapshots = await firestore.getAll(...refs)\n for (const snapshot of snapshots) {\n if (!snapshot.exists) continue\n const row = toRow(snapshot)\n if (row) rows.push(row)\n }\n }\n return rows\n}\n\n/** The card a grid renders for one product. */\nfunction toItem(row: CatalogRow): PublicCatalogItem {\n const { product } = row\n const [minPrice, maxPrice] = CommerceModel.productPriceRange(product)\n const primary = product.variants[0]\n return {\n id: row.id,\n name: product.name,\n slug: product.slug,\n type: product.type,\n priceUsd: minPrice,\n maxPriceUsd: maxPrice,\n ...(primary?.compareAtPriceUsd\n ? { compareAtPriceUsd: primary.compareAtPriceUsd }\n : {}),\n ...(product.mediaUrls?.[0] || product.imageUrl\n ? { imageUrl: product.mediaUrls?.[0] ?? product.imageUrl }\n : {}),\n // The same verdict the In stock chip queries (`soldOut`), from the one\n // function every stock writer stores it through.\n soldOut: CommerceModel.productStockFields(product).soldOut,\n ...(product.tags?.length ? { tags: product.tags } : {}),\n ...(product.variants.some((variant) => CommerceModel.variantHasPrice(variant))\n ? {}\n : { priceComingSoon: true as const }),\n }\n}\n\n/**\n * The plan's refusals, as the plan made them; the grid words each one. A\n * search refused because a smart collection already holds the query's one\n * array clause gets a reason a shopper can read.\n */\nfunction refusalsOf(plan: ListQueryPlan): PublicCatalogRefusal[] {\n return plan.refused.map((refusal) =>\n refusal.clause === 'search' && refusal.reason.startsWith('this list is already narrowed')\n ? {\n ...refusal,\n reason: 'this collection is already narrowed by a tag or category, which search cannot combine with',\n }\n : refusal,\n )\n}\n\n/** How a sort reads in the grid's select, for the notice below. */\nconst SORT_LABELS: Readonly<Record<string, string>> = {\n nameLower: 'Name',\n createdAtMs: 'Newest',\n}\n\n/** The operators that make a field a range, which must lead the order. */\nconst RANGE_OPS: ReadonlySet<string> = new Set(['<', '<=', '>', '>=', '!='])\n\n/**\n * The plan, ordered the way the visitor asked when a range forced its field.\n *\n * A range leads the order, so while the price slider (or a smart collection's\n * \"price under\" rule) is set the query is ordered by price: \"high to low\"\n * stays high to low, and any other sort — Name, Newest — becomes low to high\n * and is SAID to, rather than silently replaced (AGL-3321).\n *\n * The notice names the cause. The slider is the visitor's to clear, so the\n * sort they chose returns when they do; a collection's rule is in the scope\n * (`request.base`), which no visitor can clear, so that notice says the\n * collection is always ordered by price.\n */\nfunction planFor(request: ListQueryRequest, sort: ListQuerySort): ListQueryPlan {\n const plan = planListQuery(STOREFRONT_CATALOG_DECLARATION, request, nameSearchNormalizers)\n if (plan.orderBy.path !== sort.path) {\n const asked = SORT_LABELS[sort.path]\n if (!asked) return plan\n const byRule = (request.base ?? []).some(\n (filter) => filter.path === plan.orderBy.path && RANGE_OPS.has(filter.op),\n )\n return {\n ...plan,\n notices: [\n ...plan.notices,\n byRule\n ? `Sorted by price, low to high — this collection picks its products by price, so it cannot be sorted by ${asked}.`\n : `Sorted by price, low to high, while a price range is set — ${asked} applies again when it is cleared.`,\n ],\n }\n }\n return plan.orderBy.direction !== sort.direction ? { ...plan, orderBy: sort } : plan\n}\n\n/** A price bound as the grid's slider shows it, for a refusal's sentence. */\nconst dollars = (cents: number) => `$${(cents / 100).toFixed(cents % 100 ? 2 : 0)}`\n\n/**\n * Public catalog listing (AGL-291): active products for storefront blocks —\n * a collection, a category or a tag, searched, filtered by type and price,\n * sorted, and paged by cursor (AGL-561, AGL-564).\n *\n * EVERY CONTROL IS ON THE QUERY (AGL-3321). This used to read `limit(500)`\n * products and narrow them in memory, so a catalog past five hundred answered\n * every search, chip and slider from an arbitrary five hundred of them. Now:\n *\n * - the catalog, a category or a tag — ONE query built by the list query\n * plan (`STOREFRONT_CATALOG_DECLARATION`), paged by its own cursor;\n * - a SMART collection whose rules a query can hold — the same query, with\n * the rules as its scope (`smartCollectionScope`);\n * - a MANUAL collection, or an explicit id list — read whole by id, which\n * is a complete set, and answered over all of it;\n * - a smart collection whose rules no query can hold (a NOT, an OR, a name\n * rule, a price over) — the same query, scoped by the membership every\n * product writer stores (`collectionIds array-contains <id>`), which the\n * collection-membership route re-stamps whenever the rules change.\n *\n * What one query cannot hold — two array clauses, two ranges — is refused by\n * name in `refused` and not applied, never applied to some rows and not\n * others. Read-only public data; prices here are display-only (charges\n * always come from the docs server-side).\n *\n * Extracted from the handler (AGL-659) so the site-page enricher seeds a\n * grid's first page through THIS function rather than a reimplementation\n * of it. A seed that filters or sorts differently from the API would\n * server-render one set of products and hydrate to another.\n */\nexport async function queryPublicCatalog(\n params: PublicCatalogQuery,\n): Promise<PublicCatalogResult> {\n const { reads, ...question } = params\n if (!reads) return runPublicCatalog(question)\n const key = JSON.stringify(question)\n reads.results ??= new Map()\n const shared = reads.results.get(key)\n if (shared) return shared\n // Stored BEFORE it is awaited, so grids running together under one\n // `Promise.all` share the one in-flight read rather than racing to store it.\n const pending = runPublicCatalog(question, reads)\n reads.results.set(key, pending)\n return pending\n}\n\nasync function runPublicCatalog(\n params: Omit<PublicCatalogQuery, 'reads'>,\n reads?: CatalogReadScope,\n): Promise<PublicCatalogResult> {\n const { hostId } = params\n const collectionSlug = params.collectionSlug ?? ''\n const categorySlug = params.categorySlug ?? ''\n // Id-based filters are preferred (rename-safe, AGL-343); slugs remain\n // for legacy screens and template-URL resolution.\n const collectionIdParam = params.collectionId ?? ''\n const categoryIdParam = params.categoryId ?? ''\n const tag = (params.tag ?? '').trim()\n const query = (params.query ?? '').trim()\n const type = STOREFRONT_PRODUCT_TYPES.includes(params.type as CommerceModel.ProductType)\n ? (params.type as CommerceModel.ProductType)\n : ''\n const ids = (params.ids ?? []).slice(0, CATALOG_PAGE_MAX)\n const asked = Math.floor(Number(params.limit ?? CATALOG_PAGE_DEFAULT))\n const max =\n Number.isFinite(asked) && asked > 0\n ? Math.min(CATALOG_PAGE_MAX, asked)\n : CATALOG_PAGE_DEFAULT\n const sort = storefrontCatalogSort(params.sort)\n const wantFacets = Boolean(params.facets)\n\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const productsRef = hostRef.collection('products')\n const sharedCategories = () =>\n (reads\n ? (reads.categories ??=\n hostRef.collection('productCategories').limit(200).get())\n : hostRef.collection('productCategories').limit(200).get())\n\n // Wishlist and related rendering (AGL-297): an explicit id list is its own\n // complete set — read it by id, keep what is on sale, in the given order.\n if (ids.length) {\n const order = new Map(ids.map((id, index) => [id, index]))\n const rows = (await readProductsById(productsRef, ids)).filter(onSale)\n rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))\n return { items: rows.slice(0, max).map(toItem) }\n }\n\n const [collectionsSnapshot, categoriesSnapshot, collectionByIdSnapshot, facetsSnapshot] =\n await Promise.all([\n // limit(5) not 1: content collections share this path and a slug is\n // unique only within a kind (AGL-954) — the catalog match is picked\n // out below rather than trusting whichever doc came back first.\n !collectionIdParam && collectionSlug\n ? hostRef\n .collection('collections')\n .where('slug', '==', collectionSlug)\n .limit(5)\n .get()\n : null,\n !categoryIdParam && categorySlug\n ? hostRef\n .collection('productCategories')\n .where('slug', '==', categorySlug)\n .limit(1)\n .get()\n : null,\n collectionIdParam\n ? hostRef.collection('collections').doc(collectionIdParam).get()\n : null,\n wantFacets ? sharedCategories() : null,\n ])\n const collectionById =\n collectionByIdSnapshot?.exists &&\n Aglyn.hostCollectionKind(collectionByIdSnapshot.data()) === 'catalog'\n ? collectionByIdSnapshot\n : undefined\n const collectionDoc =\n collectionById ??\n collectionsSnapshot?.docs.find(\n (docSnapshot) => Aglyn.hostCollectionKind(docSnapshot.data()) === 'catalog',\n )\n const collection = collectionDoc\n ? (collectionDoc.data() as CommerceModel.HostCollection)\n : undefined\n const categoryId = categoryIdParam || categoriesSnapshot?.docs[0]?.id || ''\n\n // Category facets (AGL-561): id/name/slug for grid filter chips,\n // parent-tree order preserved (order, then name). The taxonomy is a\n // bounded picker, read whole.\n const categories: PublicCatalogCategory[] | undefined = facetsSnapshot\n ? facetsSnapshot.docs\n .map((docSnapshot) => {\n const data = docSnapshot.data() as CommerceModel.ProductCategory\n return {\n id: docSnapshot.id,\n name: String(data.name ?? ''),\n slug: String(data.slug ?? ''),\n order: Number(data.order ?? 0),\n }\n })\n .sort((a, b) => a.order - b.order || a.name.localeCompare(b.name))\n .map(({ id, name, slug }) => ({ id, name, slug }))\n : undefined\n\n // The visitor's controls, as clauses on the one declaration. A price\n // clause carries the dollars the slider showed, for a refusal's sentence.\n const priceClauses: ListFilterClause[] = [\n ...(params.minPriceCents != null\n ? [{\n field: 'price',\n op: '>=',\n value: String(params.minPriceCents),\n label: dollars(params.minPriceCents),\n }]\n : []),\n ...(params.maxPriceCents != null\n ? [{\n field: 'price',\n op: '<=',\n value: String(params.maxPriceCents),\n label: dollars(params.maxPriceCents),\n }]\n : []),\n ]\n const otherClauses: ListFilterClause[] = [\n ...(categoryId ? [{ field: 'category', op: 'contains', value: categoryId }] : []),\n ...(tag ? [{ field: 'tag', op: 'contains', value: tag }] : []),\n ...(type ? [{ field: 'type', op: 'equals', value: type }] : []),\n ...(params.inStock ? [{ field: 'stock', op: 'is', value: 'false' }] : []),\n ]\n // The words typed, as a quick search hands them over: the plan searches by\n // the first and says so when there were more.\n const search = query.split(/\\s+/).filter(Boolean)\n\n const result = (\n rows: CatalogRow[],\n plan: ListQueryPlan,\n extra: Partial<PublicCatalogResult> = {},\n ): PublicCatalogResult => {\n const refused = refusalsOf(plan)\n const notices = [...plan.notices, ...(extra.notices ?? [])]\n return {\n items: rows.map(toItem),\n ...(extra.nextCursor ? { nextCursor: extra.nextCursor } : {}),\n ...(categories ? { categories } : {}),\n ...(extra.priceBounds ? { priceBounds: extra.priceBounds } : {}),\n ...(refused.length ? { refused } : {}),\n ...(notices.length ? { notices } : {}),\n }\n }\n\n /*\n * A collection held WHOLE: a manual one's members, or a smart one whose\n * rules no query can hold. The plan still decides what is applied — the\n * same refusals as the query path, so a grid behaves the same whatever it\n * lists — and its predicates are answered over every held row.\n */\n const answerHeld = (\n held: CatalogRow[],\n options: { manualOrder?: Map<string, number>; notices?: string[] },\n ): PublicCatalogResult => {\n const request: ListQueryRequest = {\n base: STOREFRONT_CATALOG_BASE,\n clauses: [...otherClauses, ...priceClauses],\n search,\n sort,\n }\n const plan = planFor(request, sort)\n const unpriced = planFor({ ...request, clauses: otherClauses }, sort)\n const matched = held.filter((row) => plan.filters.every((filter) => rowMatches(row, filter)))\n // Bounds span every OTHER control, so the slider stays anchored while the\n // visitor narrows it.\n const bounding = held.filter((row) =>\n unpriced.filters.every((filter) => rowMatches(row, filter)),\n )\n const priceBounds =\n wantFacets && bounding.length\n ? {\n minCents: Math.min(...bounding.map((row) => Number(rowValue(row, 'priceFromCents')))),\n maxCents: Math.max(...bounding.map((row) => Number(rowValue(row, 'priceFromCents')))),\n }\n : undefined\n // A manual collection keeps the merchant's order unless the visitor (or\n // the author) chose a price or recency sort.\n const keepManual =\n options.manualOrder && plan.orderBy.path === STOREFRONT_CATALOG_SORTS.name.path\n const page = keepManual\n ? pageHeldRows(\n matched,\n (row) => options.manualOrder?.get(row.id) ?? Number.MAX_SAFE_INTEGER,\n 'asc',\n params.after,\n max,\n )\n : pageHeldRows(\n matched,\n (row) => rowValue(row, plan.orderBy.path),\n plan.orderBy.direction,\n params.after,\n max,\n )\n return result(page.rows, plan, {\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n ...(priceBounds ? { priceBounds } : {}),\n ...(options.notices ? { notices: options.notices } : {}),\n })\n }\n\n let scope: ListQueryFilter[] = []\n if (collection) {\n if (collection.mode === 'manual') {\n const memberIds = (collection.productIds ?? []).slice(0, MANUAL_COLLECTION_MAX)\n const manualOrder = new Map(memberIds.map((id, index) => [id, index]))\n return answerHeld(await readProductsById(productsRef, memberIds), { manualOrder })\n }\n const smart = smartCollectionScope(collection)\n if (!(collection.rules ?? []).length) {\n // A smart collection with no rules holds nothing (`matchesCollection`).\n return result([], planFor({ base: STOREFRONT_CATALOG_BASE, clauses: [], sort }, sort))\n }\n if ('unservable' in smart) {\n // Rules no query can express (a NOT, an OR, a name rule, a price over)\n // are answered by the membership every product writer stores\n // (`collectionIds`, AGL-3321): one array clause on the same query, so\n // the grid's other array controls are refused by name beside it.\n scope = [\n {\n path: CommerceModel.PRODUCT_COLLECTION_IDS,\n op: 'array-contains',\n value: String(collectionDoc?.id ?? ''),\n },\n ]\n } else {\n scope = smart.filters\n // A \"type is not\" rule is `type in […]`; the visitor's Type chip is the\n // narrower question, answered inside it or not at all.\n const typeRule = scope.find((filter) => filter.path === 'type')\n if (typeRule && type) {\n const allowed = Array.isArray(typeRule.value) ? typeRule.value : [typeRule.value]\n if (!allowed.includes(type)) {\n return result([], planFor({ base: STOREFRONT_CATALOG_BASE, clauses: [], sort }, sort))\n }\n scope = scope.filter((filter) => filter !== typeRule)\n }\n }\n }\n\n // THE QUERY: every predicate, one order, a page and one row to spare.\n const request: ListQueryRequest = {\n base: [...STOREFRONT_CATALOG_BASE, ...scope],\n clauses: [...otherClauses, ...priceClauses],\n search,\n sort,\n }\n const plan = planFor(request, sort)\n const byId = firebaseAdmin.firestore.FieldPath.documentId()\n const ordered = (target: ListQueryPlan) =>\n applyListQuery(productsRef, target).orderBy(byId, target.orderBy.direction)\n let pageQuery = ordered(plan)\n const cursor = decodeCursor(params.after)\n if (cursor) pageQuery = pageQuery.startAfter(cursor.value, cursor.id)\n\n // Bounds span every OTHER control, so the slider stays anchored while the\n // visitor narrows it: the lowest and highest price the rest allows, as two\n // one-row reads ordered by price.\n const byPrice = STOREFRONT_CATALOG_SORTS['price-asc']\n const unpriced = wantFacets\n ? planFor({ ...request, clauses: otherClauses, sort: byPrice }, byPrice)\n : null\n const pricedBy = (target: ListQueryPlan, direction: 'asc' | 'desc') =>\n ordered({ ...target, orderBy: { path: 'priceFromCents', direction } }).limit(1).get()\n\n const [snapshot, lowest, highest] = await Promise.all([\n pageQuery.limit(max + 1).get(),\n unpriced ? pricedBy(unpriced, 'asc') : null,\n unpriced ? pricedBy(unpriced, 'desc') : null,\n ])\n const docs = snapshot.docs.slice(0, max)\n const rows = docs.map(toRow).filter((row): row is CatalogRow => Boolean(row))\n const last = docs[docs.length - 1]\n const low = lowest?.docs[0]?.get('priceFromCents')\n const high = highest?.docs[0]?.get('priceFromCents')\n return result(rows, plan, {\n ...(last && snapshot.docs.length > max\n ? { nextCursor: encodeCursor(last.get(plan.orderBy.path), last.id) }\n : {}),\n ...(typeof low === 'number' && typeof high === 'number'\n ? { priceBounds: { minCents: low, maxCents: high } }\n : {}),\n })\n}\n\n/** Public catalog listing endpoint — a thin parse over queryPublicCatalog. */\nexport const catalogHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n\n try {\n const result = await queryPublicCatalog({\n hostId,\n collectionSlug: String(req.query.collection ?? ''),\n categorySlug: String(req.query.category ?? ''),\n collectionId: String(req.query.collectionId ?? ''),\n categoryId: String(req.query.categoryId ?? ''),\n tag: String(req.query.tag ?? ''),\n query: String(req.query.q ?? ''),\n type: String(req.query.type ?? '') as CommerceModel.ProductType | '',\n inStock: String(req.query.inStock ?? '') === '1',\n ids: String(req.query.ids ?? '')\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean),\n // Price-range filter (AGL-564): integer cents on the displayed price.\n minPriceCents: parsePriceCents(req.query.minPriceCents),\n maxPriceCents: parsePriceCents(req.query.maxPriceCents),\n sort: String(req.query.sort ?? 'name'),\n limit: Number(req.query.limit ?? CATALOG_PAGE_DEFAULT),\n after: String(req.query.after ?? ''),\n facets: String(req.query.facets ?? '') === '1',\n })\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=60, stale-while-revalidate=300',\n )\n return res.status(200).json(result)\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Catalog unavailable' })\n }\n}\n"],"names":["nameSearchNormalizers","Aglyn","planListQuery","firebaseAdmin","applyListQuery","smartCollectionScope","STOREFRONT_CATALOG_BASE","STOREFRONT_CATALOG_DECLARATION","STOREFRONT_CATALOG_SORTS","STOREFRONT_PRODUCT_TYPES","storefrontCatalogSort","CommerceModel","parsePriceCents","value","raw","String","trim","undefined","cents","Number","isFinite","Math","round","createCatalogReadScope","CATALOG_PAGE_MAX","CATALOG_PAGE_DEFAULT","MANUAL_COLLECTION_MAX","GET_ALL_CHUNK","toRow","docSnapshot","product","data","liftLegacyProduct","id","values","status","type","deletedAt","productSearchFields","name","variants","productStockFields","rowValue","row","path","onSale","every","filter","rowMatches","actual","list","Array","isArray","op","includes","some","member","encodeCursor","Buffer","from","JSON","stringify","toString","decodeCursor","parsed","parse","length","compareValues","a","b","pageHeldRows","rows","key","direction","after","max","signed","ordered","sort","cursor","start","findIndex","page","slice","last","nextCursor","readProductsById","productsRef","ids","firestore","app","refs","map","doc","snapshots","getAll","snapshot","exists","push","toItem","minPrice","maxPrice","productPriceRange","primary","slug","priceUsd","maxPriceUsd","compareAtPriceUsd","mediaUrls","imageUrl","soldOut","tags","variant","variantHasPrice","priceComingSoon","refusalsOf","plan","refused","refusal","clause","reason","startsWith","SORT_LABELS","nameLower","createdAtMs","RANGE_OPS","Set","planFor","request","orderBy","asked","byRule","base","has","notices","dollars","toFixed","queryPublicCatalog","params","reads","question","runPublicCatalog","results","Map","shared","get","pending","set","categoriesSnapshot","lowest","highest","hostId","collectionSlug","categorySlug","collectionIdParam","collectionId","categoryIdParam","categoryId","tag","query","floor","limit","min","wantFacets","Boolean","facets","hostRef","collection","sharedCategories","categories","order","index","items","collectionsSnapshot","collectionByIdSnapshot","facetsSnapshot","Promise","all","where","collectionById","hostCollectionKind","collectionDoc","docs","find","localeCompare","priceClauses","minPriceCents","field","label","maxPriceCents","otherClauses","inStock","search","split","result","extra","priceBounds","answerHeld","held","options","clauses","unpriced","matched","filters","bounding","minCents","maxCents","keepManual","manualOrder","MAX_SAFE_INTEGER","scope","mode","memberIds","productIds","smart","rules","PRODUCT_COLLECTION_IDS","typeRule","allowed","byId","FieldPath","documentId","target","pageQuery","startAfter","byPrice","pricedBy","low","high","catalogHandler","req","res","json","error","category","q","setHeader","console"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { siteReturnUrl } from "@aglyn/aglyn/app-utils/site-return-url";
|
|
18
|
+
import { advertisingRequestFacts, recordAdvertisingOrderConsent } from "@aglyn/aglyn/plugin-manager/plugin-advertising-conversions";
|
|
18
19
|
import * as Aglyn from "@aglyn/aglyn/server";
|
|
19
20
|
import * as CommerceModel from "../model/index.js";
|
|
20
21
|
import { claimAttempt, deriveStripeObjectKey } from "@aglyn/aglyn/server";
|
|
@@ -107,7 +108,7 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
107
108
|
if (held) await held.release();
|
|
108
109
|
};
|
|
109
110
|
try {
|
|
110
|
-
var _variant_priceUsd, _req_headers_referer, _req_headers_host, _storeSettings_get, _lifted_type, _ref1, _ref2, _lifted_name, _ref3, _lifted_type1, _variant_weightGrams, _lifted_type2, _lifted_type3, _product_name, _ref4;
|
|
111
|
+
var _variant_priceUsd, _req_headers_referer, _req_headers_host, _storeSettings_get, _lifted_type, _ref1, _ref2, _lifted_name, _ref3, _lifted_type1, _variant_weightGrams, _lifted_type2, _lifted_type3, _product_name, _ref4, _session_id;
|
|
111
112
|
var _ownerOrg_org, _ownerOrg_org1, _ownerOrg_org2;
|
|
112
113
|
const firestore = firebaseAdmin.app().firestore();
|
|
113
114
|
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
@@ -1051,6 +1052,15 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
1051
1052
|
// reason: releasing here would hand the last unit to the next shopper while
|
|
1052
1053
|
// this one still holds a payable session, which is the oversell itself.
|
|
1053
1054
|
stockHold = null;
|
|
1055
|
+
// The shopper's advertising consent, for the order this session may
|
|
1056
|
+
// become (AGL-3694): the plugin that sends Conversions API events reports
|
|
1057
|
+
// the purchase only where it was granted. A no-op with no wire, with GPC,
|
|
1058
|
+
// or on a site that connected no vendor; it never fails the checkout.
|
|
1059
|
+
await recordAdvertisingOrderConsent(_extends({
|
|
1060
|
+
hostId,
|
|
1061
|
+
orderKey: String((_session_id = session.id) != null ? _session_id : ''),
|
|
1062
|
+
wire: body.adConsent
|
|
1063
|
+
}, advertisingRequestFacts(req.headers)));
|
|
1054
1064
|
await claim.record(200, payload);
|
|
1055
1065
|
return res.status(200).json(payload);
|
|
1056
1066
|
} catch (error) {
|