Setting the file. One moment.
Mapping Resolution · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page lib/ mapping-resolution.js
JavaScript · 97 lines · 6 KB
13
const
fs
=
require
(
'node:fs'
);
14 const path = require ( 'node:path' );
15 const L = require ( './source-location' );
16
17 const FILE = 'mapping/mapping-resolution.json' ;
18 const GENERATOR = 'wix-replatform/lib/mapping-resolution.js' ;
19 const SCHEMA_VERSION = 2 ;
20 const SEED_GENERATOR = 'rp-mapper/resolve-mapping.js' ;
21 const SEED_KEYS = [ 'vendor' , 'overlayVersion' , 'overlayPresent' , 'fieldMappings' , 'coverage' , 'derivedEntities' , 'residue' , 'overlayDrift' , 'canonicalErrors' , 'duplicateColumns' ];
22
23 function fields () { return require ( './mapping-field-contract' ); }
24 // The seed is the CSV resolver's own output, or the seed already retained by a prior projection.
25 function seedOf ( existing ) {
26 if ( ! existing || typeof existing !== 'object' ) return null ;
27 if (existing.generator === SEED_GENERATOR ) {
28 const body = Object. fromEntries ( SEED_KEYS . filter ( k => Object. hasOwn (existing, k)). map ( k => [k, existing[k]]));
29 return { body, provenance: { generator: existing.generator, schemaVersion: existing.schemaVersion ?? null , generatedAt: existing.generatedAt ?? null , digest: fields (). digest (body) } };
30 }
31 if (existing.generator === GENERATOR && existing.seed) {
32 const body = Object. fromEntries ( SEED_KEYS . filter ( k => Object. hasOwn (existing, k)). map ( k => [k, existing[k]]));
33 if ( fields (). digest (body) !== existing.seed.digest) return { body, provenance: { ... existing.seed, digest: fields (). digest (body), tampered: true } };
34 return { body, provenance: existing.seed };
35 }
36 return null ;
37 }
38 function entityResolution ( row ) {
39 const F = fields (), ref = F . entityRef (row);
40 if (row.fieldMappingStatus === 'pending' ) return { sourceEntity: ref, status: 'pending-inventory' , decisions: [], unclaimed: [] };
41 if (row.targetClassification === 'manual-mapping' ) return { sourceEntity: ref, status: 'manual' , decisions: [], unclaimed: [] };
42 const decisions = (row.fieldMappings || []). map ( d => ({
43 id: d.id,
44 disposition: d.disposition,
45 pending: F . pendingDecision (row, d),
46 ... (d.disposition === 'deferred-field' ? {pendingRestoration: true , recovery:d.recovery} : {}),
47 locations: F . decisionLocations (d). sort (),
48 outputs: (d.outputs || []). map ( o => ({ ref: `${ d . id }:${ o . id }` , targetRef: o.targetRef, writeOperation: o.writeOperation, writePath: L . join (o.writeInput, o.targetPath), sourceLocations: F . outputLocations (d, o). sort (), ... (o.appliesWhen !== undefined ? { appliesWhen: o.appliesWhen } : {}) })),
49 omittedOutputs: (d.omittedOutputs || []). map ( o => ({ writeOperation: o.writeOperation, writePath: L . join (o.writeInput, o.targetPath) }))
50 })). sort (( a , b ) => a.id < b.id ? - 1 : a.id > b.id ? 1 : 0 );
51 const claimed = new Set (decisions. flatMap ( d => d.locations));
52 const coverage = F . coverageLocations (row);
53 return {
54 sourceEntity: ref,
55 status: decisions. some ( d => d.pending) ? 'pending-decisions' : 'resolved' ,
56 sourceSnapshotDigest: row.discoveryCoverage?.sourceSnapshotDigest || null ,
57 inventoryDigest: row.discoveryCoverage?.inventoryDigest || null ,
58 selectors: (row.discoveryCoverage?.paths || []). filter ( p => p && typeof p === 'object' && p.dynamic === true )
59 . map ( p => { const t = L . template (p); return { array: t.array, keyPath: t.keyPath, valuePath: t.path, observedKeys: [ ... p.observedKeys]. map (String). sort () }; }),
60 coverageLocations: coverage. length ,
61 decisions,
62 unclaimed: coverage. filter ( p => ! claimed. has (p)). sort ()
63 };
64 }
65 // Pure: same plan + seed always yields the same bytes.
66 function project ( plan , { seed = null } = {}) {
67 const F = fields ();
68 if (plan?.fieldContractVersion !== 1 ) throw Object. assign ( new Error ( 'mapping resolution requires a fieldContractVersion:1 authority' ), { code: 'mapping_resolution_invalid' });
69 const entities = F . entityRows (plan). map (entityResolution). sort (( a , b ) => a.sourceEntity < b.sourceEntity ? - 1 : a.sourceEntity > b.sourceEntity ? 1 : 0 );
70 return {
71 ... (seed ? seed.body : {}),
72 schemaVersion: SCHEMA_VERSION ,
73 generator: GENERATOR ,
74 deterministic: true ,
75 locationEncoding: L . ENCODING ,
76 authority: { path: 'mapping/mapping-plan.json' , fieldContractVersion: 1 , digest: F . digest (plan) },
77 source: { platform: plan.sourcePlatform || null , kind: seed ? 'csv-resolver-seed' : 'reviewed-authority' },
78 seed: seed ? seed.provenance : null ,
79 entities
80 };
81 }
82 function readExisting ( projectDir ) {
83 const file = path. join (projectDir, FILE );
84 if ( ! fs. existsSync (file)) return null ;
85 try { return JSON . parse (fs. readFileSync (file, 'utf8' )); } catch { return null ; }
86 }
87 function expected ( projectDir , plan ) {
88 return project (plan, { seed: seedOf ( readExisting (projectDir)) });
89 }
90 function compare ( projectDir , plan ) {
91 const actual = readExisting (projectDir);
92 if ( ! actual) return { ok: false , errors: [ `${ FILE } missing; rerun the mapping contract` ] };
93 if (actual.seed?.tampered) return { ok: false , errors: [ `${ FILE } seed changed after projection; rerun the resolver and contract` ] };
94 const want = expected (projectDir, plan);
95 return fields (). canonical (actual) === fields (). canonical (want) ? { ok: true , errors: [] } : { ok: false , errors: [ `${ FILE } is stale or conflicts with the mapping authority; rerun the mapping contract` ] };
96 }
97 module . exports = { FILE, GENERATOR, SCHEMA_VERSION, SEED_GENERATOR, SEED_KEYS, seedOf, project, expected, compare };