Setting the file. One moment.
Canonical Record · Rp Source CSV · wix/skills · Skills Docs
ContentsBack to the top of the page lib/ canonical-record.js
JavaScript · 136 lines · 5 KB
15 //
16 // Longer term the readers should emit canonical records directly and this module becomes a no-op
17 // identity map. It is deliberately a separate step rather than being folded into the reader, so the
18 // change can happen per vendor without a flag day.
19
20 const { validateCanonicalPath } = require ( '../../rp-target-wix/lib/wix-target-spec.js' );
21
22 // `rename` moves a value unchanged. `derive` computes one from the whole record — kept to genuine
23 // multi-field logic, because a derive that only reads one field should be a rename.
24 const READER_FIELD_MAPS = {
25 shopify: {
26 product: {
27 rename: {
28 title: 'name' ,
29 bodyHtml: 'description' ,
30 slug: 'slug' ,
31 vendor: 'brand' ,
32 seoTitle: 'seoTitle' ,
33 seoDescription: 'seoDescription' ,
34 images: 'images' ,
35 optionNames: 'optionNames' ,
36 categoryPath: 'categoryPath' ,
37 tags: 'tags' ,
38 sourceId: 'sourceId' ,
39 },
40 derive: {
41 // Shopify carries BOTH a lifecycle status and a publish flag; a product is visible only
42 // when both agree. Collapsing to either one alone silently publishes drafts.
43 visible : ( record ) => record.status === 'active' && record.published !== false ,
44 },
45 },
46 variant: {
47 rename: {
48 sku: 'sku' ,
49 price: 'price' ,
50 compareAtPrice: 'compareAtPrice' ,
51 // The reader has already converted grams to the site's weight unit; the canonical field
52 // carries a bare number, so the converted value is the one that travels.
53 weightKg: 'weight' ,
54 barcode: 'barcode' ,
55 cost: 'cost' ,
56 choices: 'choices' ,
57 },
58 derive: {
59 visible : () => true ,
60 },
61 },
62 category: {
63 rename: { name: 'name' , parentPath: 'parentPath' , path: 'path' , sourceId: 'sourceId' },
64 derive: { visible : () => true },
65 },
66 },
67 };
68
69 // woocommerce / magento / bigcommerce readers are not built yet. Registering them explicitly as
70 // absent is better than falling back to shopify's map and silently mis-renaming fields.
71 const SUPPORTED_VENDORS = Object. keys ( READER_FIELD_MAPS );
72
73 function mapEntity ( record , entityMap ) {
74 const out = {};
75 for ( const [ readerKey , canonicalKey ] of Object. entries (entityMap.rename || {})) {
76 const value = record[readerKey];
77 if (value !== undefined && value !== null ) out[canonicalKey] = value;
78 }
79 for ( const [ canonicalKey , fn ] of Object. entries (entityMap.derive || {})) {
80 out[canonicalKey] = fn (record);
81 }
82 return out;
83 }
84
85 function mapFor ( vendor , entity ) {
86 const vendorMap = READER_FIELD_MAPS [vendor];
87 if ( ! vendorMap) {
88 throw new Error (
89 `canonical-record: no reader field map for vendor "${ vendor }". ` +
90 `Known: ${ SUPPORTED_VENDORS . join ( ', ' ) }. Add one in READER_FIELD_MAPS rather than reusing another vendor's.` ,
91 );
92 }
93 const entityMap = vendorMap[entity];
94 if ( ! entityMap) throw new Error ( `canonical-record: vendor "${ vendor }" has no map for entity "${ entity }"` );
95 return entityMap;
96 }
97
98 function toCanonicalProduct ( record , { vendor = 'shopify' } = {}) {
99 const product = mapEntity (record, mapFor (vendor, 'product' ));
100 const variantMap = mapFor (vendor, 'variant' );
101 product.variants = (record.variants || []). map (( variant ) => mapEntity (variant, variantMap));
102 return product;
103 }
104
105 function toCanonicalCategory ( record , { vendor = 'shopify' } = {}) {
106 return mapEntity (record, mapFor (vendor, 'category' ));
107 }
108
109 // Self-check: every canonical key a map produces must be a real canonical field. Runs at require
110 // time so a typo in the map is a load error, not a field that silently never lands in Wix.
111 function validateMaps () {
112 const problems = [];
113 for ( const [ vendor , entities ] of Object. entries ( READER_FIELD_MAPS )) {
114 for ( const [ entity , entityMap ] of Object. entries (entities)) {
115 const keys = [ ... Object. values (entityMap.rename || {}), ... Object. keys (entityMap.derive || {})];
116 for ( const key of keys) {
117 // Canonical paths are `<entity>.<field>`; the maps hold the bare field name.
118 if ( ! validateCanonicalPath ( `${ entity }.${ key }` )) problems. push ( `${ vendor }.${ entity } -> ${ entity }.${ key }` );
119 }
120 }
121 }
122 return problems;
123 }
124
125 const MAP_PROBLEMS = validateMaps ();
126 if ( MAP_PROBLEMS . length ) {
127 throw new Error ( `canonical-record: map targets are not canonical fields: ${ MAP_PROBLEMS . join ( ', ' ) }` );
128 }
129
130 module . exports = {
131 READER_FIELD_MAPS,
132 SUPPORTED_VENDORS,
133 toCanonicalProduct,
134 toCanonicalCategory,
135 validateMaps,
136 };