← back to Dw Activation Debug TK11314

lib/live-product-fields.js

62 lines

'use strict';

// Explicit catalog namespaces only. Never infer a width from a title, price,
// description, or an unrelated shipping/package dimension.
const WIDTH_NAMESPACES = ['global', 'custom', 'dwc', 'specs', 'carnegie'];
function field(nodes, namespace, key) {
  return (nodes || []).find(m => m.namespace === namespace && m.key === key &&
    typeof m.value === 'string' && m.value.trim())?.value.trim() || '';
}
function widthFromLive(product) {
  const nodes = product.metafields?.nodes || [];
  for (const namespace of WIDTH_NAMESPACES) {
    if (namespace === 'carnegie' && String(product.vendor).toLowerCase() !== 'carnegie') continue;
    const value = field(nodes, namespace, 'width');
    if (value) return { value, source: `${namespace}.width` };
  }
  return { value: '', source: null };
}
function canonicalProduct(product, dwSku, vendor) {
  const nodes = product.metafields?.nodes || [];
  const get = (namespace, key) => field(nodes, namespace, key);
  const images = (product.images?.nodes || []).map(image => image.url);
  const specs = {
    width: widthFromLive(product).value,
    length: get('global', 'length'), repeat: get('global', 'repeat'),
    material: get('global', 'material') || get('custom', 'material') || get('specs', 'material'),
    unitOfMeasure: get('global', 'unit_of_measure'),
  };
  return { title: product.title, dwSku: dwSku || '', vendor, tags: product.tags || [],
    descriptionHtml: product.descriptionHtml || '', specs, vendorSpecs: specs,
    images, vendorImages: images,
    variants: (product.variants?.nodes || []).map(v => ({ sku: v.sku, title: v.title })),
  };
}
async function completeLiveProduct(product, query) {
  const result = { ...product };
  for (const connection of ['variants', 'metafields']) {
    let page = result[connection];
    const nodes = [...(page?.nodes || [])];
    const cursors = new Set();
    while (page?.pageInfo?.hasNextPage) {
      const cursor = page.pageInfo.endCursor;
      if (!cursor || cursors.has(cursor) || cursors.size >= 100)
        throw new Error(`Cannot safely paginate ${connection}`);
      cursors.add(cursor);
      const fields = connection === 'variants' ? 'price sku title' : 'namespace key value';
      const response = await query(`query($id:ID!,$cursor:String!){product(id:$id){id
        ${connection}(first:100,after:$cursor){nodes{${fields}} pageInfo{hasNextPage endCursor}}
      }}`, { id: product.id, cursor });
      const next = response.json?.data?.product;
      if (response.status !== 200 || response.json?.errors?.length || next?.id !== product.id ||
          !Array.isArray(next[connection]?.nodes) || typeof next[connection]?.pageInfo?.hasNextPage !== 'boolean')
        throw new Error(`Incomplete Shopify ${connection} response`);
      page = next[connection];
      nodes.push(...page.nodes);
    }
    result[connection] = { ...page, nodes };
  }
  return result;
}
module.exports = { WIDTH_NAMESPACES, widthFromLive, canonicalProduct, completeLiveProduct };