@wix/web5-core 1.63.5 → 1.63.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/component/types.js.map +1 -1
- package/dist/cjs/entity/defaultExtractor.js +11 -0
- package/dist/cjs/entity/defaultExtractor.js.map +1 -1
- package/dist/cjs/entity/index.js +9 -3
- package/dist/cjs/entity/index.js.map +1 -1
- package/dist/cjs/entity/matchedVariant.js +151 -0
- package/dist/cjs/entity/matchedVariant.js.map +1 -0
- package/dist/cjs/hooks/useResolveSearchSpringEntityData.js +17 -1
- package/dist/cjs/hooks/useResolveSearchSpringEntityData.js.map +1 -1
- package/dist/cjs/hooks/useResolveShopifyEntityData.js +30 -9
- package/dist/cjs/hooks/useResolveShopifyEntityData.js.map +1 -1
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/shopify/transformShopifyEntity.js +27 -6
- package/dist/cjs/services/shopify/transformShopifyEntity.js.map +1 -1
- package/dist/cjs/types/entity.js.map +1 -1
- package/dist/esm/component/types.js.map +1 -1
- package/dist/esm/entity/defaultExtractor.js +11 -0
- package/dist/esm/entity/defaultExtractor.js.map +1 -1
- package/dist/esm/entity/index.js +1 -0
- package/dist/esm/entity/index.js.map +1 -1
- package/dist/esm/entity/matchedVariant.js +146 -0
- package/dist/esm/entity/matchedVariant.js.map +1 -0
- package/dist/esm/hooks/useResolveSearchSpringEntityData.js +17 -1
- package/dist/esm/hooks/useResolveSearchSpringEntityData.js.map +1 -1
- package/dist/esm/hooks/useResolveShopifyEntityData.js +30 -9
- package/dist/esm/hooks/useResolveShopifyEntityData.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/services/shopify/transformShopifyEntity.js +27 -6
- package/dist/esm/services/shopify/transformShopifyEntity.js.map +1 -1
- package/dist/esm/types/entity.js.map +1 -1
- package/dist/types/component/types.d.ts +22 -0
- package/dist/types/component/types.d.ts.map +1 -1
- package/dist/types/entity/defaultExtractor.d.ts.map +1 -1
- package/dist/types/entity/index.d.ts +1 -0
- package/dist/types/entity/index.d.ts.map +1 -1
- package/dist/types/entity/matchedVariant.d.ts +79 -0
- package/dist/types/entity/matchedVariant.d.ts.map +1 -0
- package/dist/types/hooks/useResolveSearchSpringEntityData.d.ts +11 -0
- package/dist/types/hooks/useResolveSearchSpringEntityData.d.ts.map +1 -1
- package/dist/types/hooks/useResolveShopifyEntityData.d.ts +11 -0
- package/dist/types/hooks/useResolveShopifyEntityData.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/services/shopify/transformShopifyEntity.d.ts +2 -2
- package/dist/types/services/shopify/transformShopifyEntity.d.ts.map +1 -1
- package/dist/types/types/entity.d.ts +16 -0
- package/dist/types/types/entity.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Web5UrlType","isWeb5EntityUrl","parseWeb5Url","extractEntityIdFromUrl","entityUrl","trimmedUrl","trim","parsed","type","ENTITY","entityType","entityId","matchesUrl","payloadItem","config","id","urlValue","urlMatchProperty","pattern","urlMatchPattern","endsWith","includes","findEntityInPayload","payload","entityConfig","typeConfig","entityTypes","matchedItem","find","item","transformToSolutionEntityData","transformToGenericEntityData","formatEntityDate","timestamp","_parts$find","_parts$find2","_parts$find3","date","Date","isNaN","getTime","undefined","formatter","Intl","DateTimeFormat","day","month","timeZone","year","parts","formatToParts","part","value","transformToBlogPostEntityData","_payloadItem$labels","_payloadItem$tags","imageUrl","publishTimestamp","publishDate","published_at","img","imgAlreadyAbsolute","startsWith","imageBaseUrl","url","title","description","excerpt","update_ts","author","authorName","cmsItemId","cms_item_id","usage","tags","labels","length","defaultExtractor"],"sources":["../../../src/entity/defaultExtractor.ts"],"sourcesContent":["import type { SolutionEntityData, BlogEntityData, EntityItemData } from '../component/types';\nimport type {\n ApiPayloadItem,\n EntityConfig,\n EntityTypeConfig,\n} from '../types/entity';\nimport type { EntityExtractor } from './types';\nimport { Web5UrlType, isWeb5EntityUrl } from '../types/link-types';\nimport { parseWeb5Url } from '../utils/entityLinkParser';\n\n/**\n * Extract entity type and ID from a web5://entity/ URL.\n * Delegates to the central `parseWeb5Url` parser.\n */\nfunction extractEntityIdFromUrl(\n entityUrl: string,\n): { entityType: string; entityId: string } | null {\n const trimmedUrl = entityUrl?.trim();\n if (!trimmedUrl || !isWeb5EntityUrl(trimmedUrl)) {\n return null;\n }\n const parsed = parseWeb5Url(trimmedUrl);\n if (parsed?.type !== Web5UrlType.ENTITY || !parsed.entityType || !parsed.entityId) {\n return null;\n }\n return { entityType: parsed.entityType, entityId: parsed.entityId };\n}\n\nfunction matchesUrl(\n payloadItem: ApiPayloadItem,\n entityId: string,\n config: EntityTypeConfig,\n): boolean {\n // Entities resolve by `entityId` (the `<id>` in `web5://entity/<type>/<id>`).\n // Accept a direct `id` match, then fall back to the configured url strategy.\n if (payloadItem.id === entityId) {\n return true;\n }\n\n const urlValue = payloadItem[config.urlMatchProperty as keyof ApiPayloadItem];\n\n if (typeof urlValue !== 'string') {\n return false;\n }\n\n const pattern = config.urlMatchPattern || 'endsWith';\n\n switch (pattern) {\n case 'endsWith':\n return urlValue.endsWith(`/${entityId}`);\n case 'contains':\n return urlValue.includes(entityId);\n case 'equals':\n return urlValue === entityId;\n default:\n return urlValue.endsWith(`/${entityId}`);\n }\n}\n\nfunction findEntityInPayload(\n entityType: string,\n entityId: string,\n payload: ApiPayloadItem[],\n entityConfig: EntityConfig,\n): ApiPayloadItem | null {\n const typeConfig = entityConfig.entityTypes[entityType];\n\n if (!typeConfig) {\n return null;\n }\n\n // Resolution keys off `entityId` (not the type): find the payload item whose\n // id/url matches, using the type's configured url strategy.\n const matchedItem = payload.find((item) =>\n matchesUrl(item, entityId, typeConfig),\n );\n\n return matchedItem || null;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToSolutionEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): SolutionEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nfunction formatEntityDate(timestamp: string): string | undefined {\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const formatter = new Intl.DateTimeFormat('en-US', {\n day: 'numeric',\n month: 'short',\n timeZone: 'Europe/Kiev',\n year: 'numeric',\n });\n const parts = formatter.formatToParts(date);\n const day = parts.find((part) => part.type === 'day')?.value;\n const month = parts.find((part) => part.type === 'month')?.value;\n const year = parts.find((part) => part.type === 'year')?.value;\n\n if (!day || !month || !year) {\n return undefined;\n }\n\n return `${day} ${month} ${year}`;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToBlogPostEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): BlogEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nexport function transformToGenericEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): EntityItemData {\n let imageUrl: string | undefined;\n const publishTimestamp = payloadItem.publishDate || payloadItem.published_at;\n\n if (payloadItem.img) {\n const imgAlreadyAbsolute = payloadItem.img.startsWith('http');\n imageUrl = config.imageBaseUrl && !imgAlreadyAbsolute\n ? `${config.imageBaseUrl}${payloadItem.img}`\n : payloadItem.img;\n }\n\n return {\n url: payloadItem.url,\n title: payloadItem.title,\n type: payloadItem.type,\n description: payloadItem.description,\n excerpt: payloadItem.excerpt,\n imageUrl,\n date: payloadItem.update_ts\n ? formatEntityDate(payloadItem.update_ts)\n : undefined,\n publishDate: publishTimestamp\n ? formatEntityDate(publishTimestamp)\n : undefined,\n author: payloadItem.author || payloadItem.authorName,\n cmsItemId: payloadItem.cms_item_id,\n usage: payloadItem.usage,\n tags: payloadItem.labels?.length\n ? payloadItem.labels\n : payloadItem.tags?.length\n ? payloadItem.tags\n : undefined,\n };\n}\n\n/**\n * Default entity extractor - config-driven extraction for any client.\n * Uses entityConfig from client JSON to resolve web5://entity/ URLs against API payload.\n */\nexport const defaultExtractor: EntityExtractor = {\n extractEntityIdFromUrl,\n findEntityInPayload,\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["Web5UrlType","isWeb5EntityUrl","parseWeb5Url","formatPriceField","readCurrencyCode","toMatchedOptions","extractEntityIdFromUrl","entityUrl","trimmedUrl","trim","parsed","type","ENTITY","entityType","entityId","matchesUrl","payloadItem","config","id","urlValue","urlMatchProperty","pattern","urlMatchPattern","endsWith","includes","findEntityInPayload","payload","entityConfig","typeConfig","entityTypes","matchedItem","find","item","transformToSolutionEntityData","transformToGenericEntityData","formatEntityDate","timestamp","_parts$find","_parts$find2","_parts$find3","date","Date","isNaN","getTime","undefined","formatter","Intl","DateTimeFormat","day","month","timeZone","year","parts","formatToParts","part","value","transformToBlogPostEntityData","_payloadItem$labels","_payloadItem$tags","imageUrl","publishTimestamp","publishDate","published_at","img","imgAlreadyAbsolute","startsWith","imageBaseUrl","currency","priceCurrency","url","title","description","excerpt","matchedOptions","options","price","priceMin","priceMax","update_ts","author","authorName","cmsItemId","cms_item_id","usage","tags","labels","length","defaultExtractor"],"sources":["../../../src/entity/defaultExtractor.ts"],"sourcesContent":["import type { SolutionEntityData, BlogEntityData, EntityItemData } from '../component/types';\nimport type {\n ApiPayloadItem,\n EntityConfig,\n EntityTypeConfig,\n} from '../types/entity';\nimport type { EntityExtractor } from './types';\nimport { Web5UrlType, isWeb5EntityUrl } from '../types/link-types';\nimport { parseWeb5Url } from '../utils/entityLinkParser';\nimport {\n formatPriceField,\n readCurrencyCode,\n toMatchedOptions,\n} from './matchedVariant';\n\n/**\n * Extract entity type and ID from a web5://entity/ URL.\n * Delegates to the central `parseWeb5Url` parser.\n */\nfunction extractEntityIdFromUrl(\n entityUrl: string,\n): { entityType: string; entityId: string } | null {\n const trimmedUrl = entityUrl?.trim();\n if (!trimmedUrl || !isWeb5EntityUrl(trimmedUrl)) {\n return null;\n }\n const parsed = parseWeb5Url(trimmedUrl);\n if (parsed?.type !== Web5UrlType.ENTITY || !parsed.entityType || !parsed.entityId) {\n return null;\n }\n return { entityType: parsed.entityType, entityId: parsed.entityId };\n}\n\nfunction matchesUrl(\n payloadItem: ApiPayloadItem,\n entityId: string,\n config: EntityTypeConfig,\n): boolean {\n // Entities resolve by `entityId` (the `<id>` in `web5://entity/<type>/<id>`).\n // Accept a direct `id` match, then fall back to the configured url strategy.\n if (payloadItem.id === entityId) {\n return true;\n }\n\n const urlValue = payloadItem[config.urlMatchProperty as keyof ApiPayloadItem];\n\n if (typeof urlValue !== 'string') {\n return false;\n }\n\n const pattern = config.urlMatchPattern || 'endsWith';\n\n switch (pattern) {\n case 'endsWith':\n return urlValue.endsWith(`/${entityId}`);\n case 'contains':\n return urlValue.includes(entityId);\n case 'equals':\n return urlValue === entityId;\n default:\n return urlValue.endsWith(`/${entityId}`);\n }\n}\n\nfunction findEntityInPayload(\n entityType: string,\n entityId: string,\n payload: ApiPayloadItem[],\n entityConfig: EntityConfig,\n): ApiPayloadItem | null {\n const typeConfig = entityConfig.entityTypes[entityType];\n\n if (!typeConfig) {\n return null;\n }\n\n // Resolution keys off `entityId` (not the type): find the payload item whose\n // id/url matches, using the type's configured url strategy.\n const matchedItem = payload.find((item) =>\n matchesUrl(item, entityId, typeConfig),\n );\n\n return matchedItem || null;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToSolutionEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): SolutionEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nfunction formatEntityDate(timestamp: string): string | undefined {\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const formatter = new Intl.DateTimeFormat('en-US', {\n day: 'numeric',\n month: 'short',\n timeZone: 'Europe/Kiev',\n year: 'numeric',\n });\n const parts = formatter.formatToParts(date);\n const day = parts.find((part) => part.type === 'day')?.value;\n const month = parts.find((part) => part.type === 'month')?.value;\n const year = parts.find((part) => part.type === 'year')?.value;\n\n if (!day || !month || !year) {\n return undefined;\n }\n\n return `${day} ${month} ${year}`;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToBlogPostEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): BlogEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nexport function transformToGenericEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): EntityItemData {\n let imageUrl: string | undefined;\n const publishTimestamp = payloadItem.publishDate || payloadItem.published_at;\n\n if (payloadItem.img) {\n const imgAlreadyAbsolute = payloadItem.img.startsWith('http');\n imageUrl = config.imageBaseUrl && !imgAlreadyAbsolute\n ? `${config.imageBaseUrl}${payloadItem.img}`\n : payloadItem.img;\n }\n\n // A document that retrieval narrowed to one child arrives with that child's\n // presentation fields already overlaid (`url`, `img`, `price`) plus the pairs\n // that identify it and the span its parent covers. The overlay is done\n // server-side; all that is left here is to shape those two for display.\n const currency = readCurrencyCode(\n payloadItem.currency,\n payloadItem.priceCurrency,\n );\n\n return {\n url: payloadItem.url,\n title: payloadItem.title,\n type: payloadItem.type,\n description: payloadItem.description,\n excerpt: payloadItem.excerpt,\n imageUrl,\n matchedOptions: toMatchedOptions(payloadItem.options),\n price: formatPriceField(payloadItem.price, currency),\n priceMin: formatPriceField(payloadItem.priceMin, currency),\n priceMax: formatPriceField(payloadItem.priceMax, currency),\n date: payloadItem.update_ts\n ? formatEntityDate(payloadItem.update_ts)\n : undefined,\n publishDate: publishTimestamp\n ? formatEntityDate(publishTimestamp)\n : undefined,\n author: payloadItem.author || payloadItem.authorName,\n cmsItemId: payloadItem.cms_item_id,\n usage: payloadItem.usage,\n tags: payloadItem.labels?.length\n ? payloadItem.labels\n : payloadItem.tags?.length\n ? payloadItem.tags\n : undefined,\n };\n}\n\n/**\n * Default entity extractor - config-driven extraction for any client.\n * Uses entityConfig from client JSON to resolve web5://entity/ URLs against API payload.\n */\nexport const defaultExtractor: EntityExtractor = {\n extractEntityIdFromUrl,\n findEntityInPayload,\n};\n"],"mappings":"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","ignoreList":[]}
|
package/dist/esm/entity/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defaultExtractor } from './defaultExtractor.js';
|
|
2
2
|
export { defaultExtractor, transformToSolutionEntityData, transformToBlogPostEntityData, transformToGenericEntityData } from './defaultExtractor.js';
|
|
3
3
|
export { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload.js';
|
|
4
|
+
export { toMatchedOptions, formatMoney, readCurrencyCode, formatPriceField, formatProductPriceLabel } from './matchedVariant.js';
|
|
4
5
|
export { fetchEntityListData } from './fetchEntityListData.js';
|
|
5
6
|
export { listEntityItems, LIST_ITEMS_ENDPOINT } from './listEntityItems.js';
|
|
6
7
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["defaultExtractor","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","enrichEntitiesFromPayload","fetchEntityListData","listEntityItems","LIST_ITEMS_ENDPOINT","extractorRegistry","getEntityExtractor","clientId","registerEntityExtractor","extractor"],"sources":["../../../src/entity/index.ts"],"sourcesContent":["import type { EntityExtractor } from './types';\nimport { defaultExtractor } from './defaultExtractor';\n\nexport {\n defaultExtractor,\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from './defaultExtractor';\n\nexport { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload';\nexport {\n fetchEntityListData,\n type FetchEntityListDataOptions,\n} from './fetchEntityListData';\nexport {\n listEntityItems,\n LIST_ITEMS_ENDPOINT,\n type ListEntityItemsOptions,\n} from './listEntityItems';\n\nexport type { EntityExtractor } from './types';\n\n/**\n * Registry of client-specific entity extractors.\n * Only needed if a client requires custom extraction logic beyond the config-driven default.\n */\nconst extractorRegistry: Record<string, EntityExtractor> = {};\n\n/**\n * Get the entity extractor for a specific client.\n * The default extractor is config-driven and works for any client with entityConfig.\n */\nexport function getEntityExtractor(clientId?: string): EntityExtractor {\n if (clientId && extractorRegistry[clientId]) {\n return extractorRegistry[clientId];\n }\n return defaultExtractor;\n}\n\n/**\n * Register a custom entity extractor for a client\n * Useful for runtime registration or testing\n */\nexport function registerEntityExtractor(\n clientId: string,\n extractor: EntityExtractor,\n): void {\n extractorRegistry[clientId] = extractor;\n}\n"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,oBAAoB;AAErD,SACEA,gBAAgB,EAChBC,6BAA6B,EAC7BC,6BAA6B,EAC7BC,4BAA4B,QACvB,oBAAoB;AAE3B,SAASC,yBAAyB,QAAQ,6BAA6B;AACvE,SACEC,mBAAmB,QAEd,uBAAuB;AAC9B,SACEC,eAAe,EACfC,mBAAmB,QAEd,mBAAmB;AAI1B;AACA;AACA;AACA;AACA,MAAMC,iBAAkD,GAAG,CAAC,CAAC;;AAE7D;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAACC,QAAiB,EAAmB;EACrE,IAAIA,QAAQ,IAAIF,iBAAiB,CAACE,QAAQ,CAAC,EAAE;IAC3C,OAAOF,iBAAiB,CAACE,QAAQ,CAAC;EACpC;EACA,
|
|
1
|
+
{"version":3,"names":["defaultExtractor","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","enrichEntitiesFromPayload","toMatchedOptions","formatMoney","readCurrencyCode","formatPriceField","formatProductPriceLabel","fetchEntityListData","listEntityItems","LIST_ITEMS_ENDPOINT","extractorRegistry","getEntityExtractor","clientId","registerEntityExtractor","extractor"],"sources":["../../../src/entity/index.ts"],"sourcesContent":["import type { EntityExtractor } from './types';\nimport { defaultExtractor } from './defaultExtractor';\n\nexport {\n defaultExtractor,\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from './defaultExtractor';\n\nexport { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload';\nexport {\n toMatchedOptions,\n formatMoney,\n readCurrencyCode,\n formatPriceField,\n formatProductPriceLabel,\n type MatchedOption,\n type ProductPriceFields,\n} from './matchedVariant';\nexport {\n fetchEntityListData,\n type FetchEntityListDataOptions,\n} from './fetchEntityListData';\nexport {\n listEntityItems,\n LIST_ITEMS_ENDPOINT,\n type ListEntityItemsOptions,\n} from './listEntityItems';\n\nexport type { EntityExtractor } from './types';\n\n/**\n * Registry of client-specific entity extractors.\n * Only needed if a client requires custom extraction logic beyond the config-driven default.\n */\nconst extractorRegistry: Record<string, EntityExtractor> = {};\n\n/**\n * Get the entity extractor for a specific client.\n * The default extractor is config-driven and works for any client with entityConfig.\n */\nexport function getEntityExtractor(clientId?: string): EntityExtractor {\n if (clientId && extractorRegistry[clientId]) {\n return extractorRegistry[clientId];\n }\n return defaultExtractor;\n}\n\n/**\n * Register a custom entity extractor for a client\n * Useful for runtime registration or testing\n */\nexport function registerEntityExtractor(\n clientId: string,\n extractor: EntityExtractor,\n): void {\n extractorRegistry[clientId] = extractor;\n}\n"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,oBAAoB;AAErD,SACEA,gBAAgB,EAChBC,6BAA6B,EAC7BC,6BAA6B,EAC7BC,4BAA4B,QACvB,oBAAoB;AAE3B,SAASC,yBAAyB,QAAQ,6BAA6B;AACvE,SACEC,gBAAgB,EAChBC,WAAW,EACXC,gBAAgB,EAChBC,gBAAgB,EAChBC,uBAAuB,QAGlB,kBAAkB;AACzB,SACEC,mBAAmB,QAEd,uBAAuB;AAC9B,SACEC,eAAe,EACfC,mBAAmB,QAEd,mBAAmB;AAI1B;AACA;AACA;AACA;AACA,MAAMC,iBAAkD,GAAG,CAAC,CAAC;;AAE7D;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAACC,QAAiB,EAAmB;EACrE,IAAIA,QAAQ,IAAIF,iBAAiB,CAACE,QAAQ,CAAC,EAAE;IAC3C,OAAOF,iBAAiB,CAACE,QAAQ,CAAC;EACpC;EACA,OAAOf,gBAAgB;AACzB;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASgB,uBAAuBA,CACrCD,QAAgB,EAChBE,SAA0B,EACpB;EACNJ,iBAAiB,CAACE,QAAQ,CAAC,GAAGE,SAAS;AACzC","ignoreList":[]}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation helpers for a document that retrieval resolved down to one
|
|
3
|
+
* child.
|
|
4
|
+
*
|
|
5
|
+
* When a query filters on an attribute that lives on the child rather than on
|
|
6
|
+
* the parent, retrieval overlays the matching child's presentation fields onto
|
|
7
|
+
* the parent document before it reaches us: `url`, `img` and `price` already
|
|
8
|
+
* describe the child, while `title`, `description` and `doc_id` stay the
|
|
9
|
+
* parent's. Two fields need shaping before a card can print them, and both
|
|
10
|
+
* live here:
|
|
11
|
+
*
|
|
12
|
+
* - `options` — the child's own `{ name: value }` pairs.
|
|
13
|
+
* - `priceMin` / `priceMax` — the parent's span, against which the child's
|
|
14
|
+
* single `price` has to be read.
|
|
15
|
+
*
|
|
16
|
+
* The overlay itself is done server-side and is deliberately NOT repeated
|
|
17
|
+
* here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** One `{ name: value }` pair, exactly as the store named it. */
|
|
21
|
+
|
|
22
|
+
function readText(value) {
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
return value.trim();
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
27
|
+
return String(value);
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Turn a document's raw `options` map into an ordered list of pairs.
|
|
34
|
+
*
|
|
35
|
+
* Every entry the store put in the map is kept, under the store's own name and
|
|
36
|
+
* in the store's own order. Nothing here knows — or may learn — what any
|
|
37
|
+
* particular name means: one catalogue's pairs describe a garment, the next
|
|
38
|
+
* one's a power tool, and both have to come out identical. Entries with an
|
|
39
|
+
* empty name or an empty/non-printable value are dropped, since a card can do
|
|
40
|
+
* nothing with them.
|
|
41
|
+
*
|
|
42
|
+
* Returns `undefined` (not `[]`) when there is nothing to show, so callers can
|
|
43
|
+
* treat it like every other optional field on the entity.
|
|
44
|
+
*/
|
|
45
|
+
export function toMatchedOptions(raw) {
|
|
46
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const pairs = [];
|
|
50
|
+
for (const [rawName, rawValue] of Object.entries(raw)) {
|
|
51
|
+
const name = rawName.trim();
|
|
52
|
+
const value = readText(rawValue);
|
|
53
|
+
if (name && value) {
|
|
54
|
+
pairs.push({
|
|
55
|
+
name,
|
|
56
|
+
value
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return pairs.length ? pairs : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render one amount in the document's currency.
|
|
65
|
+
*
|
|
66
|
+
* With no currency code on the document the amount is printed bare: assuming
|
|
67
|
+
* "$" prints a false fact on every store that doesn't sell in dollars. Whole
|
|
68
|
+
* amounts stay whole ("107" not "107.00"); a real fractional amount keeps its
|
|
69
|
+
* minor units.
|
|
70
|
+
*/
|
|
71
|
+
export function formatMoney(amount, currency) {
|
|
72
|
+
if (!currency) {
|
|
73
|
+
return String(amount);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return new Intl.NumberFormat(undefined, {
|
|
77
|
+
style: 'currency',
|
|
78
|
+
currency,
|
|
79
|
+
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2
|
|
80
|
+
}).format(amount);
|
|
81
|
+
} catch {
|
|
82
|
+
// A well-formed but unknown code: show it rather than dropping it.
|
|
83
|
+
return `${amount} ${currency}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Normalize a document currency to an ISO-4217 code, or drop it. */
|
|
88
|
+
export function readCurrencyCode() {
|
|
89
|
+
for (var _len = arguments.length, candidates = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
90
|
+
candidates[_key] = arguments[_key];
|
|
91
|
+
}
|
|
92
|
+
for (const candidate of candidates) {
|
|
93
|
+
if (typeof candidate !== 'string') {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const code = candidate.trim().toUpperCase();
|
|
97
|
+
if (/^[A-Z]{3}$/.test(code)) {
|
|
98
|
+
return code;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Format one of a document's price fields for display. Numbers are rendered in
|
|
106
|
+
* the document's currency; an already-formatted string is passed through
|
|
107
|
+
* untouched; anything else yields `undefined`.
|
|
108
|
+
*/
|
|
109
|
+
export function formatPriceField(value, currency) {
|
|
110
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
111
|
+
return formatMoney(value, currency);
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === 'string' && value.trim()) {
|
|
114
|
+
return value.trim();
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The already-formatted price fields a card reads. */
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* What a product card should print as its price.
|
|
123
|
+
*
|
|
124
|
+
* `price` belongs to the single variant the search matched — the same variant
|
|
125
|
+
* the card's image and link point at. `priceMin`/`priceMax` describe the whole
|
|
126
|
+
* product. When those two differ the product has no one price, and printing
|
|
127
|
+
* the matched variant's number on its own would pass it off as the product's;
|
|
128
|
+
* printing it after "from" would be worse still, because "from" is a claim
|
|
129
|
+
* about the floor of the span and the matched variant is not always the
|
|
130
|
+
* cheapest one. So a span prints its own floor, prefixed.
|
|
131
|
+
*
|
|
132
|
+
* With no span — the two ends equal, or only one of them known — nothing
|
|
133
|
+
* changes: the matched variant's price is printed as it always was.
|
|
134
|
+
*/
|
|
135
|
+
export function formatProductPriceLabel(fields) {
|
|
136
|
+
const {
|
|
137
|
+
price,
|
|
138
|
+
priceMin,
|
|
139
|
+
priceMax
|
|
140
|
+
} = fields;
|
|
141
|
+
if (priceMin && priceMax && priceMin !== priceMax) {
|
|
142
|
+
return `from ${priceMin}`;
|
|
143
|
+
}
|
|
144
|
+
return price;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=matchedVariant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","_len","arguments","candidates","_key","candidate","code","toUpperCase","test","formatPriceField","formatProductPriceLabel","fields","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n}\n\n/**\n * What a product card should print as its price.\n *\n * `price` belongs to the single variant the search matched — the same variant\n * the card's image and link point at. `priceMin`/`priceMax` describe the whole\n * product. When those two differ the product has no one price, and printing\n * the matched variant's number on its own would pass it off as the product's;\n * printing it after \"from\" would be worse still, because \"from\" is a claim\n * about the floor of the span and the matched variant is not always the\n * cheapest one. So a span prints its own floor, prefixed.\n *\n * With no span — the two ends equal, or only one of them known — nothing\n * changes: the matched variant's price is printed as it always was.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -26,8 +26,20 @@ function getSearchSpringHandle(entity) {
|
|
|
26
26
|
var _entity$data;
|
|
27
27
|
return extractProductHandle((_entity$data = entity.data) == null ? void 0 : _entity$data.url) || entity.entityId;
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about
|
|
32
|
+
* it.
|
|
33
|
+
*
|
|
34
|
+
* Only the turn saw the shopper's question, so only it knows that the brown
|
|
35
|
+
* variant was the one asked for — its `title`, `imageUrl`, `url`,
|
|
36
|
+
* `matchedOptions` and `variantId` all describe that one variant and must
|
|
37
|
+
* survive enrichment. Everything SearchSpring answers with that can go stale
|
|
38
|
+
* between the turn and the render — `price`, `compareAtPrice`, `available`,
|
|
39
|
+
* `priceMin`, `priceMax` — comes from `resolved`.
|
|
40
|
+
*/
|
|
29
41
|
export function mergeSearchSpringEntityData(existing, resolved) {
|
|
30
|
-
var _existing$sizes, _existing$variants, _existing$options, _existing$allImages;
|
|
42
|
+
var _existing$matchedOpti, _existing$sizes, _existing$variants, _existing$options, _existing$allImages;
|
|
31
43
|
if (!existing) {
|
|
32
44
|
return resolved;
|
|
33
45
|
}
|
|
@@ -35,6 +47,10 @@ export function mergeSearchSpringEntityData(existing, resolved) {
|
|
|
35
47
|
...existing,
|
|
36
48
|
...resolved,
|
|
37
49
|
url: existing.url || resolved.url,
|
|
50
|
+
title: existing.title || resolved.title,
|
|
51
|
+
imageUrl: existing.imageUrl || resolved.imageUrl,
|
|
52
|
+
variantId: existing.variantId || resolved.variantId,
|
|
53
|
+
matchedOptions: (_existing$matchedOpti = existing.matchedOptions) != null && _existing$matchedOpti.length ? existing.matchedOptions : resolved.matchedOptions,
|
|
38
54
|
sizes: (_existing$sizes = existing.sizes) != null && _existing$sizes.length ? existing.sizes : resolved.sizes,
|
|
39
55
|
variants: (_existing$variants = existing.variants) != null && _existing$variants.length ? existing.variants : resolved.variants,
|
|
40
56
|
options: (_existing$options = existing.options) != null && _existing$options.length ? existing.options : resolved.options,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useState","useEffect","useMemo","fetchProductsByHandlesMap","transformSSProductToEntityItemData","ssEntityCache","Map","extractProductHandle","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","handle","slice","length","getSearchSpringHandle","entity","_entity$data","data","entityId","mergeSearchSpringEntityData","existing","resolved","_existing$sizes","_existing$variants","_existing$options","_existing$allImages","sizes","variants","options","allImages","useResolveSearchSpringEntityData","entities","config","resolvedMap","setResolvedMap","settled","setSettled","entityKey","map","e","entityType","join","ignore","siteId","productEntityHandles","filter","toLowerCase","includes","handlesToFetch","Set","_ref","has","cached","forEach","_ref2","item","get","set","fetchAll","productMap","product","itemData","finalMap","_ref3","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveSearchSpringEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { fetchProductsByHandlesMap } from '../services/searchspring/searchSpringClient';\nimport { transformSSProductToEntityItemData } from '../services/searchspring/transformSSProduct';\nimport type { SearchSpringConfig } from '../services/searchspring/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: handle → resolved EntityItemData\nconst ssEntityCache = new Map<string, EntityItemData>();\n\nfunction extractProductHandle(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n const handle =\n productsIndex >= 0\n ? withoutLeadingSlash.slice(productsIndex + 'products/'.length)\n : withoutLeadingSlash;\n\n return handle || undefined;\n}\n\nfunction getSearchSpringHandle(entity: {\n entityId: string;\n data?: EntityItemData;\n}): string {\n return extractProductHandle(entity.data?.url) || entity.entityId;\n}\n\nexport function mergeSearchSpringEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n sizes: existing.sizes?.length ? existing.sizes : resolved.sizes,\n variants: existing.variants?.length ? existing.variants : resolved.variants,\n options: existing.options?.length ? existing.options : resolved.options,\n allImages: existing.allImages?.length\n ? existing.allImages\n : resolved.allImages,\n };\n}\n\n/**\n * SearchSpring-backed entity resolution hook.\n *\n * Fetches product data from the SearchSpring Search API by handle\n * and transforms it to EntityItemData. Uses a single batched API call\n * for all products (bgfilter.handle supports OR).\n */\nexport function useResolveSearchSpringEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n config: SearchSpringConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable key for dependency tracking\n const entityKey = useMemo(\n () =>\n entities\n .map((e) => `${e.entityType}:${e.entityId}:${getSearchSpringHandle(e)}`)\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n\n if (!entities.length || !config.siteId) {\n setSettled(true);\n return;\n }\n\n const productEntityHandles = entities\n .filter((e) => e.entityType.toLowerCase().includes('product'))\n .map((entity) => ({\n entity,\n handle: getSearchSpringHandle(entity),\n }));\n\n // Collect handles that need fetching. CMS payload IDs use payload url as the handle.\n const handlesToFetch = [\n ...new Set(\n productEntityHandles\n .map(({ handle }) => handle)\n .filter((handle) => !ssEntityCache.has(handle)),\n ),\n ];\n\n // If everything is cached, resolve immediately\n if (handlesToFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n productEntityHandles.forEach(({ entity, handle }) => {\n const item = ssEntityCache.get(handle);\n if (item) {\n cached.set(entity.entityId, item);\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n const productMap = await fetchProductsByHandlesMap(\n config.siteId,\n handlesToFetch,\n );\n\n // Transform and cache\n for (const [handle, product] of productMap) {\n const itemData = transformSSProductToEntityItemData(product);\n ssEntityCache.set(handle, itemData);\n }\n\n if (ignore) {\n return;\n }\n\n // Build the final map\n const finalMap = new Map<string, EntityItemData>();\n productEntityHandles.forEach(({ entity, handle }) => {\n const item = ssEntityCache.get(handle);\n if (item) {\n finalMap.set(entity.entityId, item);\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityKey, config.siteId]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return {\n ...entity,\n data: mergeSearchSpringEntityData(entity.data, resolved),\n };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useState","useEffect","useMemo","fetchProductsByHandlesMap","transformSSProductToEntityItemData","ssEntityCache","Map","extractProductHandle","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","handle","slice","length","getSearchSpringHandle","entity","_entity$data","data","entityId","mergeSearchSpringEntityData","existing","resolved","_existing$matchedOpti","_existing$sizes","_existing$variants","_existing$options","_existing$allImages","title","imageUrl","variantId","matchedOptions","sizes","variants","options","allImages","useResolveSearchSpringEntityData","entities","config","resolvedMap","setResolvedMap","settled","setSettled","entityKey","map","e","entityType","join","ignore","siteId","productEntityHandles","filter","toLowerCase","includes","handlesToFetch","Set","_ref","has","cached","forEach","_ref2","item","get","set","fetchAll","productMap","product","itemData","finalMap","_ref3","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveSearchSpringEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { fetchProductsByHandlesMap } from '../services/searchspring/searchSpringClient';\nimport { transformSSProductToEntityItemData } from '../services/searchspring/transformSSProduct';\nimport type { SearchSpringConfig } from '../services/searchspring/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: handle → resolved EntityItemData\nconst ssEntityCache = new Map<string, EntityItemData>();\n\nfunction extractProductHandle(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n const handle =\n productsIndex >= 0\n ? withoutLeadingSlash.slice(productsIndex + 'products/'.length)\n : withoutLeadingSlash;\n\n return handle || undefined;\n}\n\nfunction getSearchSpringHandle(entity: {\n entityId: string;\n data?: EntityItemData;\n}): string {\n return extractProductHandle(entity.data?.url) || entity.entityId;\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything SearchSpring answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeSearchSpringEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n sizes: existing.sizes?.length ? existing.sizes : resolved.sizes,\n variants: existing.variants?.length ? existing.variants : resolved.variants,\n options: existing.options?.length ? existing.options : resolved.options,\n allImages: existing.allImages?.length\n ? existing.allImages\n : resolved.allImages,\n };\n}\n\n/**\n * SearchSpring-backed entity resolution hook.\n *\n * Fetches product data from the SearchSpring Search API by handle\n * and transforms it to EntityItemData. Uses a single batched API call\n * for all products (bgfilter.handle supports OR).\n */\nexport function useResolveSearchSpringEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n config: SearchSpringConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable key for dependency tracking\n const entityKey = useMemo(\n () =>\n entities\n .map((e) => `${e.entityType}:${e.entityId}:${getSearchSpringHandle(e)}`)\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n\n if (!entities.length || !config.siteId) {\n setSettled(true);\n return;\n }\n\n const productEntityHandles = entities\n .filter((e) => e.entityType.toLowerCase().includes('product'))\n .map((entity) => ({\n entity,\n handle: getSearchSpringHandle(entity),\n }));\n\n // Collect handles that need fetching. CMS payload IDs use payload url as the handle.\n const handlesToFetch = [\n ...new Set(\n productEntityHandles\n .map(({ handle }) => handle)\n .filter((handle) => !ssEntityCache.has(handle)),\n ),\n ];\n\n // If everything is cached, resolve immediately\n if (handlesToFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n productEntityHandles.forEach(({ entity, handle }) => {\n const item = ssEntityCache.get(handle);\n if (item) {\n cached.set(entity.entityId, item);\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n const productMap = await fetchProductsByHandlesMap(\n config.siteId,\n handlesToFetch,\n );\n\n // Transform and cache\n for (const [handle, product] of productMap) {\n const itemData = transformSSProductToEntityItemData(product);\n ssEntityCache.set(handle, itemData);\n }\n\n if (ignore) {\n return;\n }\n\n // Build the final map\n const finalMap = new Map<string, EntityItemData>();\n productEntityHandles.forEach(({ entity, handle }) => {\n const item = ssEntityCache.get(handle);\n if (item) {\n finalMap.set(entity.entityId, item);\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityKey, config.siteId]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return {\n ...entity,\n data: mergeSearchSpringEntityData(entity.data, resolved),\n };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,10 +2,10 @@ import { useState, useEffect, useMemo, useRef } from 'react';
|
|
|
2
2
|
import { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient.js';
|
|
3
3
|
import { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver.js';
|
|
4
4
|
import { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity.js';
|
|
5
|
-
// Session-level cache: entityType:entityId → resolved EntityItemData
|
|
5
|
+
// Session-level cache: entityType:entityId:variantId → resolved EntityItemData
|
|
6
6
|
const shopifyEntityCache = new Map();
|
|
7
|
-
function cacheKey(entityType, entityId) {
|
|
8
|
-
return `${entityType}:${entityId}`;
|
|
7
|
+
function cacheKey(entityType, entityId, variantId) {
|
|
8
|
+
return `${entityType}:${entityId}:${variantId || ''}`;
|
|
9
9
|
}
|
|
10
10
|
function normalizeShopifyEntityType(entityType) {
|
|
11
11
|
const lower = entityType.toLowerCase();
|
|
@@ -46,23 +46,41 @@ function extractHandleFromUrl(url) {
|
|
|
46
46
|
return withoutLeadingSlash || undefined;
|
|
47
47
|
}
|
|
48
48
|
function getShopifyLookup(entity) {
|
|
49
|
-
var _entity$data;
|
|
49
|
+
var _entity$data, _entity$data2;
|
|
50
50
|
const entityType = normalizeShopifyEntityType(entity.entityType);
|
|
51
51
|
const urlHandle = extractHandleFromUrl((_entity$data = entity.data) == null ? void 0 : _entity$data.url);
|
|
52
52
|
const entityId = (entityType === 'product' || entityType === 'collection') && urlHandle ? urlHandle : entity.entityId;
|
|
53
53
|
return {
|
|
54
54
|
entityType,
|
|
55
|
-
entityId
|
|
55
|
+
entityId,
|
|
56
|
+
variantId: (_entity$data2 = entity.data) == null ? void 0 : _entity$data2.variantId
|
|
56
57
|
};
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about
|
|
62
|
+
* it.
|
|
63
|
+
*
|
|
64
|
+
* Only the turn saw the shopper's question, so only it knows that the brown
|
|
65
|
+
* variant was the one asked for — its `title`, `imageUrl`, `url`,
|
|
66
|
+
* `matchedOptions` and `variantId` all describe that one variant and must
|
|
67
|
+
* survive enrichment. Everything the Storefront answers with that can go stale
|
|
68
|
+
* between the turn and the render — `price`, `compareAtPrice`, `available`,
|
|
69
|
+
* `priceMin`, `priceMax` — comes from `resolved`.
|
|
70
|
+
*/
|
|
58
71
|
export function mergeShopifyEntityData(existing, resolved) {
|
|
72
|
+
var _existing$matchedOpti;
|
|
59
73
|
if (!existing) {
|
|
60
74
|
return resolved;
|
|
61
75
|
}
|
|
62
76
|
return {
|
|
63
77
|
...existing,
|
|
64
78
|
...resolved,
|
|
65
|
-
url: existing.url || resolved.url
|
|
79
|
+
url: existing.url || resolved.url,
|
|
80
|
+
title: existing.title || resolved.title,
|
|
81
|
+
imageUrl: existing.imageUrl || resolved.imageUrl,
|
|
82
|
+
variantId: existing.variantId || resolved.variantId,
|
|
83
|
+
matchedOptions: (_existing$matchedOpti = existing.matchedOptions) != null && _existing$matchedOpti.length ? existing.matchedOptions : resolved.matchedOptions
|
|
66
84
|
};
|
|
67
85
|
}
|
|
68
86
|
|
|
@@ -88,7 +106,7 @@ export function useResolveShopifyEntityData(entities, shopifyConfig) {
|
|
|
88
106
|
}
|
|
89
107
|
const entityIds = useMemo(() => entities.map(e => {
|
|
90
108
|
const lookup = getShopifyLookup(e);
|
|
91
|
-
return `${e.entityType}:${e.entityId}:${lookup.entityType
|
|
109
|
+
return `${e.entityType}:${e.entityId}:${cacheKey(lookup.entityType, lookup.entityId, lookup.variantId)}`;
|
|
92
110
|
}).join(','), [entities]);
|
|
93
111
|
useEffect(() => {
|
|
94
112
|
let ignore = false;
|
|
@@ -102,7 +120,10 @@ export function useResolveShopifyEntityData(entities, shopifyConfig) {
|
|
|
102
120
|
return {
|
|
103
121
|
entity,
|
|
104
122
|
lookup,
|
|
105
|
-
key:
|
|
123
|
+
// The variant is part of the key: the cached entry carries that
|
|
124
|
+
// variant's live price, so two entities on the same product but
|
|
125
|
+
// different variants must not share it.
|
|
126
|
+
key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId)
|
|
106
127
|
};
|
|
107
128
|
});
|
|
108
129
|
|
|
@@ -141,7 +162,7 @@ export function useResolveShopifyEntityData(entities, shopifyConfig) {
|
|
|
141
162
|
} = _ref3;
|
|
142
163
|
const resolved = await resolveShopifyEntity(client, lookup.entityType, lookup.entityId, shopifyConfig);
|
|
143
164
|
if (resolved) {
|
|
144
|
-
const itemData = transformShopifyEntityToItemData(resolved, lookup.entityType);
|
|
165
|
+
const itemData = transformShopifyEntityToItemData(resolved, lookup.entityType, lookup.variantId);
|
|
145
166
|
shopifyEntityCache.set(key, mergeShopifyEntityData(entity.data, itemData));
|
|
146
167
|
}
|
|
147
168
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useState","useEffect","useMemo","useRef","ShopifyStorefrontClient","resolveShopifyEntity","transformShopifyEntityToItemData","shopifyEntityCache","Map","cacheKey","entityType","entityId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","getShopifyLookup","entity","_entity$data","urlHandle","data","mergeShopifyEntityData","existing","resolved","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","settled","setSettled","clientRef","configKeyRef","configKey","storeDomain","apiVersion","current","entityIds","map","e","lookup","join","ignore","client","entityLookups","key","toFetch","filter","_ref","has","cached","forEach","_ref2","item","get","set","fetchAll","Promise","allSettled","_ref3","itemData","finalMap","_ref4","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(entityType: string, entityId: string): string {\n return `${entityType}:${entityId}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n if (lower.includes('product')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\nfunction getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return { entityType, entityId };\n}\n\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${lookup.entityType}:${lookup.entityId}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n key: cacheKey(lookup.entityType, lookup.entityId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useState","useEffect","useMemo","useRef","ShopifyStorefrontClient","resolveShopifyEntity","transformShopifyEntityToItemData","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","getShopifyLookup","entity","_entity$data","_entity$data2","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","settled","setSettled","clientRef","configKeyRef","configKey","storeDomain","apiVersion","current","entityIds","map","e","lookup","join","ignore","client","entityLookups","key","toFetch","filter","_ref","has","cached","forEach","_ref2","item","get","set","fetchAll","Promise","allSettled","_ref3","itemData","finalMap","_ref4","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n if (lower.includes('product')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\nfunction getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return { entityType, entityId, variantId: entity.data?.variantId };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
package/dist/esm/index.js
CHANGED
|
@@ -59,7 +59,7 @@ export { ChipsProvider, useChips } from './context/ChipsContext.js';
|
|
|
59
59
|
|
|
60
60
|
// DI types
|
|
61
61
|
|
|
62
|
-
export { defaultExtractor, enrichEntitiesFromPayload, fetchEntityListData, listEntityItems, LIST_ITEMS_ENDPOINT, getEntityExtractor, registerEntityExtractor, transformToSolutionEntityData, transformToBlogPostEntityData, transformToGenericEntityData } from './entity/index.js';
|
|
62
|
+
export { defaultExtractor, enrichEntitiesFromPayload, fetchEntityListData, listEntityItems, LIST_ITEMS_ENDPOINT, getEntityExtractor, registerEntityExtractor, transformToSolutionEntityData, transformToBlogPostEntityData, transformToGenericEntityData, toMatchedOptions, formatMoney, readCurrencyCode, formatPriceField, formatProductPriceLabel } from './entity/index.js';
|
|
63
63
|
export { CALLOUT_KINDS } from './types/callout.js';
|
|
64
64
|
|
|
65
65
|
// Wrapper hooks
|