@porulle/adapter-shopify 0.77.0 → 0.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/adapter-shopify",
3
- "version": "0.77.0",
3
+ "version": "0.78.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "zod": "^4.1.11",
19
- "@porulle/core": "0.77.0"
19
+ "@porulle/core": "0.78.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^24.5.2",
package/src/catalog.ts CHANGED
@@ -25,7 +25,7 @@ const VARIANT_FIELDS = `
25
25
 
26
26
  const PRODUCT_FIELDS = `
27
27
  legacyResourceId title handle status descriptionHtml vendor productType tags onlineStoreUrl
28
- category { name }
28
+ category { id name }
29
29
  options { name position optionValues { name } }
30
30
  media(first: ${MEDIA_PER_PRODUCT}) { nodes { id alt mediaContentType ... on MediaImage { image { url } } } }
31
31
  variants(first: ${VARIANTS_WITH_PRODUCT}) { pageInfo { hasNextPage endCursor } nodes { ${VARIANT_FIELDS} } }`;
@@ -74,7 +74,7 @@ const productSchema = z.object({
74
74
  productType: z.string(),
75
75
  tags: z.array(z.string()),
76
76
  onlineStoreUrl: z.string().nullable(),
77
- category: z.object({ name: z.string() }).nullable(),
77
+ category: z.object({ id: z.string(), name: z.string() }).nullable(),
78
78
  options: z.array(z.object({ name: z.string(), position: z.number(), optionValues: z.array(z.object({ name: z.string() })) })),
79
79
  media: z.object({
80
80
  nodes: z.array(z.object({
@@ -139,10 +139,19 @@ function slugify(value: string): string {
139
139
  return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
140
140
  }
141
141
 
142
+ /** `gid://shopify/TaxonomyCategory/aa-1-4` → `aa-1-4`, the Standard Product Taxonomy's own id. */
143
+ function taxonomyCategoryId(gid: string): string | undefined {
144
+ const id = gid.slice(gid.lastIndexOf("/") + 1);
145
+ return /^[a-z]{2}(-\d+)*$/.test(id) ? id : undefined;
146
+ }
147
+
142
148
  export function toCatalogItem(product: ShopifyProduct, variants: readonly ShopifyVariant[], currency: string): ChannelCatalogItem {
143
149
  const images = product.media.nodes.flatMap((media) => (media.mediaContentType === "IMAGE" && media.image ? [{ id: media.id, url: media.image.url, alt: media.alt }] : []));
144
150
  const category = product.productType ? slugify(product.productType) : product.category ? slugify(product.category.name) : "";
145
151
  const storefrontUrl = product.onlineStoreUrl;
152
+ // The merchant's (or Shopify's) Standard Product Taxonomy category, as Shopify's own id. A
153
+ // platform that classifies against the same taxonomy reads it instead of guessing from text.
154
+ const shopifyCategory = product.category ? taxonomyCategoryId(product.category.id) : undefined;
146
155
  return {
147
156
  externalId: product.legacyResourceId,
148
157
  slug: product.handle,
@@ -184,6 +193,7 @@ export function toCatalogItem(product: ShopifyProduct, variants: readonly Shopif
184
193
  // Only Shopify's own answer. Null means the product is not on the Online Store channel; a URL
185
194
  // assembled from the handle would be a guess that 404s on any shop with a custom route.
186
195
  ...(storefrontUrl ? { storefrontUrl } : {}),
196
+ ...(shopifyCategory ? { metadata: { shopifyTaxonomyCategoryId: shopifyCategory } } : {}),
187
197
  };
188
198
  }
189
199