theprogrammablemind_4wp 9.5.1-beta.6 → 9.5.1-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/fragments.js +126 -0
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"runtime.js",
|
|
46
46
|
"src/helpers.js",
|
|
47
47
|
"src/flatten.js",
|
|
48
|
+
"src/fragments.js",
|
|
48
49
|
"src/unflatten.js",
|
|
49
50
|
"src/config.js",
|
|
50
51
|
"src/configHelpers.js",
|
|
@@ -71,6 +72,6 @@
|
|
|
71
72
|
"sort-json": "^2.0.0",
|
|
72
73
|
"uuid": "^8.3.2"
|
|
73
74
|
},
|
|
74
|
-
"version": "9.5.1-beta.
|
|
75
|
+
"version": "9.5.1-beta.8",
|
|
75
76
|
"license": "UNLICENSED"
|
|
76
77
|
}
|
package/src/fragments.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const helpers = require('./helpers')
|
|
3
|
+
|
|
4
|
+
function fragmentInstantiator(args, contexts) {
|
|
5
|
+
return new Object({
|
|
6
|
+
contexts: () => contexts,
|
|
7
|
+
|
|
8
|
+
instantiate: async (mappings) => {
|
|
9
|
+
const root = _.cloneDeep(contexts);
|
|
10
|
+
|
|
11
|
+
// To prevent infinite loops on circular references
|
|
12
|
+
const visited = new WeakSet();
|
|
13
|
+
|
|
14
|
+
// Stack item: { node, path }
|
|
15
|
+
// path is an array like: [], ['user'], ['users', 0], ['users', 0, 'address']
|
|
16
|
+
const todo = [{ node: root, path: [] }];
|
|
17
|
+
|
|
18
|
+
args = { ...args };
|
|
19
|
+
|
|
20
|
+
while (todo.length > 0) {
|
|
21
|
+
const { node, path } = todo.pop();
|
|
22
|
+
|
|
23
|
+
// Skip non-compound values early
|
|
24
|
+
if (!helpers.isCompound(node)) continue;
|
|
25
|
+
|
|
26
|
+
// Prevent reprocessing (including circular refs)
|
|
27
|
+
if (visited.has(node)) continue;
|
|
28
|
+
visited.add(node);
|
|
29
|
+
|
|
30
|
+
// Attach clean, frozen path array (non-enumerable)
|
|
31
|
+
Object.defineProperty(node, '__path', {
|
|
32
|
+
value: Object.freeze(path.slice()), // immutable copy
|
|
33
|
+
writable: false,
|
|
34
|
+
enumerable: false,
|
|
35
|
+
configurable: false
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Helpful string version
|
|
39
|
+
Object.defineProperty(node, '__pathString', {
|
|
40
|
+
value: path.map(p => String(p)).join('.'),
|
|
41
|
+
enumerable: false
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Pass rich context to mappings
|
|
45
|
+
args.context = node;
|
|
46
|
+
args.path = path; // e.g. ['users', 0, 'profile']
|
|
47
|
+
args.pathString = path.map(p => String(p)).join('.'); // "users.0.profile"
|
|
48
|
+
|
|
49
|
+
// Apply mappings
|
|
50
|
+
for (const mapping of mappings) {
|
|
51
|
+
if (await mapping.match(args)) {
|
|
52
|
+
await mapping.apply(args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Traverse children — build path correctly for objects AND arrays
|
|
57
|
+
if (Array.isArray(node)) {
|
|
58
|
+
node.forEach((child, index) => {
|
|
59
|
+
if (helpers.isCompound(child)) {
|
|
60
|
+
todo.push({
|
|
61
|
+
node: child,
|
|
62
|
+
path: [...path, index] // index as number!
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
// Plain object
|
|
68
|
+
for (const key of Object.keys(node)) {
|
|
69
|
+
const child = node[key];
|
|
70
|
+
if (helpers.isCompound(child)) {
|
|
71
|
+
todo.push({
|
|
72
|
+
node: child,
|
|
73
|
+
path: [...path, key] // key as string
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return root;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function fragmentMapperInstantiator(values, modelFrom, modelTo) {
|
|
86
|
+
const paths = {}
|
|
87
|
+
for (const value of values) {
|
|
88
|
+
paths[value] = { value }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
const fi = fragmentInstantiator({paths}, modelFrom)
|
|
93
|
+
await fi.instantiate([
|
|
94
|
+
{
|
|
95
|
+
match: ({context, path}) => values.includes(context.value),
|
|
96
|
+
apply: ({context, path}) => paths[context.value].from = path
|
|
97
|
+
},
|
|
98
|
+
])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
const fi = fragmentInstantiator({paths}, modelTo)
|
|
103
|
+
await fi.instantiate([
|
|
104
|
+
{
|
|
105
|
+
match: ({context, path}) => values.includes(context.value),
|
|
106
|
+
apply: ({context, path}) => paths[context.value].to = path
|
|
107
|
+
},
|
|
108
|
+
])
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
instantiate: (actualFrom) => {
|
|
112
|
+
const actualTo = structuredClone(modelTo)
|
|
113
|
+
for (const value in paths) {
|
|
114
|
+
const { from, to } = paths[value]
|
|
115
|
+
const actualValue = helpers.getByPath(actualFrom, from, null)
|
|
116
|
+
helpers.setByPath(actualTo, to, actualValue)
|
|
117
|
+
}
|
|
118
|
+
return actualTo
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
fragmentInstantiator,
|
|
125
|
+
fragmentMapperInstantiator,
|
|
126
|
+
}
|