Setting the file. One moment.
Mapping Target Rules · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page Bundled file
lib/ mapping-target-rules.js
JavaScript · 94 lines · 7 KB
];
6 const errors = [], ids =new Set ();
7 for ( const rule of rules) {
8 const a = rule.applicability;
9 if ( ! rule.id || ids. has (rule.id)) errors. push ( 'mappingRules requires unique stable IDs' );
10 ids. add (rule.id);
11 if ( ! a?.sourceSystem || ! a.sourceEntity || ! Array. isArray (a.predicates) || ! a.predicates. length ||
12 a.predicates. some ( p =>! p.path || ! p.semantic || ! p.type)) errors. push ( `${ rule . id }: source/schema/semantic predicates required` );
13 const row = {sourceEntity:rule.id,requiredSourceEntityRefs:[],fieldMappings:[rule],discoveryCoverage:{paths:rule.sourcePaths || [],
14 sourceSnapshotDigest: 'rule-template' ,inventoryDigest: 'rule-template' ,uninspectedAreas:[],evidence:rule.evidence || []}};
15 errors. push ( ... validateEntity (row));
16 if ((rule.outputs || []). some ( o => o.targetRef !== targetRef)) errors. push ( `${ rule . id }: output targets a different entity` );
17 }
18 return errors;
19 }
20 function matchMappingRules ( rules = [], { sourceSystem , sourceEntity , coverage } = {}) {
21 const matches = rules. filter ( rule => {
22 const a = rule.applicability;
23 return a?.sourceSystem === sourceSystem && a.sourceEntity === sourceEntity && a.predicates. every ( p =>
24 (coverage?.paths || []). some ( leaf =>typeof leaf === 'object' && Object. entries (p). every (([ k , v ]) => canonical (leaf[k]) === canonical (v))));
25 });
26 const conflicts = [];
27 for ( let i = 0 ;i < matches. length ;i ++ ) for ( let j = i + 1 ;j < matches. length ;j ++ ) {
28 const a = matches[i], b = matches[j];
29 if (a.sourcePaths. some ( p => b.sourcePaths. includes (p)) && canonical ([a.outputs,a.transform,a.valuePolicy,a.referencePolicy || null ]) !== canonical ([b.outputs,b.transform,b.valuePolicy,b.referencePolicy || null ])) {
30 conflicts. push ({ruleIds:[a.id,b.id],sourcePaths:a.sourcePaths. filter ( p => b.sourcePaths. includes (p)),reason: 'Incompatible reviewed rules; return to mapping.' });
31 }
32 }
33 const conflicting =new Set (conflicts. flatMap ( c => c.ruleIds));
34 const unique =new Map ();
35 for ( const rule of matches. filter ( m =>! conflicting. has (m.id)). sort (( a , b ) => a.id. localeCompare (b.id))) {
36 const signature = canonical ([rule.sourcePaths,rule.outputs,rule.transform,rule.valuePolicy,rule.referencePolicy || null ]);
37 const prior = unique. get (signature);
38 if (prior) prior.ruleRefs. push (rule.id);
39 else unique. set (signature,{ ... structuredClone (rule),ruleRef:rule.id,ruleRefs:[rule.id]});
40 }
41 return {decisions:[ ... unique. values ()],conflicts};
42 }
43 function choosePreservation ({ nativeCandidates = [], extended = null , cms = null } = {}) {
44 const support = require ( './target-import-support.js' );
45 // The entity's knowledge decides import safety; the candidate's own flag only
46 // counts for a target with no knowledge file.
47 const safe = c => (c.output?.targetRef && support. knowledgeImportSafe (c.output.targetRef)) ?? c.importSafe;
48 const routable = c => c.suitable === true && c.writable === true &&
49 c.evidence?. length && c.output?.targetRef && c.output.writeOperation && c.output.writeInput && c.output.targetPath;
50 const native = nativeCandidates. find ( c => routable (c) && safe (c) === true );
51 const unsafe =! native && nativeCandidates. find ( c => routable (c) && support. importUnsafe (c.output.targetRef));
52 if (unsafe) { const why = support. importUnsafe (unsafe.output.targetRef); return {disposition: 'unresolved' ,outputs:[],reasonCode: 'import-unsafe' ,importSafeReason:why,
53 reason: `Native ${ unsafe . output . targetRef } is not import-safe: ${ why . summary }` }; }
54 // A native target with no reviewed writer is written by guarded raw requests. Only a
55 // write operation that is neither the entity's own writer nor the raw path is a gap; the import
56 // client refuses the old direct-REST fallback, and a missing writer never routes to custom storage.
57 if (native && ! native.writerId && ! support. hasEntityWriter (native.output.targetRef, native.output.writeOperation)) {
58 const refusal = support. writerRefusal (native.output.targetRef, native.output.writeOperation);
59 return {disposition: 'unresolved' ,outputs:[],reasonCode: 'missing-writer' ,missingWriter: true ,refusalReason:refusal?.reason || 'not-a-writer' ,
60 reason: `Write operation ${ native . output . writeOperation } is not an rp-target-wix writer for native ${ native . output . targetRef } (${ refusal ?. reason || 'not-a-writer'})${ refusal ?. instead ? `; use ${ refusal . instead }` : '; use its reviewed writer, or sendGuardedRawRequest when it has none'}.` };
61 }
62 if (native) return {disposition: 'native' ,outputs:[native.output],evidence:native.evidence, ... (support. rawRequestEntity (native.output.targetRef) ? {writer: 'raw-request' } : {})};
63 const admitted = extended && [ 'ecom/order' , 'stores/product' , 'crm/contact' ]. includes (extended.targetRef) &&
64 [ 'string' , 'TEXT' ]. includes (extended.fieldType) && extended.permissionsPreset === 'migration-internal' &&
65 extended.namespace === '_user_fields' && ! extended.targetNestedSubrecord && Number. isSafeInteger (extended.maxLength) && extended.maxLength > 0 &&
66 (extended.targetRef !== 'crm/contact' || extended.contactsVersion === 'contacts-v5' ||
67 (extended.contactsVersion === 'contacts-v4' && extended.explicitLegacyChoice === true ));
68 if (admitted && extended.output?.storage && extended.evidence?. length &&
69 ( ! extended.existingDefinition || extended.compatibilityVerified === true )) {
70 return {disposition: 'extended-field' ,outputs:[extended.output],evidence:extended.evidence,
71 setupRequirement:extended.setupRequirement};
72 }
73 if (cms?.output?.storage?.collectionId && cms.output.storage.fieldKey && cms.output.storage.fieldType &&
74 cms.output.storage.permissionsPreset === 'migration-internal' && cms.output.storage.parentLink && cms.evidence?. length &&
75 ( ! cms.existingDefinition || cms.compatibilityVerified === true )) {
76 return {disposition: 'cms' ,outputs:[cms.output],evidence:cms.evidence,
77 setupRequirement:{requirementClass: 'create-cms-collections' , ... cms.setupRequirement}};
78 }
79 return {disposition: 'unresolved' ,outputs:[],reason: 'No suitable native operation or compatible admitted custom storage.' };
80 }
81 function assertKnownMappings ( plan ) {
82 const fields = require ( './mapping-field-contract' ), proofs = require ( './dependency-proofs' );
83 const semantics = d => canonical ({sourcePaths:d.sourcePaths,disposition:d.disposition,transform:d.transform,valuePolicy:d.valuePolicy,referencePolicy:d.referencePolicy || null ,
84 outputs:(d.outputs || []). map (({ id , supersedes , verification , ... binding }) => binding)});
85 for ( const row of fields. entityRows (plan)) {
86 let knowledge; try {knowledge = proofs. domain (row.targetRef);} catch (error){ if (error.code?. startsWith ( 'mapping_assessment_' )) throw error; continue ;}
87 if ( ! knowledge.mappingRules?. length ) continue ;
88 const matched = matchMappingRules (knowledge.mappingRules,{sourceSystem:row.sourceSystem || plan.sourceSystem || plan.sourcePlatform || plan.source?.system,
89 sourceEntity:row.sourceEntity,coverage:row.discoveryCoverage});
90 if (matched.conflicts. length ) throw Object. assign ( new Error ( 'mapping_known_rules_conflict' ),{code: 'mapping_known_rules_conflict' ,sourceEntity:row.sourceEntity});
91 for ( const rule of matched.decisions) if ( ! row.fieldMappings. some ( d => semantics (d) === semantics (rule))) throw Object. assign ( new Error ( 'mapping_known_rule_not_applied' ),{code: 'mapping_known_rule_not_applied' ,ruleId:rule.id,sourceEntity:row.sourceEntity});
92 }
93 }
94 module . exports = {validateMappingRules,matchMappingRules,choosePreservation,assertKnownMappings};