@aglyn/plugins-commerce 1.0.0-beta.219 → 1.0.0-beta.221

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +5 -0
  2. package/package.json +14 -14
  3. package/src/lib/components/console/catalog-organization-card.component.js +626 -446
  4. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  5. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  6. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  7. package/src/lib/components/console/product-editor-dialog.component.js +1127 -846
  8. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
  10. package/src/lib/components/console/site-member-purchases.component.js +339 -0
  11. package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
  12. package/src/lib/components/member-signup.js +2 -2
  13. package/src/lib/components/member-signup.js.map +1 -1
  14. package/src/lib/components/newsletter-signup.js +2 -2
  15. package/src/lib/components/newsletter-signup.js.map +1 -1
  16. package/src/lib/constants/operator-alerts.js +5 -3
  17. package/src/lib/constants/operator-alerts.js.map +1 -1
  18. package/src/lib/model/commerce-io.js +1 -1
  19. package/src/lib/model/commerce-io.js.map +1 -1
  20. package/src/lib/model/commerce-record-lists.d.ts +31 -0
  21. package/src/lib/model/commerce-record-lists.js +45 -0
  22. package/src/lib/model/commerce-record-lists.js.map +1 -0
  23. package/src/lib/model/commerce-record-routes.d.ts +18 -0
  24. package/src/lib/model/commerce-record-routes.js +1 -1
  25. package/src/lib/model/commerce-record-routes.js.map +1 -1
  26. package/src/lib/model/commerce-search-sources.js +4 -2
  27. package/src/lib/model/commerce-search-sources.js.map +1 -1
  28. package/src/lib/model/commerce-topics.d.ts +20 -0
  29. package/src/lib/model/commerce-topics.js +33 -0
  30. package/src/lib/model/commerce-topics.js.map +1 -0
  31. package/src/lib/model/index.d.ts +1 -0
  32. package/src/lib/model/index.js +1 -0
  33. package/src/lib/model/index.js.map +1 -1
  34. package/src/lib/model/product-record.d.ts +32 -0
  35. package/src/lib/model/product-record.js +82 -0
  36. package/src/lib/model/product-record.js.map +1 -0
  37. package/src/lib/model/site-member-purchases.d.ts +48 -0
  38. package/src/lib/model/site-member-purchases.js +50 -0
  39. package/src/lib/model/site-member-purchases.js.map +1 -0
  40. package/src/lib/plugin.js +12 -0
  41. package/src/lib/plugin.js.map +1 -1
  42. package/src/lib/server/billing-webhook.js +410 -208
  43. package/src/lib/server/billing-webhook.js.map +1 -1
  44. package/src/lib/server/cart-checkout.js +1 -1
  45. package/src/lib/server/cart-checkout.js.map +1 -1
  46. package/src/lib/server/gift-cards.js +7 -3
  47. package/src/lib/server/gift-cards.js.map +1 -1
  48. package/src/lib/server/low-stock.js +3 -1
  49. package/src/lib/server/low-stock.js.map +1 -1
  50. package/src/lib/server/member-post.js +2 -2
  51. package/src/lib/server/member-post.js.map +1 -1
  52. package/src/lib/server/membership-admin-password.js +4 -1
  53. package/src/lib/server/membership-admin-password.js.map +1 -1
  54. package/src/lib/server/membership-register.js +13 -2
  55. package/src/lib/server/membership-register.js.map +1 -1
  56. package/src/lib/server/newsletter.js +32 -19
  57. package/src/lib/server/newsletter.js.map +1 -1
  58. package/src/lib/server/pos-order.js +18 -10
  59. package/src/lib/server/pos-order.js.map +1 -1
  60. package/src/lib/server/process-abandoned.js +4 -2
  61. package/src/lib/server/process-abandoned.js.map +1 -1
  62. package/src/lib/server/process-restock.js +7 -5
  63. package/src/lib/server/process-restock.js.map +1 -1
  64. package/src/lib/server/product-record-index.js +5 -37
  65. package/src/lib/server/product-record-index.js.map +1 -1
  66. package/src/lib/server/reconcile-stock.js +1 -1
  67. package/src/lib/server/reconcile-stock.js.map +1 -1
  68. package/src/lib/server/reserve-stock.js +6 -1
  69. package/src/lib/server/reserve-stock.js.map +1 -1
  70. package/src/lib/server/revenue-source.js +3 -3
  71. package/src/lib/server/revenue-source.js.map +1 -1
  72. package/src/lib/server/supplier-outbox.js +16 -1
  73. package/src/lib/server/supplier-outbox.js.map +1 -1
  74. package/src/lib/server/supplier-update.js +19 -9
  75. package/src/lib/server/supplier-update.js.map +1 -1
  76. package/src/lib/starter-templates.d.ts +19 -0
  77. package/src/lib/starter-templates.js +157 -0
  78. package/src/lib/starter-templates.js.map +1 -0
  79. package/src/lib/tenant-emails.js +54 -14
  80. package/src/lib/tenant-emails.js.map +1 -1
  81. package/src/lib/testing/stand-in-record-system.d.ts +11 -2
  82. package/src/lib/testing/stand-in-record-system.js +5 -3
  83. package/src/lib/testing/stand-in-record-system.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/operator-alerts.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 type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport { BUNDLE_ID } from './bundle-common'\n\n/**\n * The storefront's operator alerts (AGL-3377): the money faults only this\n * plugin can see, declared in the registry's shape and registered from\n * `declarations.server`, so core lists none of them and an install without\n * commerce shows none of them on Staff → Operator alerts.\n *\n * Each is raised through `raiseOperatorAlert` with its definition, which\n * also registers it in a process whose declarations have not loaded yet.\n */\nconst DAY = 24 * 60\n\nexport const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition = {\n type: 'commerce.chargebackUnroutable',\n pluginId: BUNDLE_ID,\n label: 'Storefront chargeback could not be routed',\n description:\n 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',\n tier: 'must',\n category: 'payments',\n title: 'A {{amount}} chargeback could not be routed to an order',\n body: '{{detail}}',\n delivery: 'immediate',\n dedupeWindowMinutes: DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.taxNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Subscription sales tax not reversed',\n description:\n 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',\n tier: 'must',\n category: 'payments',\n title: 'Sales tax not reversed on invoice {{invoiceId}}',\n body:\n '{{amount}} of sales tax on invoice {{invoiceId}} (site {{hostId}}) was not pulled back from the merchant: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.sellerShareNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Seller share not reversed on a lost dispute',\n description:\n 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',\n tier: 'must',\n category: 'payments',\n title: 'Seller share not reversed on dispute {{disputeId}}',\n body:\n 'The lost dispute {{disputeId}} on order {{orderId}} (site {{hostId}}) cost {{amount}}, and the merchant’s share was not reversed: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition = {\n type: 'commerce.feeRepriceRefused',\n pluginId: BUNDLE_ID,\n label: 'Platform fee correction refused',\n description:\n 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',\n tier: 'must',\n category: 'payments',\n title: 'Platform fee not corrected on {{subject}}',\n body: 'The platform fee on {{subject}} (site {{hostId}}) could not be corrected: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[] = [\n COMMERCE_CHARGEBACK_UNROUTABLE,\n COMMERCE_TAX_NOT_REVERSED,\n COMMERCE_SELLER_SHARE_NOT_REVERSED,\n COMMERCE_FEE_REPRICE_REFUSED,\n]\n"],"names":["BUNDLE_ID","DAY","COMMERCE_CHARGEBACK_UNROUTABLE","type","pluginId","label","description","tier","category","title","body","delivery","dedupeWindowMinutes","defaultEnabled","COMMERCE_TAX_NOT_REVERSED","COMMERCE_SELLER_SHARE_NOT_REVERSED","COMMERCE_FEE_REPRICE_REFUSED","COMMERCE_OPERATOR_ALERTS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,SAAS,QAAQ,qBAAiB;AAE3C;;;;;;;;CAQC,GACD,MAAMC,MAAM,KAAK;AAEjB,OAAO,MAAMC,iCAA0D;IACrEC,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqBX;IACrBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMC,4BAAqD;IAChEX,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAME,qCAA8D;IACzEZ,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMG,+BAAwD;IACnEb,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMI,2BAA+D;IAC1Ef;IACAY;IACAC;IACAC;CACD,CAAA"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/operator-alerts.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 type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport { BUNDLE_ID } from './bundle-common'\n\n/**\n * The storefront's operator alerts (AGL-3377): the money faults only this\n * plugin can see, declared in the registry's shape and registered from\n * `declarations.server`, so core lists none of them and an install without\n * commerce shows none of them on Staff → Operator alerts.\n *\n * Each is raised through `raiseOperatorAlert` with its definition, which\n * also registers it in a process whose declarations have not loaded yet.\n */\nconst DAY = 24 * 60\n\nexport const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition = {\n type: 'commerce.chargebackUnroutable',\n pluginId: BUNDLE_ID,\n label: 'Storefront chargeback could not be routed',\n description:\n 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',\n tier: 'must',\n category: 'payments',\n title: 'A {{amount}} chargeback could not be routed to an order',\n body: '{{detail}}',\n delivery: 'immediate',\n dedupeWindowMinutes: DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.taxNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Subscription sales tax not reversed',\n description:\n 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',\n tier: 'must',\n category: 'payments',\n title: 'Sales tax not reversed on invoice {{invoiceId}}',\n // `{{site}}` is the site's name and id (AGL-3432), so staff need not look\n // it up; the last sentence is the consequence the description states.\n body:\n '{{amount}} of sales tax on invoice {{invoiceId}} for site {{site}} was not pulled back from the merchant: {{reason}}. The platform files and remits that tax, so it may owe tax it does not hold.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.sellerShareNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Seller share not reversed on a lost dispute',\n description:\n 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',\n tier: 'must',\n category: 'payments',\n title: 'Seller share not reversed on dispute {{disputeId}}',\n body:\n 'The lost dispute {{disputeId}} on order {{orderId}} from site {{site}} cost {{amount}}, and the merchant’s share was not reversed: {{reason}}. The platform is out that share until it is recovered by hand in Stripe.',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition = {\n type: 'commerce.feeRepriceRefused',\n pluginId: BUNDLE_ID,\n label: 'Platform fee correction refused',\n description:\n 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',\n tier: 'must',\n category: 'payments',\n title: 'Platform fee not corrected on {{subject}}',\n body:\n 'The platform fee on {{subject}} for site {{site}} could not be corrected: {{reason}}. Until it is, the merchant is charged a fee its plan does not set.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[] = [\n COMMERCE_CHARGEBACK_UNROUTABLE,\n COMMERCE_TAX_NOT_REVERSED,\n COMMERCE_SELLER_SHARE_NOT_REVERSED,\n COMMERCE_FEE_REPRICE_REFUSED,\n]\n"],"names":["BUNDLE_ID","DAY","COMMERCE_CHARGEBACK_UNROUTABLE","type","pluginId","label","description","tier","category","title","body","delivery","dedupeWindowMinutes","defaultEnabled","COMMERCE_TAX_NOT_REVERSED","COMMERCE_SELLER_SHARE_NOT_REVERSED","COMMERCE_FEE_REPRICE_REFUSED","COMMERCE_OPERATOR_ALERTS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,SAAS,QAAQ,qBAAiB;AAE3C;;;;;;;;CAQC,GACD,MAAMC,MAAM,KAAK;AAEjB,OAAO,MAAMC,iCAA0D;IACrEC,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqBX;IACrBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMC,4BAAqD;IAChEX,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACP,0EAA0E;IAC1E,sEAAsE;IACtEC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAME,qCAA8D;IACzEZ,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMG,+BAAwD;IACnEb,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMI,2BAA+D;IAC1Ef;IACAY;IACAC;IACAC;CACD,CAAA"}
@@ -22,7 +22,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
22
22
  * with only `Image Src` add media. Export mirrors the same columns so
23
23
  * round-trips are lossless for the fields both sides share.
24
24
  */ import { commerceSlug, validateProduct } from "./commerce.js";
25
- import { parseCsv } from "@aglyn/aglyn/app-utils/dataset-csv";
25
+ import { parseCsv } from "@aglyn/aglyn/app-utils/csv";
26
26
  export const PRODUCT_CSV_HEADER = [
27
27
  'Handle',
28
28
  'Title',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-io.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\n/**\n * Catalog CSV import/export (AGL-282) speaking Shopify's product CSV\n * dialect — the migration wedge for switchers: `Handle` groups variant\n * rows into one product, `Option1..3 Name/Value` build the option axes,\n * `Variant *` columns carry per-variant data, and repeated-handle rows\n * with only `Image Src` add media. Export mirrors the same columns so\n * round-trips are lossless for the fields both sides share.\n */\n\nimport {\n commerceSlug,\n validateProduct,\n type HostProduct,\n type ProductOption,\n type ProductStatus,\n type ProductVariant,\n} from './commerce'\nimport { parseCsv } from '@aglyn/aglyn/app-utils/dataset-csv'\n\nexport const PRODUCT_CSV_HEADER = [\n 'Handle',\n 'Title',\n 'Body (HTML)',\n 'Type',\n 'Tags',\n 'Published',\n 'Option1 Name',\n 'Option1 Value',\n 'Option2 Name',\n 'Option2 Value',\n 'Option3 Name',\n 'Option3 Value',\n 'Variant SKU',\n 'Variant Barcode',\n 'Variant Price',\n 'Variant Compare At Price',\n 'Variant Inventory Qty',\n 'Variant Grams',\n 'Image Src',\n] as const\n\nfunction csvCell(value: unknown): string {\n const text = value == null ? '' : String(value)\n return /[\",\\n]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text\n}\n\n/** One row per variant; extra image rows follow Shopify's convention. */\nexport function productsToCsv(\n products: Array<HostProduct & { $id?: string }>,\n): string {\n const lines = [PRODUCT_CSV_HEADER.join(',')]\n for (const product of products) {\n const options = product.options ?? []\n product.variants.forEach((variant, index) => {\n const optionCells: string[] = []\n for (let axis = 0; axis < 3; axis += 1) {\n const option = options[axis]\n optionCells.push(\n index === 0 ? (option?.name ?? '') : '',\n option ? (variant.options?.[option.name] ?? '') : '',\n )\n }\n lines.push(\n [\n csvCell(product.slug),\n csvCell(index === 0 ? product.name : ''),\n csvCell(index === 0 ? (product.description ?? '') : ''),\n csvCell(index === 0 ? product.type : ''),\n csvCell(index === 0 ? (product.tags ?? []).join(', ') : ''),\n csvCell(index === 0 ? String(product.status === 'active') : ''),\n ...optionCells.map(csvCell),\n csvCell(variant.sku ?? ''),\n csvCell(variant.barcode ?? ''),\n csvCell(variant.priceUsd),\n csvCell(variant.compareAtPriceUsd ?? ''),\n csvCell(variant.inventory ?? ''),\n csvCell(variant.weightGrams ?? ''),\n csvCell(index === 0 ? (product.mediaUrls?.[0] ?? '') : ''),\n ].join(','),\n )\n })\n for (const url of (product.mediaUrls ?? []).slice(1)) {\n lines.push(\n [\n csvCell(product.slug),\n ...Array(PRODUCT_CSV_HEADER.length - 2).fill(''),\n csvCell(url),\n ].join(','),\n )\n }\n }\n return lines.join('\\n')\n}\n\nexport interface ProductCsvImport {\n products: HostProduct[]\n /** Row-anchored problems; importable products still parse. */\n errors: string[]\n}\n\n/** Case-insensitive column lookup so hand-edited headers still map. */\nfunction headerIndex(header: string[]): Map<string, number> {\n const map = new Map<string, number>()\n header.forEach((cell, index) =>\n map.set(cell.trim().toLowerCase(), index),\n )\n return map\n}\n\nexport function parseProductsCsv(text: string): ProductCsvImport {\n const rows = parseCsv(text)\n if (rows.length < 2) {\n return { products: [], errors: ['No data rows found'] }\n }\n const columns = headerIndex(rows[0])\n const cell = (row: string[], name: string): string => {\n const index = columns.get(name.toLowerCase())\n return index == null ? '' : (row[index] ?? '').trim()\n }\n if (!columns.has('handle') || !columns.has('title')) {\n return {\n products: [],\n errors: ['Missing required columns: Handle, Title'],\n }\n }\n\n const byHandle = new Map<string, HostProduct>()\n const errors: string[] = []\n rows.slice(1).forEach((row, rowIndex) => {\n const line = rowIndex + 2\n const handle = commerceSlug(cell(row, 'handle'))\n if (!handle) {\n if (row.some((value) => value.trim())) {\n errors.push(`Row ${line}: missing Handle`)\n }\n return\n }\n let product = byHandle.get(handle)\n const title = cell(row, 'title')\n if (!product) {\n if (!title) {\n // Image-only continuation row for a product we never saw.\n errors.push(`Row ${line}: first row for \"${handle}\" needs a Title`)\n return\n }\n const published = cell(row, 'published').toLowerCase()\n const type = cell(row, 'type').toLowerCase()\n product = {\n name: title,\n slug: handle,\n description: cell(row, 'body (html)') || undefined,\n type: ['physical', 'digital', 'service'].includes(type)\n ? (type as HostProduct['type'])\n : 'physical',\n status: (published === 'false'\n ? 'draft'\n : 'active') as ProductStatus,\n tags: cell(row, 'tags')\n ? cell(row, 'tags')\n .split(',')\n .map((tag) => tag.trim())\n .filter(Boolean)\n : undefined,\n options: [],\n variants: [],\n mediaUrls: [],\n }\n byHandle.set(handle, product)\n }\n\n const imageSrc = cell(row, 'image src')\n if (imageSrc && !(product.mediaUrls ?? []).includes(imageSrc)) {\n product.mediaUrls = [...(product.mediaUrls ?? []), imageSrc]\n }\n\n const priceRaw = cell(row, 'variant price')\n const hasVariant = priceRaw !== '' || cell(row, 'variant sku') !== ''\n if (!hasVariant) return\n\n const options: Record<string, string> = {}\n for (let axis = 1; axis <= 3; axis += 1) {\n const name = cell(row, `option${axis} name`)\n const value = cell(row, `option${axis} value`)\n if (name && product.options![axis - 1] == null) {\n product.options![axis - 1] = { name, values: [] }\n }\n const optionName = product.options![axis - 1]?.name\n if (optionName && value) {\n options[optionName] = value\n const axisOption = product.options![axis - 1] as ProductOption\n if (!axisOption.values.includes(value)) {\n axisOption.values.push(value)\n }\n }\n }\n\n const price = Number(priceRaw)\n if (!Number.isFinite(price) || price < 0) {\n errors.push(`Row ${line}: invalid Variant Price \"${priceRaw}\"`)\n return\n }\n const qtyRaw = cell(row, 'variant inventory qty')\n const compareRaw = cell(row, 'variant compare at price')\n const gramsRaw = cell(row, 'variant grams')\n const variant: ProductVariant = {\n id: `v${product.variants.length + 1}`,\n ...(Object.keys(options).length ? { options } : {}),\n priceUsd: price,\n ...(cell(row, 'variant sku') ? { sku: cell(row, 'variant sku') } : {}),\n ...(cell(row, 'variant barcode')\n ? { barcode: cell(row, 'variant barcode') }\n : {}),\n ...(compareRaw && Number.isFinite(Number(compareRaw))\n ? { compareAtPriceUsd: Number(compareRaw) }\n : {}),\n inventory: qtyRaw === '' ? null : Math.max(0, Math.round(Number(qtyRaw))),\n ...(gramsRaw && Number.isFinite(Number(gramsRaw))\n ? { weightGrams: Number(gramsRaw) }\n : {}),\n }\n product.variants.push(variant)\n })\n\n const products: HostProduct[] = []\n for (const [handle, product] of byHandle) {\n product.options = (product.options ?? []).filter(Boolean)\n if (product.options.length === 0) delete product.options\n if ((product.mediaUrls?.length ?? 0) === 0) delete product.mediaUrls\n if (product.variants.length === 0) {\n errors.push(`\"${handle}\": no variant rows (needs Variant Price)`)\n continue\n }\n const problem = validateProduct(product)\n if (problem) {\n errors.push(`\"${handle}\": ${problem}`)\n continue\n }\n products.push(product)\n }\n return { products, errors }\n}\n"],"names":["commerceSlug","validateProduct","parseCsv","PRODUCT_CSV_HEADER","csvCell","value","text","String","test","replace","productsToCsv","products","lines","join","product","options","variants","forEach","variant","index","optionCells","axis","option","push","name","slug","description","type","tags","status","map","sku","barcode","priceUsd","compareAtPriceUsd","inventory","weightGrams","mediaUrls","url","slice","Array","length","fill","headerIndex","header","Map","cell","set","trim","toLowerCase","parseProductsCsv","rows","errors","columns","row","get","has","byHandle","rowIndex","line","handle","some","title","published","undefined","includes","split","tag","filter","Boolean","imageSrc","priceRaw","hasVariant","values","optionName","axisOption","price","Number","isFinite","qtyRaw","compareRaw","gramsRaw","id","Object","keys","Math","max","round","problem"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-io.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\n/**\n * Catalog CSV import/export (AGL-282) speaking Shopify's product CSV\n * dialect — the migration wedge for switchers: `Handle` groups variant\n * rows into one product, `Option1..3 Name/Value` build the option axes,\n * `Variant *` columns carry per-variant data, and repeated-handle rows\n * with only `Image Src` add media. Export mirrors the same columns so\n * round-trips are lossless for the fields both sides share.\n */\n\nimport {\n commerceSlug,\n validateProduct,\n type HostProduct,\n type ProductOption,\n type ProductStatus,\n type ProductVariant,\n} from './commerce'\nimport { parseCsv } from '@aglyn/aglyn/app-utils/csv'\n\nexport const PRODUCT_CSV_HEADER = [\n 'Handle',\n 'Title',\n 'Body (HTML)',\n 'Type',\n 'Tags',\n 'Published',\n 'Option1 Name',\n 'Option1 Value',\n 'Option2 Name',\n 'Option2 Value',\n 'Option3 Name',\n 'Option3 Value',\n 'Variant SKU',\n 'Variant Barcode',\n 'Variant Price',\n 'Variant Compare At Price',\n 'Variant Inventory Qty',\n 'Variant Grams',\n 'Image Src',\n] as const\n\nfunction csvCell(value: unknown): string {\n const text = value == null ? '' : String(value)\n return /[\",\\n]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text\n}\n\n/** One row per variant; extra image rows follow Shopify's convention. */\nexport function productsToCsv(\n products: Array<HostProduct & { $id?: string }>,\n): string {\n const lines = [PRODUCT_CSV_HEADER.join(',')]\n for (const product of products) {\n const options = product.options ?? []\n product.variants.forEach((variant, index) => {\n const optionCells: string[] = []\n for (let axis = 0; axis < 3; axis += 1) {\n const option = options[axis]\n optionCells.push(\n index === 0 ? (option?.name ?? '') : '',\n option ? (variant.options?.[option.name] ?? '') : '',\n )\n }\n lines.push(\n [\n csvCell(product.slug),\n csvCell(index === 0 ? product.name : ''),\n csvCell(index === 0 ? (product.description ?? '') : ''),\n csvCell(index === 0 ? product.type : ''),\n csvCell(index === 0 ? (product.tags ?? []).join(', ') : ''),\n csvCell(index === 0 ? String(product.status === 'active') : ''),\n ...optionCells.map(csvCell),\n csvCell(variant.sku ?? ''),\n csvCell(variant.barcode ?? ''),\n csvCell(variant.priceUsd),\n csvCell(variant.compareAtPriceUsd ?? ''),\n csvCell(variant.inventory ?? ''),\n csvCell(variant.weightGrams ?? ''),\n csvCell(index === 0 ? (product.mediaUrls?.[0] ?? '') : ''),\n ].join(','),\n )\n })\n for (const url of (product.mediaUrls ?? []).slice(1)) {\n lines.push(\n [\n csvCell(product.slug),\n ...Array(PRODUCT_CSV_HEADER.length - 2).fill(''),\n csvCell(url),\n ].join(','),\n )\n }\n }\n return lines.join('\\n')\n}\n\nexport interface ProductCsvImport {\n products: HostProduct[]\n /** Row-anchored problems; importable products still parse. */\n errors: string[]\n}\n\n/** Case-insensitive column lookup so hand-edited headers still map. */\nfunction headerIndex(header: string[]): Map<string, number> {\n const map = new Map<string, number>()\n header.forEach((cell, index) =>\n map.set(cell.trim().toLowerCase(), index),\n )\n return map\n}\n\nexport function parseProductsCsv(text: string): ProductCsvImport {\n const rows = parseCsv(text)\n if (rows.length < 2) {\n return { products: [], errors: ['No data rows found'] }\n }\n const columns = headerIndex(rows[0])\n const cell = (row: string[], name: string): string => {\n const index = columns.get(name.toLowerCase())\n return index == null ? '' : (row[index] ?? '').trim()\n }\n if (!columns.has('handle') || !columns.has('title')) {\n return {\n products: [],\n errors: ['Missing required columns: Handle, Title'],\n }\n }\n\n const byHandle = new Map<string, HostProduct>()\n const errors: string[] = []\n rows.slice(1).forEach((row, rowIndex) => {\n const line = rowIndex + 2\n const handle = commerceSlug(cell(row, 'handle'))\n if (!handle) {\n if (row.some((value) => value.trim())) {\n errors.push(`Row ${line}: missing Handle`)\n }\n return\n }\n let product = byHandle.get(handle)\n const title = cell(row, 'title')\n if (!product) {\n if (!title) {\n // Image-only continuation row for a product we never saw.\n errors.push(`Row ${line}: first row for \"${handle}\" needs a Title`)\n return\n }\n const published = cell(row, 'published').toLowerCase()\n const type = cell(row, 'type').toLowerCase()\n product = {\n name: title,\n slug: handle,\n description: cell(row, 'body (html)') || undefined,\n type: ['physical', 'digital', 'service'].includes(type)\n ? (type as HostProduct['type'])\n : 'physical',\n status: (published === 'false'\n ? 'draft'\n : 'active') as ProductStatus,\n tags: cell(row, 'tags')\n ? cell(row, 'tags')\n .split(',')\n .map((tag) => tag.trim())\n .filter(Boolean)\n : undefined,\n options: [],\n variants: [],\n mediaUrls: [],\n }\n byHandle.set(handle, product)\n }\n\n const imageSrc = cell(row, 'image src')\n if (imageSrc && !(product.mediaUrls ?? []).includes(imageSrc)) {\n product.mediaUrls = [...(product.mediaUrls ?? []), imageSrc]\n }\n\n const priceRaw = cell(row, 'variant price')\n const hasVariant = priceRaw !== '' || cell(row, 'variant sku') !== ''\n if (!hasVariant) return\n\n const options: Record<string, string> = {}\n for (let axis = 1; axis <= 3; axis += 1) {\n const name = cell(row, `option${axis} name`)\n const value = cell(row, `option${axis} value`)\n if (name && product.options![axis - 1] == null) {\n product.options![axis - 1] = { name, values: [] }\n }\n const optionName = product.options![axis - 1]?.name\n if (optionName && value) {\n options[optionName] = value\n const axisOption = product.options![axis - 1] as ProductOption\n if (!axisOption.values.includes(value)) {\n axisOption.values.push(value)\n }\n }\n }\n\n const price = Number(priceRaw)\n if (!Number.isFinite(price) || price < 0) {\n errors.push(`Row ${line}: invalid Variant Price \"${priceRaw}\"`)\n return\n }\n const qtyRaw = cell(row, 'variant inventory qty')\n const compareRaw = cell(row, 'variant compare at price')\n const gramsRaw = cell(row, 'variant grams')\n const variant: ProductVariant = {\n id: `v${product.variants.length + 1}`,\n ...(Object.keys(options).length ? { options } : {}),\n priceUsd: price,\n ...(cell(row, 'variant sku') ? { sku: cell(row, 'variant sku') } : {}),\n ...(cell(row, 'variant barcode')\n ? { barcode: cell(row, 'variant barcode') }\n : {}),\n ...(compareRaw && Number.isFinite(Number(compareRaw))\n ? { compareAtPriceUsd: Number(compareRaw) }\n : {}),\n inventory: qtyRaw === '' ? null : Math.max(0, Math.round(Number(qtyRaw))),\n ...(gramsRaw && Number.isFinite(Number(gramsRaw))\n ? { weightGrams: Number(gramsRaw) }\n : {}),\n }\n product.variants.push(variant)\n })\n\n const products: HostProduct[] = []\n for (const [handle, product] of byHandle) {\n product.options = (product.options ?? []).filter(Boolean)\n if (product.options.length === 0) delete product.options\n if ((product.mediaUrls?.length ?? 0) === 0) delete product.mediaUrls\n if (product.variants.length === 0) {\n errors.push(`\"${handle}\": no variant rows (needs Variant Price)`)\n continue\n }\n const problem = validateProduct(product)\n if (problem) {\n errors.push(`\"${handle}\": ${problem}`)\n continue\n }\n products.push(product)\n }\n return { products, errors }\n}\n"],"names":["commerceSlug","validateProduct","parseCsv","PRODUCT_CSV_HEADER","csvCell","value","text","String","test","replace","productsToCsv","products","lines","join","product","options","variants","forEach","variant","index","optionCells","axis","option","push","name","slug","description","type","tags","status","map","sku","barcode","priceUsd","compareAtPriceUsd","inventory","weightGrams","mediaUrls","url","slice","Array","length","fill","headerIndex","header","Map","cell","set","trim","toLowerCase","parseProductsCsv","rows","errors","columns","row","get","has","byHandle","rowIndex","line","handle","some","title","published","undefined","includes","split","tag","filter","Boolean","imageSrc","priceRaw","hasVariant","values","optionName","axisOption","price","Number","isFinite","qtyRaw","compareRaw","gramsRaw","id","Object","keys","Math","max","round","problem"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginRecordListSource } from '@aglyn/aglyn/plugin-manager/plugin-record-lists';
18
+ /**
19
+ * A site's products, FOUND in the console for another plugin's picker
20
+ * (AGL-3080) — a deal adding a product as a line, priced from the catalog.
21
+ *
22
+ * A search, not a listing: the first word typed is matched as a prefix of a
23
+ * word in an ACTIVE product's name, ordered by name — a draft or archived
24
+ * product is not offered for sale. With nothing typed, at the organization,
25
+ * where no catalog lives, and for a listing, from which no product is
26
+ * installed, the source answers none. Each record is `productIndexedRecord`'s,
27
+ * the server index's shape, with its price and its priced variants.
28
+ */
29
+ export declare const productRecordListSource: PluginRecordListSource;
30
+ /** Called from the console registrar, owner named for a spec that calls it directly. */
31
+ export declare function registerCommerceRecordLists(): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { nameSearchToken } from "@aglyn/aglyn/app-utils/name-search";
17
+ import { registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
18
+ import { collection, limit, orderBy, query, where } from "firebase/firestore";
19
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
20
+ import { productIndexedRecord } from "./product-record.js";
21
+ /**
22
+ * A site's products, FOUND in the console for another plugin's picker
23
+ * (AGL-3080) — a deal adding a product as a line, priced from the catalog.
24
+ *
25
+ * A search, not a listing: the first word typed is matched as a prefix of a
26
+ * word in an ACTIVE product's name, ordered by name — a draft or archived
27
+ * product is not offered for sale. With nothing typed, at the organization,
28
+ * where no catalog lives, and for a listing, from which no product is
29
+ * installed, the source answers none. Each record is `productIndexedRecord`'s,
30
+ * the server index's shape, with its price and its priced variants.
31
+ */ export const productRecordListSource = {
32
+ query (firestore, request) {
33
+ const token = nameSearchToken(request.search);
34
+ if (!request.hostId || !token || request.installedFrom) return null;
35
+ return query(collection(firestore, 'hosts', request.hostId, 'products'), where('status', '==', 'active'), where('nameTokens', 'array-contains', token), orderBy('nameLower'), limit(request.limit));
36
+ },
37
+ record: productIndexedRecord
38
+ };
39
+ /** Called from the console registrar, owner named for a spec that calls it directly. */ export function registerCommerceRecordLists() {
40
+ registerPluginRecordListSource('product', productRecordListSource, {
41
+ pluginId: BUNDLE_ID
42
+ });
43
+ }
44
+
45
+ //# sourceMappingURL=commerce-record-lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-record-lists.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 { nameSearchToken } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n registerPluginRecordListSource,\n type PluginRecordListSource,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { productIndexedRecord } from './product-record'\n\n/**\n * A site's products, FOUND in the console for another plugin's picker\n * (AGL-3080) — a deal adding a product as a line, priced from the catalog.\n *\n * A search, not a listing: the first word typed is matched as a prefix of a\n * word in an ACTIVE product's name, ordered by name — a draft or archived\n * product is not offered for sale. With nothing typed, at the organization,\n * where no catalog lives, and for a listing, from which no product is\n * installed, the source answers none. Each record is `productIndexedRecord`'s,\n * the server index's shape, with its price and its priced variants.\n */\nexport const productRecordListSource: PluginRecordListSource = {\n query(firestore, request) {\n const token = nameSearchToken(request.search)\n if (!request.hostId || !token || request.installedFrom) return null\n return query(\n collection(firestore, 'hosts', request.hostId, 'products'),\n where('status', '==', 'active'),\n where('nameTokens', 'array-contains', token),\n orderBy('nameLower'),\n limit(request.limit),\n )\n },\n record: productIndexedRecord,\n}\n\n/** Called from the console registrar, owner named for a spec that calls it directly. */\nexport function registerCommerceRecordLists(): void {\n registerPluginRecordListSource('product', productRecordListSource, { pluginId: BUNDLE_ID })\n}\n"],"names":["nameSearchToken","registerPluginRecordListSource","collection","limit","orderBy","query","where","BUNDLE_ID","productIndexedRecord","productRecordListSource","firestore","request","token","search","hostId","installedFrom","record","registerCommerceRecordLists","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,qCAAoC;AACpE,SACEC,8BAA8B,QAEzB,kDAAiD;AACxD,SAASC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AAC7E,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,oBAAoB,QAAQ,sBAAkB;AAEvD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,0BAAkD;IAC7DJ,OAAMK,SAAS,EAAEC,OAAO;QACtB,MAAMC,QAAQZ,gBAAgBW,QAAQE,MAAM;QAC5C,IAAI,CAACF,QAAQG,MAAM,IAAI,CAACF,SAASD,QAAQI,aAAa,EAAE,OAAO;QAC/D,OAAOV,MACLH,WAAWQ,WAAW,SAASC,QAAQG,MAAM,EAAE,aAC/CR,MAAM,UAAU,MAAM,WACtBA,MAAM,cAAc,kBAAkBM,QACtCR,QAAQ,cACRD,MAAMQ,QAAQR,KAAK;IAEvB;IACAa,QAAQR;AACV,EAAC;AAED,sFAAsF,GACtF,OAAO,SAASS;IACdhB,+BAA+B,WAAWQ,yBAAyB;QAAES,UAAUX;IAAU;AAC3F"}
@@ -11,6 +11,24 @@
11
11
  * See the License for the specific language governing permissions and
12
12
  * limitations under the License.
13
13
  */
14
+ /**
15
+ * Where a product and an order are read, published for every other surface
16
+ * (AGL-3080).
17
+ *
18
+ * A surface elsewhere — the AI plugin's job drawer opening a product it
19
+ * drafted, a contact's timeline opening the order it names, a person's page
20
+ * listing their orders — asks the record-route registry for `product` or
21
+ * `order` rather than spelling this plugin's console path, and gets `null`
22
+ * (text instead of a link) where this plugin is not loaded.
23
+ *
24
+ * Both are a site's, so only a site has an address. The catalog has no page
25
+ * per product — a product opens in the hub's editor — so the record answers
26
+ * the catalog a caller would find it in. An order opens as a dialog over the
27
+ * Orders list, which reads {@link ORDERS_ORDER_PARAM} on arrival; the list
28
+ * narrowed to one buyer reads {@link ORDERS_CUSTOMER_PARAM}.
29
+ */
30
+ /** The nav slug the shell resolves the catalog by. */
31
+ export declare const PRODUCTS_SLUG = "products";
14
32
  /** The query key the Orders list opens one order's dialog by. */
15
33
  export declare const ORDERS_ORDER_PARAM = "order";
16
34
  /**
@@ -28,7 +28,7 @@ import { BUNDLE_ID } from "../constants/bundle-common.js";
28
28
  * the catalog a caller would find it in. An order opens as a dialog over the
29
29
  * Orders list, which reads {@link ORDERS_ORDER_PARAM} on arrival; the list
30
30
  * narrowed to one buyer reads {@link ORDERS_CUSTOMER_PARAM}.
31
- */ /** The nav slug the shell resolves the catalog by. */ const PRODUCTS_SLUG = 'products';
31
+ */ /** The nav slug the shell resolves the catalog by. */ export const PRODUCTS_SLUG = 'products';
32
32
  /** The query key the Orders list opens one order's dialog by. */ export const ORDERS_ORDER_PARAM = 'order';
33
33
  /**
34
34
  * The query key the Orders list narrows to one buyer by — the same word the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-record-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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\n\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n registerPluginRecordRoute,\n type PluginRecordRouteContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * Where a product and an order are read, published for every other surface\n * (AGL-3080).\n *\n * A surface elsewhere — the AI plugin's job drawer opening a product it\n * drafted, a contact's timeline opening the order it names, a person's page\n * listing their orders — asks the record-route registry for `product` or\n * `order` rather than spelling this plugin's console path, and gets `null`\n * (text instead of a link) where this plugin is not loaded.\n *\n * Both are a site's, so only a site has an address. The catalog has no page\n * per product — a product opens in the hub's editor — so the record answers\n * the catalog a caller would find it in. An order opens as a dialog over the\n * Orders list, which reads {@link ORDERS_ORDER_PARAM} on arrival; the list\n * narrowed to one buyer reads {@link ORDERS_CUSTOMER_PARAM}.\n */\n\n/** The nav slug the shell resolves the catalog by. */\nconst PRODUCTS_SLUG = 'products'\n\n/** The query key the Orders list opens one order's dialog by. */\nexport const ORDERS_ORDER_PARAM = 'order'\n\n/**\n * The query key the Orders list narrows to one buyer by — the same word the\n * CRM's contacts list opens a person by, so a reader who edits one URL into\n * the other is not surprised.\n */\nexport const ORDERS_CUSTOMER_PARAM = 'email'\n\nfunction catalog(context: PluginRecordRouteContext): string | null {\n return context.host\n ? buildRoute(Route.HOST_PLUGIN, {\n orgSlug: context.orgSlug,\n host: context.host,\n pluginSlug: PRODUCTS_SLUG,\n })\n : null\n}\n\nfunction orders(context: PluginRecordRouteContext, key?: string, value?: string): string | null {\n const list = catalog(context)\n if (!list) return null\n return key && value\n ? `${list}/orders?${new URLSearchParams({ [key]: value }).toString()}`\n : `${list}/orders`\n}\n\n/**\n * Called from the console registrar. The owner is named rather than read off\n * the loader's marker, so the route registers the same way under a spec that\n * calls the registrar directly.\n */\nexport function registerCommerceRecordRoutes(): void {\n registerPluginRecordRoute(\n 'product',\n { list: catalog, record: catalog },\n { pluginId: BUNDLE_ID },\n )\n registerPluginRecordRoute(\n 'order',\n {\n list: (context) => orders(context),\n record: (context, id) => orders(context, ORDERS_ORDER_PARAM, id),\n byEmail: (context, email) => orders(context, ORDERS_CUSTOMER_PARAM, email),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","PRODUCTS_SLUG","ORDERS_ORDER_PARAM","ORDERS_CUSTOMER_PARAM","catalog","context","host","HOST_PLUGIN","orgSlug","pluginSlug","orders","key","value","list","URLSearchParams","toString","registerCommerceRecordRoutes","record","pluginId","id","byEmail","email"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GAGD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,yBAAyB,QAEpB,mDAAkD;AACzD,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;;;;;;;;CAeC,GAED,oDAAoD,GACpD,MAAMC,gBAAgB;AAEtB,+DAA+D,GAC/D,OAAO,MAAMC,qBAAqB,QAAO;AAEzC;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,QAAO;AAE5C,SAASC,QAAQC,OAAiC;IAChD,OAAOA,QAAQC,IAAI,GACfT,WAAWC,MAAMS,WAAW,EAAE;QAC5BC,SAASH,QAAQG,OAAO;QACxBF,MAAMD,QAAQC,IAAI;QAClBG,YAAYR;IACd,KACA;AACN;AAEA,SAASS,OAAOL,OAAiC,EAAEM,GAAY,EAAEC,KAAc;IAC7E,MAAMC,OAAOT,QAAQC;IACrB,IAAI,CAACQ,MAAM,OAAO;IAClB,OAAOF,OAAOC,QACV,GAAGC,KAAK,QAAQ,EAAE,IAAIC,gBAAgB;QAAE,CAACH,IAAI,EAAEC;IAAM,GAAGG,QAAQ,IAAI,GACpE,GAAGF,KAAK,OAAO,CAAC;AACtB;AAEA;;;;CAIC,GACD,OAAO,SAASG;IACdjB,0BACE,WACA;QAAEc,MAAMT;QAASa,QAAQb;IAAQ,GACjC;QAAEc,UAAUlB;IAAU;IAExBD,0BACE,SACA;QACEc,MAAM,CAACR,UAAYK,OAAOL;QAC1BY,QAAQ,CAACZ,SAASc,KAAOT,OAAOL,SAASH,oBAAoBiB;QAC7DC,SAAS,CAACf,SAASgB,QAAUX,OAAOL,SAASF,uBAAuBkB;IACtE,GACA;QAAEH,UAAUlB;IAAU;AAE1B"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-record-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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\n\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n registerPluginRecordRoute,\n type PluginRecordRouteContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * Where a product and an order are read, published for every other surface\n * (AGL-3080).\n *\n * A surface elsewhere — the AI plugin's job drawer opening a product it\n * drafted, a contact's timeline opening the order it names, a person's page\n * listing their orders — asks the record-route registry for `product` or\n * `order` rather than spelling this plugin's console path, and gets `null`\n * (text instead of a link) where this plugin is not loaded.\n *\n * Both are a site's, so only a site has an address. The catalog has no page\n * per product — a product opens in the hub's editor — so the record answers\n * the catalog a caller would find it in. An order opens as a dialog over the\n * Orders list, which reads {@link ORDERS_ORDER_PARAM} on arrival; the list\n * narrowed to one buyer reads {@link ORDERS_CUSTOMER_PARAM}.\n */\n\n/** The nav slug the shell resolves the catalog by. */\nexport const PRODUCTS_SLUG = 'products'\n\n/** The query key the Orders list opens one order's dialog by. */\nexport const ORDERS_ORDER_PARAM = 'order'\n\n/**\n * The query key the Orders list narrows to one buyer by — the same word the\n * CRM's contacts list opens a person by, so a reader who edits one URL into\n * the other is not surprised.\n */\nexport const ORDERS_CUSTOMER_PARAM = 'email'\n\nfunction catalog(context: PluginRecordRouteContext): string | null {\n return context.host\n ? buildRoute(Route.HOST_PLUGIN, {\n orgSlug: context.orgSlug,\n host: context.host,\n pluginSlug: PRODUCTS_SLUG,\n })\n : null\n}\n\nfunction orders(context: PluginRecordRouteContext, key?: string, value?: string): string | null {\n const list = catalog(context)\n if (!list) return null\n return key && value\n ? `${list}/orders?${new URLSearchParams({ [key]: value }).toString()}`\n : `${list}/orders`\n}\n\n/**\n * Called from the console registrar. The owner is named rather than read off\n * the loader's marker, so the route registers the same way under a spec that\n * calls the registrar directly.\n */\nexport function registerCommerceRecordRoutes(): void {\n registerPluginRecordRoute(\n 'product',\n { list: catalog, record: catalog },\n { pluginId: BUNDLE_ID },\n )\n registerPluginRecordRoute(\n 'order',\n {\n list: (context) => orders(context),\n record: (context, id) => orders(context, ORDERS_ORDER_PARAM, id),\n byEmail: (context, email) => orders(context, ORDERS_CUSTOMER_PARAM, email),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","PRODUCTS_SLUG","ORDERS_ORDER_PARAM","ORDERS_CUSTOMER_PARAM","catalog","context","host","HOST_PLUGIN","orgSlug","pluginSlug","orders","key","value","list","URLSearchParams","toString","registerCommerceRecordRoutes","record","pluginId","id","byEmail","email"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GAGD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,yBAAyB,QAEpB,mDAAkD;AACzD,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;;;;;;;;CAeC,GAED,oDAAoD,GACpD,OAAO,MAAMC,gBAAgB,WAAU;AAEvC,+DAA+D,GAC/D,OAAO,MAAMC,qBAAqB,QAAO;AAEzC;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,QAAO;AAE5C,SAASC,QAAQC,OAAiC;IAChD,OAAOA,QAAQC,IAAI,GACfT,WAAWC,MAAMS,WAAW,EAAE;QAC5BC,SAASH,QAAQG,OAAO;QACxBF,MAAMD,QAAQC,IAAI;QAClBG,YAAYR;IACd,KACA;AACN;AAEA,SAASS,OAAOL,OAAiC,EAAEM,GAAY,EAAEC,KAAc;IAC7E,MAAMC,OAAOT,QAAQC;IACrB,IAAI,CAACQ,MAAM,OAAO;IAClB,OAAOF,OAAOC,QACV,GAAGC,KAAK,QAAQ,EAAE,IAAIC,gBAAgB;QAAE,CAACH,IAAI,EAAEC;IAAM,GAAGG,QAAQ,IAAI,GACpE,GAAGF,KAAK,OAAO,CAAC;AACtB;AAEA;;;;CAIC,GACD,OAAO,SAASG;IACdjB,0BACE,WACA;QAAEc,MAAMT;QAASa,QAAQb;IAAQ,GACjC;QAAEc,UAAUlB;IAAU;IAExBD,0BACE,SACA;QACEc,MAAM,CAACR,UAAYK,OAAOL;QAC1BY,QAAQ,CAACZ,SAASc,KAAOT,OAAOL,SAASH,oBAAoBiB;QAC7DC,SAAS,CAACf,SAASgB,QAAUX,OAAOL,SAASF,uBAAuBkB;IACtE,GACA;QAAEH,UAAUlB;IAAU;AAE1B"}
@@ -11,6 +11,7 @@
11
11
  * See the License for the specific language governing permissions and
12
12
  * limitations under the License.
13
13
  */ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
14
+ import { PRODUCTS_SLUG } from "./commerce-record-routes.js";
14
15
  /**
15
16
  * What the console's search finds in a store (AGL-3080): its products, by
16
17
  * name or slug, opening on the site's Products page. Read only where the plan
@@ -30,9 +31,10 @@
30
31
  entitlementKey: 'productsPerHost',
31
32
  featureFlag: 'commerce',
32
33
  order: 160,
33
- href: (_row, { orgSlug, host })=>host ? buildRoute(Route.HOST_PRODUCTS, {
34
+ href: (_row, { orgSlug, host })=>host ? buildRoute(Route.HOST_PLUGIN, {
34
35
  orgSlug,
35
- host
36
+ host,
37
+ pluginSlug: PRODUCTS_SLUG
36
38
  }) : null
37
39
  }
38
40
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-search-sources.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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 type { ConsoleSearchSource } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\n\n/**\n * What the console's search finds in a store (AGL-3080): its products, by\n * name or slug, opening on the site's Products page. Read only where the plan\n * carries commerce and allows the site products, so a workspace that holds\n * none never spends a read on them.\n */\nexport const COMMERCE_SEARCH_SOURCES: readonly ConsoleSearchSource[] = [\n {\n id: 'products',\n group: 'Products',\n noun: 'products',\n scope: 'host',\n collection: 'products',\n nameField: 'name',\n extraFields: ['slug'],\n entitlementKey: 'productsPerHost',\n featureFlag: 'commerce',\n order: 160,\n href: (_row, { orgSlug, host }) =>\n host ? buildRoute(Route.HOST_PRODUCTS, { orgSlug, host }) : null,\n },\n]\n"],"names":["buildRoute","Route","COMMERCE_SEARCH_SOURCES","id","group","noun","scope","collection","nameField","extraFields","entitlementKey","featureFlag","order","href","_row","orgSlug","host","HOST_PRODUCTS"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GAGD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AAEzE;;;;;CAKC,GACD,OAAO,MAAMC,0BAA0D;IACrE;QACEC,IAAI;QACJC,OAAO;QACPC,MAAM;QACNC,OAAO;QACPC,YAAY;QACZC,WAAW;QACXC,aAAa;YAAC;SAAO;QACrBC,gBAAgB;QAChBC,aAAa;QACbC,OAAO;QACPC,MAAM,CAACC,MAAM,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAC5BA,OAAOhB,WAAWC,MAAMgB,aAAa,EAAE;gBAAEF;gBAASC;YAAK,KAAK;IAChE;CACD,CAAA"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-search-sources.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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 type { ConsoleSearchSource } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { PRODUCTS_SLUG } from './commerce-record-routes'\n\n/**\n * What the console's search finds in a store (AGL-3080): its products, by\n * name or slug, opening on the site's Products page. Read only where the plan\n * carries commerce and allows the site products, so a workspace that holds\n * none never spends a read on them.\n */\nexport const COMMERCE_SEARCH_SOURCES: readonly ConsoleSearchSource[] = [\n {\n id: 'products',\n group: 'Products',\n noun: 'products',\n scope: 'host',\n collection: 'products',\n nameField: 'name',\n extraFields: ['slug'],\n entitlementKey: 'productsPerHost',\n featureFlag: 'commerce',\n order: 160,\n href: (_row, { orgSlug, host }) =>\n host\n ? buildRoute(Route.HOST_PLUGIN, { orgSlug, host, pluginSlug: PRODUCTS_SLUG })\n : null,\n },\n]\n"],"names":["buildRoute","Route","PRODUCTS_SLUG","COMMERCE_SEARCH_SOURCES","id","group","noun","scope","collection","nameField","extraFields","entitlementKey","featureFlag","order","href","_row","orgSlug","host","HOST_PLUGIN","pluginSlug"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GAGD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,aAAa,QAAQ,8BAA0B;AAExD;;;;;CAKC,GACD,OAAO,MAAMC,0BAA0D;IACrE;QACEC,IAAI;QACJC,OAAO;QACPC,MAAM;QACNC,OAAO;QACPC,YAAY;QACZC,WAAW;QACXC,aAAa;YAAC;SAAO;QACrBC,gBAAgB;QAChBC,aAAa;QACbC,OAAO;QACPC,MAAM,CAACC,MAAM,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAC5BA,OACIjB,WAAWC,MAAMiB,WAAW,EAAE;gBAAEF;gBAASC;gBAAME,YAAYjB;YAAc,KACzE;IACR;CACD,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** Regular news and stories, including a site's posts to its members. */
18
+ export declare const NEWSLETTER_TOPIC_ID = "newsletter";
19
+ /** New products, restocks, and changes to what a site offers. */
20
+ export declare const PRODUCT_UPDATES_TOPIC_ID = "product-updates";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * THE STREAMS THIS PLUGIN'S MAIL IS SENT UNDER.
18
+ *
19
+ * Declared in `plugins.config.json` (`subscriptionTopics`), which is where
20
+ * their names, descriptions and preference-page order live; named here
21
+ * wherever a send declares which stream it belongs to, because a mistyped id
22
+ * does not fail loudly. It names a stream nobody has opted out of, so the
23
+ * send path finds no opt-out and the send goes to everybody — the control
24
+ * silently doing nothing.
25
+ *
26
+ * Each value is a wire format: a Firestore path component and a component of
27
+ * the unsubscribe link's signed subject, so changing one orphans every
28
+ * opt-out already recorded against it. A cart reminder rides the platform's
29
+ * default stream (`DEFAULT_SUBSCRIPTION_TOPIC_ID`), as a campaign does.
30
+ *=========================================*/ /** Regular news and stories, including a site's posts to its members. */ export const NEWSLETTER_TOPIC_ID = 'newsletter';
31
+ /** New products, restocks, and changes to what a site offers. */ export const PRODUCT_UPDATES_TOPIC_ID = 'product-updates';
32
+
33
+ //# sourceMappingURL=commerce-topics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-topics.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\n/*==========================================\n * THE STREAMS THIS PLUGIN'S MAIL IS SENT UNDER.\n *\n * Declared in `plugins.config.json` (`subscriptionTopics`), which is where\n * their names, descriptions and preference-page order live; named here\n * wherever a send declares which stream it belongs to, because a mistyped id\n * does not fail loudly. It names a stream nobody has opted out of, so the\n * send path finds no opt-out and the send goes to everybody — the control\n * silently doing nothing.\n *\n * Each value is a wire format: a Firestore path component and a component of\n * the unsubscribe link's signed subject, so changing one orphans every\n * opt-out already recorded against it. A cart reminder rides the platform's\n * default stream (`DEFAULT_SUBSCRIPTION_TOPIC_ID`), as a campaign does.\n *=========================================*/\n\n/** Regular news and stories, including a site's posts to its members. */\nexport const NEWSLETTER_TOPIC_ID = 'newsletter'\n\n/** New products, restocks, and changes to what a site offers. */\nexport const PRODUCT_UPDATES_TOPIC_ID = 'product-updates'\n"],"names":["NEWSLETTER_TOPIC_ID","PRODUCT_UPDATES_TOPIC_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;2CAc2C,GAE3C,uEAAuE,GACvE,OAAO,MAAMA,sBAAsB,aAAY;AAE/C,+DAA+D,GAC/D,OAAO,MAAMC,2BAA2B,kBAAiB"}
@@ -26,6 +26,7 @@ export * from './commerce-cart';
26
26
  export * from './commerce-connect-transfer';
27
27
  export * from './commerce-discounts';
28
28
  export * from './commerce-dispute';
29
+ export * from './commerce-topics';
29
30
  export * from './commerce-gift-cards';
30
31
  export * from './commerce-io';
31
32
  export * from './commerce-orders';
@@ -24,6 +24,7 @@ export * from "./commerce-cart.js";
24
24
  export * from "./commerce-connect-transfer.js";
25
25
  export * from "./commerce-discounts.js";
26
26
  export * from "./commerce-dispute.js";
27
+ export * from "./commerce-topics.js";
27
28
  export * from "./commerce-gift-cards.js";
28
29
  export * from "./commerce-io.js";
29
30
  export * from "./commerce-orders.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/index.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\n/**\n * Commerce domain model (AGL-411): pure types + logic relocated from the\n * core `@aglyn/aglyn` app-utils — the commerce plugin owns its domain.\n * Context-free and framework-free, so both the plugin's client components\n * and its `/server` handlers (and other plugins/apps via\n * `@aglyn/plugins-commerce/model`) can import it.\n */\nexport * from './commerce'\nexport * from './commerce-cart'\nexport * from './commerce-connect-transfer'\nexport * from './commerce-discounts'\nexport * from './commerce-dispute'\nexport * from './commerce-gift-cards'\nexport * from './commerce-io'\nexport * from './commerce-orders'\nexport * from './commerce-promotions'\nexport * from './commerce-reservations'\nexport * from './commerce-shipping'\nexport * from './commerce-stock-holds'\nexport * from './commerce-subscription-status'\nexport * from './commerce-tax'\nexport * from './commerce-tax-decision'\nexport * from './order-figures'\nexport * from './order-list-fields'\nexport * from './purchase-analytics'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,iCAA6B;AAC3C,cAAc,0BAAsB;AACpC,cAAc,wBAAoB;AAClC,cAAc,2BAAuB;AACrC,cAAc,mBAAe;AAC7B,cAAc,uBAAmB;AACjC,cAAc,2BAAuB;AACrC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,oBAAgB;AAC9B,cAAc,6BAAyB;AACvC,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,0BAAsB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/index.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\n/**\n * Commerce domain model (AGL-411): pure types + logic relocated from the\n * core `@aglyn/aglyn` app-utils — the commerce plugin owns its domain.\n * Context-free and framework-free, so both the plugin's client components\n * and its `/server` handlers (and other plugins/apps via\n * `@aglyn/plugins-commerce/model`) can import it.\n */\nexport * from './commerce'\nexport * from './commerce-cart'\nexport * from './commerce-connect-transfer'\nexport * from './commerce-discounts'\nexport * from './commerce-dispute'\nexport * from './commerce-topics'\nexport * from './commerce-gift-cards'\nexport * from './commerce-io'\nexport * from './commerce-orders'\nexport * from './commerce-promotions'\nexport * from './commerce-reservations'\nexport * from './commerce-shipping'\nexport * from './commerce-stock-holds'\nexport * from './commerce-subscription-status'\nexport * from './commerce-tax'\nexport * from './commerce-tax-decision'\nexport * from './order-figures'\nexport * from './order-list-fields'\nexport * from './purchase-analytics'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,iCAA6B;AAC3C,cAAc,0BAAsB;AACpC,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,2BAAuB;AACrC,cAAc,mBAAe;AAC7B,cAAc,uBAAmB;AACjC,cAAc,2BAAuB;AACrC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,oBAAgB;AAC9B,cAAc,6BAAyB;AACvC,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,0BAAsB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
18
+ /**
19
+ * One stored product as this plugin shares it with another (AGL-3080) — the
20
+ * one shape its server index (`product-record-index.ts`) and its console list
21
+ * source (`commerce-record-lists.ts`) both answer.
22
+ *
23
+ * `null` for a deleted product and an unnamed one. Otherwise its `facts`:
24
+ * `type` (`physical` | `digital` | `service` | the stored value),
25
+ * `description`, `tags: string[]`, `categoryIds: string[]`,
26
+ * `options: Array<{ name, values: string[] }>`, `imageUrl` (the first
27
+ * photo's stored media value, or `null`), `seoTitle`, `seoDescription`,
28
+ * `priceUsd` (the product's own price in dollars, or `null`) and
29
+ * `variants: Array<{ id, options: Record<string, string>, priceUsd }>` —
30
+ * each priced variant as stored, empty for a product without variants.
31
+ */
32
+ export declare function productIndexedRecord(id: string, data: Readonly<Record<string, unknown>> | undefined): PluginIndexedRecord | null;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * One stored product as this plugin shares it with another (AGL-3080) — the
18
+ * one shape its server index (`product-record-index.ts`) and its console list
19
+ * source (`commerce-record-lists.ts`) both answer.
20
+ *
21
+ * `null` for a deleted product and an unnamed one. Otherwise its `facts`:
22
+ * `type` (`physical` | `digital` | `service` | the stored value),
23
+ * `description`, `tags: string[]`, `categoryIds: string[]`,
24
+ * `options: Array<{ name, values: string[] }>`, `imageUrl` (the first
25
+ * photo's stored media value, or `null`), `seoTitle`, `seoDescription`,
26
+ * `priceUsd` (the product's own price in dollars, or `null`) and
27
+ * `variants: Array<{ id, options: Record<string, string>, priceUsd }>` —
28
+ * each priced variant as stored, empty for a product without variants.
29
+ */ export function productIndexedRecord(id, data) {
30
+ var _data_seo;
31
+ if (!data || data['deletedAt'] != null) return null;
32
+ const name = str(data['name']);
33
+ if (!name) return null;
34
+ const seo = (_data_seo = data['seo']) != null ? _data_seo : {};
35
+ const mediaUrls = Array.isArray(data['mediaUrls']) ? data['mediaUrls'] : [];
36
+ return {
37
+ id,
38
+ name,
39
+ facts: {
40
+ type: data['type'],
41
+ description: str(data['description']),
42
+ tags: strings(data['tags']),
43
+ categoryIds: strings(data['categoryIds']),
44
+ options: (Array.isArray(data['options']) ? data['options'] : []).map((option)=>{
45
+ const record = option != null ? option : {};
46
+ return {
47
+ name: str(record['name']),
48
+ values: strings(record['values'])
49
+ };
50
+ }),
51
+ imageUrl: str(mediaUrls[0]) || str(data['imageUrl']) || null,
52
+ seoTitle: str(seo['title']),
53
+ seoDescription: str(seo['description']),
54
+ priceUsd: price(data['priceUsd']),
55
+ variants: (Array.isArray(data['variants']) ? data['variants'] : []).map((variant)=>{
56
+ var _record_options;
57
+ const record = variant != null ? variant : {};
58
+ const options = (_record_options = record['options']) != null ? _record_options : {};
59
+ return {
60
+ id: str(record['id']),
61
+ options: Object.fromEntries(Object.entries(options).map(([key, value])=>[
62
+ key,
63
+ str(value)
64
+ ])),
65
+ priceUsd: price(record['priceUsd'])
66
+ };
67
+ })
68
+ }
69
+ };
70
+ }
71
+ function str(value) {
72
+ return typeof value === 'string' ? value : '';
73
+ }
74
+ function strings(value) {
75
+ return (Array.isArray(value) ? value : []).map(str).filter(Boolean);
76
+ }
77
+ function price(value) {
78
+ const parsed = typeof value === 'number' ? value : Number(value);
79
+ return value === null || value === undefined || value === '' || !Number.isFinite(parsed) ? null : parsed;
80
+ }
81
+
82
+ //# sourceMappingURL=product-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/product-record.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 type { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\n\n/**\n * One stored product as this plugin shares it with another (AGL-3080) — the\n * one shape its server index (`product-record-index.ts`) and its console list\n * source (`commerce-record-lists.ts`) both answer.\n *\n * `null` for a deleted product and an unnamed one. Otherwise its `facts`:\n * `type` (`physical` | `digital` | `service` | the stored value),\n * `description`, `tags: string[]`, `categoryIds: string[]`,\n * `options: Array<{ name, values: string[] }>`, `imageUrl` (the first\n * photo's stored media value, or `null`), `seoTitle`, `seoDescription`,\n * `priceUsd` (the product's own price in dollars, or `null`) and\n * `variants: Array<{ id, options: Record<string, string>, priceUsd }>` —\n * each priced variant as stored, empty for a product without variants.\n */\nexport function productIndexedRecord(\n id: string,\n data: Readonly<Record<string, unknown>> | undefined,\n): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const name = str(data['name'])\n if (!name) return null\n const seo = (data['seo'] ?? {}) as Record<string, unknown>\n const mediaUrls = Array.isArray(data['mediaUrls']) ? data['mediaUrls'] : []\n return {\n id,\n name,\n facts: {\n type: data['type'],\n description: str(data['description']),\n tags: strings(data['tags']),\n categoryIds: strings(data['categoryIds']),\n options: (Array.isArray(data['options']) ? data['options'] : []).map((option) => {\n const record = (option ?? {}) as Record<string, unknown>\n return { name: str(record['name']), values: strings(record['values']) }\n }),\n imageUrl: str(mediaUrls[0]) || str(data['imageUrl']) || null,\n seoTitle: str(seo['title']),\n seoDescription: str(seo['description']),\n priceUsd: price(data['priceUsd']),\n variants: (Array.isArray(data['variants']) ? data['variants'] : []).map((variant) => {\n const record = (variant ?? {}) as Record<string, unknown>\n const options = (record['options'] ?? {}) as Record<string, unknown>\n return {\n id: str(record['id']),\n options: Object.fromEntries(Object.entries(options).map(([key, value]) => [key, str(value)])),\n priceUsd: price(record['priceUsd']),\n }\n }),\n },\n }\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\nfunction strings(value: unknown): string[] {\n return (Array.isArray(value) ? value : []).map(str).filter(Boolean)\n}\n\nfunction price(value: unknown): number | null {\n const parsed = typeof value === 'number' ? value : Number(value)\n return value === null || value === undefined || value === '' || !Number.isFinite(parsed) ? null : parsed\n}\n"],"names":["productIndexedRecord","id","data","name","str","seo","mediaUrls","Array","isArray","facts","type","description","tags","strings","categoryIds","options","map","option","record","values","imageUrl","seoTitle","seoDescription","priceUsd","price","variants","variant","Object","fromEntries","entries","key","value","filter","Boolean","parsed","Number","undefined","isFinite"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,qBACdC,EAAU,EACVC,IAAmD;QAKtCA;IAHb,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,OAAOC,IAAIF,IAAI,CAAC,OAAO;IAC7B,IAAI,CAACC,MAAM,OAAO;IAClB,MAAME,OAAOH,YAAAA,IAAI,CAAC,MAAM,YAAXA,YAAe,CAAC;IAC7B,MAAMI,YAAYC,MAAMC,OAAO,CAACN,IAAI,CAAC,YAAY,IAAIA,IAAI,CAAC,YAAY,GAAG,EAAE;IAC3E,OAAO;QACLD;QACAE;QACAM,OAAO;YACLC,MAAMR,IAAI,CAAC,OAAO;YAClBS,aAAaP,IAAIF,IAAI,CAAC,cAAc;YACpCU,MAAMC,QAAQX,IAAI,CAAC,OAAO;YAC1BY,aAAaD,QAAQX,IAAI,CAAC,cAAc;YACxCa,SAAS,AAACR,CAAAA,MAAMC,OAAO,CAACN,IAAI,CAAC,UAAU,IAAIA,IAAI,CAAC,UAAU,GAAG,EAAE,AAAD,EAAGc,GAAG,CAAC,CAACC;gBACpE,MAAMC,SAAUD,iBAAAA,SAAU,CAAC;gBAC3B,OAAO;oBAAEd,MAAMC,IAAIc,MAAM,CAAC,OAAO;oBAAGC,QAAQN,QAAQK,MAAM,CAAC,SAAS;gBAAE;YACxE;YACAE,UAAUhB,IAAIE,SAAS,CAAC,EAAE,KAAKF,IAAIF,IAAI,CAAC,WAAW,KAAK;YACxDmB,UAAUjB,IAAIC,GAAG,CAAC,QAAQ;YAC1BiB,gBAAgBlB,IAAIC,GAAG,CAAC,cAAc;YACtCkB,UAAUC,MAAMtB,IAAI,CAAC,WAAW;YAChCuB,UAAU,AAAClB,CAAAA,MAAMC,OAAO,CAACN,IAAI,CAAC,WAAW,IAAIA,IAAI,CAAC,WAAW,GAAG,EAAE,AAAD,EAAGc,GAAG,CAAC,CAACU;oBAEtDR;gBADjB,MAAMA,SAAUQ,kBAAAA,UAAW,CAAC;gBAC5B,MAAMX,WAAWG,kBAAAA,MAAM,CAAC,UAAU,YAAjBA,kBAAqB,CAAC;gBACvC,OAAO;oBACLjB,IAAIG,IAAIc,MAAM,CAAC,KAAK;oBACpBH,SAASY,OAAOC,WAAW,CAACD,OAAOE,OAAO,CAACd,SAASC,GAAG,CAAC,CAAC,CAACc,KAAKC,MAAM,GAAK;4BAACD;4BAAK1B,IAAI2B;yBAAO;oBAC3FR,UAAUC,MAAMN,MAAM,CAAC,WAAW;gBACpC;YACF;QACF;IACF;AACF;AAEA,SAASd,IAAI2B,KAAc;IACzB,OAAO,OAAOA,UAAU,WAAWA,QAAQ;AAC7C;AAEA,SAASlB,QAAQkB,KAAc;IAC7B,OAAO,AAACxB,CAAAA,MAAMC,OAAO,CAACuB,SAASA,QAAQ,EAAE,AAAD,EAAGf,GAAG,CAACZ,KAAK4B,MAAM,CAACC;AAC7D;AAEA,SAAST,MAAMO,KAAc;IAC3B,MAAMG,SAAS,OAAOH,UAAU,WAAWA,QAAQI,OAAOJ;IAC1D,OAAOA,UAAU,QAAQA,UAAUK,aAAaL,UAAU,MAAM,CAACI,OAAOE,QAAQ,CAACH,UAAU,OAAOA;AACpG"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Lifetime purchase math for a site's visitor accounts (AGL-546). Pure —
19
+ * operates on raw `hosts/{hostId}/orders` doc shapes (both the v1
20
+ * `totals.totalCents` form and the legacy flat `amountCents` form) so a
21
+ * member's purchases can be totalled from the docs already loaded. The stale
22
+ * `siteMembers.purchaseCents` field this replaces was never written.
23
+ */
24
+ /** The slice of an order doc the lifetime total needs. */
25
+ export interface PurchaseTotalOrder {
26
+ status?: string;
27
+ /** Legacy Commerce Starter flat total (cents). */
28
+ amountCents?: number;
29
+ /** Orders v1 totals block. */
30
+ totals?: {
31
+ totalCents?: number;
32
+ };
33
+ /** Cents refunded so far (partial or full), a lost chargeback included. */
34
+ refundedCents?: number;
35
+ }
36
+ /** Charged cents for one order: v1 total, else legacy amount. */
37
+ export declare function orderChargedCents(order: PurchaseTotalOrder): number;
38
+ /**
39
+ * Sum of what the member actually paid: charged totals minus refunds,
40
+ * skipping never-charged (pending/cancelled) orders. Each order clamps at
41
+ * zero so an over-recorded refund cannot drag the lifetime negative.
42
+ *
43
+ * `refundedCents` carries a lost chargeback as well as a refund (AGL-1787),
44
+ * and money reversed is money reversed whichever door it left by, so the
45
+ * whole figure is netted; only a LABEL splits the two
46
+ * (`splitOrderReversal`, AGL-1810).
47
+ */
48
+ export declare function computeLifetimePurchaseCents(orders: readonly PurchaseTotalOrder[]): number;