1'use strict';23// rp-target-wix — the DECLARATIVE Wix target spec.4//5// This module is the single source of truth for two things the pipeline used to re-derive with an6// LLM on every run:7//8// 1. CANONICAL_FIELDS — the vendor-neutral vocabulary a source adapter maps its columns onto.9// A vendor overlay's `columnMap[].wixTarget` MUST name a field in here; anything else is a10// typo or an unmodelled concept, and `validateCanonicalPath` catches it at load time instead11// of at import time.12//13// 2. STORES_V3_TARGET — how each canonical field lands in a real Wix Stores V3 payload, plus14// every trap learned by live call. This is vendor-independent: Shopify, WooCommerce, Magento
163 // Constants the API demands regardless of source.
164 constants: {
165 productType: 'PHYSICAL',
166 // TRAP (verified): a PHYSICAL product requires an EMPTY physicalProperties object at product
167 // level. Omitting it 400s.
168 physicalProperties: {},
169 },
170 // Vendors express price as either (price, compareAt) like Shopify or (regular, sale) like Woo /
171 // Magento / BigCommerce. Wix models `actualPrice` (what is charged) + `compareAtPrice` (the
172 // strike-through), so the pair must be RESOLVED, not mapped field-for-field. Encoding the rule
173 // here once removes the single most repeated per-run judgement call.
174 priceResolution: {
175 rule: 'when variant.discountedPrice is present and strictly less than variant.price, it becomes actualPrice and variant.price becomes compareAtPrice; otherwise variant.price is actualPrice and variant.compareAtPrice is the strike-through',
176 dropCompareAtUnlessGreater: true,
177 note: 'vendors write 0.00 (and Woo writes an empty sale price) to mean "no discount"; Wix rejects compareAtPrice <= actualPrice',
178 },
179 // Canonical fields Wix Stores V3 has no home for. Naming them here is what lets the resolver
180 // report an honest "mapped but will not land" instead of silently dropping them.
181 unsupported: {
182 'product.tags': 'Wix Stores V3 has no product-tag field; a tag taxonomy must become categories or a CMS field.',
183 'tag.name': 'Same as product.tags — no native product-tag target in Stores V3.',
184 'product.sourceId': 'Identity only: tracked in the crosswalk, not written to Wix (no client-settable id).',
185 'variant.parentRef': 'Identity only: used to attach a variation row to its parent during extract.',
186 'product.shortDescription': 'Stores V3 has one description; a second body would need an info section (not modelled here).',
187 'variant.inStock': 'Fed into inventoryItem.inStock by the variants builder rather than mapped directly.',
188 'variant.image': 'Per-variant media needs Wix media ids, which only exist after ingestion; requires a follow-up patch.',
189 'variant.width': 'Dimension fields on the V3 variant create shape are unverified; not mapped rather than guessed.',
190 'variant.height': 'Dimension fields on the V3 variant create shape are unverified; not mapped rather than guessed.',
191 'variant.length': 'Dimension fields on the V3 variant create shape are unverified; not mapped rather than guessed.',
192 'image.url': 'Assembled into product.images by the adapter, then emitted via the media builder.',
193 'image.altText': 'Assembled into product.images by the adapter, then emitted via the media builder.',
194 'image.position': 'Ordering signal only: the first media item becomes the product main media.',
195 'category.path': 'Identity only: the dedupe key and crosswalk key, not a Wix field.',
196 'product.productType': 'Source product-type strings are a taxonomy, not the Wix productType enum (PHYSICAL/DIGITAL).',
197 'product.ribbon': 'Ribbon is a separate inline entity; not modelled by this spec version.',
198 'variant.cost': 'Cost sits behind the MERCHANT_DATA fieldset and is not settable on bulk create.',
199 'variant.barcode': 'No barcode field on the V3 variant create shape.',
200 'variant.inventoryPolicy': 'V3 models availability, not Shopify-style deny/continue policy.',
201 'variant.requiresShipping': 'Implied by productType PHYSICAL; not separately settable.',
202 'variant.taxable': 'Tax behaviour is a site-level/tax-group concern, not a variant field.',
203 'variant.weightUnit': 'Weight is sent in the site weight unit; the source unit only drives conversion.',
204 'variant.inventoryTracked': 'V3 inventory tracking is derived from the inventory item, not a flag.',
205 },
206};
207
208const CATEGORY_TARGET = {
209 entity: 'category',
210 endpoint: 'POST /categories/v1/categories',
211 provenance: 'verified',
212 // TRAP (verified): treeReference is TOP-LEVEL, a sibling of `category`. Nesting it 400s