theprogrammablemind 9.7.1 → 9.8.0-beta.1
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/client.js +72 -54
- package/index.js +6 -2
- package/package.json +1 -1
- package/src/config.js +33 -3
- package/src/configHelpers.js +31 -13
- package/src/debug.js +17 -2
- package/src/digraph.js +18 -16
- package/src/digraph_internal.js +20 -12
- package/src/flatten.js +15 -19
- package/src/fragments.js +28 -2
- package/src/generators.js +36 -16
- package/src/helpers.js +126 -22
- package/src/project2.js +4 -0
- package/src/semantics.js +38 -24
package/src/debug.js
CHANGED
|
@@ -27,6 +27,18 @@ const unhit = (name) => {
|
|
|
27
27
|
hits[name] = false
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const hitScoped = async (name, call) => {
|
|
31
|
+
hit(name)
|
|
32
|
+
try {
|
|
33
|
+
const result = await call()
|
|
34
|
+
unhit(name)
|
|
35
|
+
return result
|
|
36
|
+
} catch( e ) {
|
|
37
|
+
unhit(name)
|
|
38
|
+
throw e
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
const wasHit = (name) => {
|
|
31
43
|
return hits[name]
|
|
32
44
|
}
|
|
@@ -64,16 +76,19 @@ const _break = (name) => {
|
|
|
64
76
|
}
|
|
65
77
|
}
|
|
66
78
|
|
|
79
|
+
const breakAt = _break;
|
|
80
|
+
|
|
67
81
|
module.exports = {
|
|
68
82
|
counter,
|
|
69
83
|
get,
|
|
70
84
|
_break, // break is a keyword
|
|
71
|
-
|
|
85
|
+
breakAt,
|
|
72
86
|
// used for tests
|
|
73
87
|
reset,
|
|
74
88
|
hit,
|
|
75
89
|
unhit,
|
|
76
90
|
wasHit,
|
|
77
91
|
getDb,
|
|
78
|
-
setDb
|
|
92
|
+
setDb,
|
|
93
|
+
hitScoped,
|
|
79
94
|
}
|
package/src/digraph.js
CHANGED
|
@@ -73,11 +73,25 @@ class Digraph {
|
|
|
73
73
|
this._edges = edges
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
tos(from) {
|
|
77
|
+
const tos = []
|
|
78
|
+
for (const edge of this._edges) {
|
|
79
|
+
if (edge[0] == from) {
|
|
80
|
+
tos.push(edge[1])
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return tos
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
froms(to) {
|
|
87
|
+
const froms = []
|
|
88
|
+
for (const edge of this._edges) {
|
|
89
|
+
if (edge[1] == to) {
|
|
90
|
+
froms.push(edge[0])
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return froms
|
|
79
94
|
}
|
|
80
|
-
*/
|
|
81
95
|
|
|
82
96
|
acdcs (s, from, to) {
|
|
83
97
|
if (!s) {
|
|
@@ -170,18 +184,6 @@ class Digraph {
|
|
|
170
184
|
return this.minima(common)
|
|
171
185
|
}
|
|
172
186
|
|
|
173
|
-
/*
|
|
174
|
-
maxima (nodes) {
|
|
175
|
-
const maxima = new Set(nodes)
|
|
176
|
-
const descendants = new Set([])
|
|
177
|
-
nodes.forEach((node) => {
|
|
178
|
-
this.descendants(node).forEach((n) => descendants.add(n))
|
|
179
|
-
})
|
|
180
|
-
descendants.forEach((n) => maxima.delete(n))
|
|
181
|
-
return maxima
|
|
182
|
-
}
|
|
183
|
-
*/
|
|
184
|
-
|
|
185
187
|
add (child, parent) {
|
|
186
188
|
this._edges.push([child, parent])
|
|
187
189
|
}
|
package/src/digraph_internal.js
CHANGED
|
@@ -39,6 +39,26 @@ class DigraphInternal {
|
|
|
39
39
|
this._edges = edges
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
tos(from) {
|
|
43
|
+
const tos = []
|
|
44
|
+
for (const edge of this._edges) {
|
|
45
|
+
if (edge[0] == from) {
|
|
46
|
+
tos.push(edge[1])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return tos
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
froms(to) {
|
|
53
|
+
const froms = []
|
|
54
|
+
for (const edge of this._edges) {
|
|
55
|
+
if (edge[1] == to) {
|
|
56
|
+
froms.push(edge[0])
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return froms
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
get length () {
|
|
43
63
|
return this._edges.length
|
|
44
64
|
}
|
|
@@ -137,18 +157,6 @@ class DigraphInternal {
|
|
|
137
157
|
return this.minima(common)
|
|
138
158
|
}
|
|
139
159
|
|
|
140
|
-
/*
|
|
141
|
-
maxima (nodes) {
|
|
142
|
-
const maxima = new Set(nodes)
|
|
143
|
-
const descendants = new Set([])
|
|
144
|
-
nodes.forEach((node) => {
|
|
145
|
-
this.descendants(node).forEach((n) => descendants.add(n))
|
|
146
|
-
})
|
|
147
|
-
descendants.forEach((n) => maxima.delete(n))
|
|
148
|
-
return maxima
|
|
149
|
-
}
|
|
150
|
-
*/
|
|
151
|
-
|
|
152
160
|
add (child, parent) {
|
|
153
161
|
this._edges.push([child, parent])
|
|
154
162
|
}
|
package/src/flatten.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const { isArray, isObject, isCompound } = require('./helpers')
|
|
1
|
+
const { isArray, isObject, isCompound, } = require('./helpers')
|
|
2
|
+
const debug = require('./debug')
|
|
2
3
|
const _ = require('lodash')
|
|
3
4
|
|
|
4
|
-
const DEBUG = (
|
|
5
|
+
const DEBUG = (label, details) => {
|
|
5
6
|
// DEBUG(...args)
|
|
6
7
|
}
|
|
7
8
|
|
|
@@ -46,33 +47,24 @@ const zip = (a1, a2) => {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const flattenListHelper = (markers, values) => {
|
|
49
|
-
const flats = []
|
|
50
50
|
let wasFlattened = false
|
|
51
|
-
|
|
51
|
+
const all = []
|
|
52
52
|
for (const value of values) {
|
|
53
53
|
if (!isCompound(value)) {
|
|
54
|
-
|
|
54
|
+
all.push([value])
|
|
55
55
|
} else {
|
|
56
56
|
const [more, wf] = flatten(markers, value)
|
|
57
57
|
if (wf) {
|
|
58
58
|
wasFlattened = wasFlattened || wf
|
|
59
|
-
|
|
59
|
+
all.push(more)
|
|
60
60
|
} else {
|
|
61
|
-
|
|
61
|
+
all.push(...more.map((m) => [m]))
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return [[singles], wasFlattened]
|
|
69
|
-
} else {
|
|
70
|
-
const results = []
|
|
71
|
-
for (const flat of cartesian(...flats)) {
|
|
72
|
-
results.push(singles.concat(flat))
|
|
73
|
-
}
|
|
74
|
-
return [results, wasFlattened]
|
|
75
|
-
}
|
|
66
|
+
const result = cartesian(...all)
|
|
67
|
+
return [result, wasFlattened]
|
|
76
68
|
}
|
|
77
69
|
|
|
78
70
|
// returns (list, wasFlattened)
|
|
@@ -84,6 +76,9 @@ const flatten = (markers, value) => {
|
|
|
84
76
|
if (value.flatten === false) {
|
|
85
77
|
return [[value], false]
|
|
86
78
|
}
|
|
79
|
+
|
|
80
|
+
const flatten_ignore = value.flatten_ignore || []
|
|
81
|
+
|
|
87
82
|
const marker = value.marker
|
|
88
83
|
let properties = value
|
|
89
84
|
|
|
@@ -113,8 +108,9 @@ const flatten = (markers, value) => {
|
|
|
113
108
|
for (const key in properties) {
|
|
114
109
|
let wf = false
|
|
115
110
|
let values
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
if (flatten_ignore.includes(key)) {
|
|
112
|
+
values = [properties[key]]
|
|
113
|
+
} else if (isObject(properties[key])) {
|
|
118
114
|
const context = properties[key];
|
|
119
115
|
[values, wf] = flatten(markers, context)
|
|
120
116
|
// } else if (isinstance(properties[key], list)) {
|
package/src/fragments.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
2
|
const helpers = require('./helpers')
|
|
3
3
|
|
|
4
|
+
function createChangeTracker(obj) {
|
|
5
|
+
const changes = new Set();
|
|
6
|
+
|
|
7
|
+
const handler = {
|
|
8
|
+
set(target, prop, value) {
|
|
9
|
+
changes.add(prop);
|
|
10
|
+
target[prop] = value;
|
|
11
|
+
return true;
|
|
12
|
+
},
|
|
13
|
+
deleteProperty(target, prop) {
|
|
14
|
+
changes.add(prop);
|
|
15
|
+
delete target[prop];
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
proxy: new Proxy(obj, handler),
|
|
22
|
+
getChanges: () => Array.from(changes)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
function fragmentInstantiator (args, contexts) {
|
|
5
27
|
return new Object({
|
|
6
28
|
contexts: () => {
|
|
@@ -13,13 +35,15 @@ function fragmentInstantiator (args, contexts) {
|
|
|
13
35
|
args.pathEquals = helpers.pathEquals
|
|
14
36
|
while (todo.length > 0) {
|
|
15
37
|
const { context, path } = todo.pop()
|
|
16
|
-
|
|
38
|
+
const { proxy, getChanges } = createChangeTracker(context);
|
|
39
|
+
args.context = proxy
|
|
17
40
|
args.path = path
|
|
18
41
|
for (const mapping of mappings) {
|
|
19
42
|
if (await mapping.match(args)) {
|
|
20
43
|
await mapping.apply(args)
|
|
21
44
|
}
|
|
22
45
|
}
|
|
46
|
+
const ignore = getChanges()
|
|
23
47
|
for (const key of Object.keys(context)) {
|
|
24
48
|
// if (['number', 'string', 'boolean'].includes(typeof (context[key]))) {
|
|
25
49
|
if (!helpers.isCompound(context[key])) {
|
|
@@ -28,7 +52,9 @@ function fragmentInstantiator (args, contexts) {
|
|
|
28
52
|
if (context[key].instantiated) {
|
|
29
53
|
continue
|
|
30
54
|
}
|
|
31
|
-
|
|
55
|
+
if (!ignore.includes(key)) {
|
|
56
|
+
todo.push({ context: context[key], path: [...path, key] })
|
|
57
|
+
}
|
|
32
58
|
}
|
|
33
59
|
}
|
|
34
60
|
if (contexts.length == 1 && instantiated.length == 1) {
|
package/src/generators.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { args: contextArgs, normalizeGenerator } = require('./helpers')
|
|
2
2
|
const Lines = require('../lines')
|
|
3
3
|
const helpers = require('./helpers')
|
|
4
|
+
const debug = require('./debug')
|
|
4
5
|
|
|
5
6
|
class Generator {
|
|
6
7
|
// constructor ({ match, apply, uuid, index, km, priority, notes }) {
|
|
@@ -65,14 +66,16 @@ class Generator {
|
|
|
65
66
|
}
|
|
66
67
|
const args = Object.assign({}, baseArgs, moreArgs, (baseArgs.getUUIDScoped || (() => { return {} }))(this.uuid))
|
|
67
68
|
// return this.match(args)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
return await debug.hitScoped(args.callId, async () => {
|
|
70
|
+
const matches = await this.match(args)
|
|
71
|
+
if ((matches && (options.debug || {}).match) ||
|
|
72
|
+
callId === this.callId) {
|
|
73
|
+
// next line is the matcher
|
|
74
|
+
debugger // eslint-disable-line no-debugger
|
|
75
|
+
await this.match(args)
|
|
76
|
+
}
|
|
77
|
+
return matches
|
|
78
|
+
})
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// apply (baseArgs, objects, g, gs, context, hierarchy, config, response, log, options = {}) {
|
|
@@ -121,11 +124,12 @@ class Generator {
|
|
|
121
124
|
if (this.property === 'generatorp') {
|
|
122
125
|
args.g = args.gp
|
|
123
126
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
return await debug.hitScoped(args.callId, async () => {
|
|
128
|
+
if ((options.debug || {}).apply || process.env.DEBUG_CALLID == args.callId || callId === this.callId) {
|
|
129
|
+
debugger // eslint-disable-line no-debugger
|
|
130
|
+
}
|
|
131
|
+
return await this._apply(args) // DEBUG: This is the line you want to step into
|
|
132
|
+
})
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -180,12 +184,22 @@ class Generators {
|
|
|
180
184
|
let generated = ''
|
|
181
185
|
let applied = false
|
|
182
186
|
const stack = args.calls.push()
|
|
187
|
+
const pOrRTag = assumed.isResponse ? "(RESPONSE)" : "(PARAPHRASE)"
|
|
188
|
+
const finallyList = []; // called after the generator is processed
|
|
189
|
+
args._finally = (call) => finallyList.unshift(call)
|
|
183
190
|
for (let igenerator = 0; igenerator < this.generators.length; ++igenerator) {
|
|
184
191
|
const generator = this.generators[igenerator]
|
|
185
192
|
if (await generator.matches(args, objects, context, hierarchy, config, options)) {
|
|
186
193
|
const log = (message) => { this.logs.push(message) }
|
|
187
194
|
// this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${stringify(context)}`)
|
|
188
195
|
let errorMessage = 'The apply function did not return a value'
|
|
196
|
+
if (process.env.TPMKMS_TRACE) {
|
|
197
|
+
console.error("TPMKMS_TRACE", debug.counter("TPMKMS_TRACE"))
|
|
198
|
+
console.error(generator.toLabel())
|
|
199
|
+
console.error(generator.toString())
|
|
200
|
+
console.log(`To debug this use debug.breakAt('${args.calls.current()}') or DEBUG_CALLID="${args.calls.current()}"`)
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
try {
|
|
190
204
|
generated = await generator.apply(args, objects, context, hierarchy, config, response, log, options)
|
|
191
205
|
} catch (e) {
|
|
@@ -207,6 +221,7 @@ class Generators {
|
|
|
207
221
|
const widths = Lines.addRemainder([10, 10])
|
|
208
222
|
const lines = new Lines(widths)
|
|
209
223
|
lines.setElement(0, 0, 'Generator')
|
|
224
|
+
lines.setElement(0, 1, `#${context.context_id || "?"}`)
|
|
210
225
|
const source = `${generator.km}/#${generator.index}`
|
|
211
226
|
lines.setElement(0, 2, `ERROR while applying (${source}) ${generator.toLabel()}`)
|
|
212
227
|
lines.newRow()
|
|
@@ -220,7 +235,7 @@ class Generators {
|
|
|
220
235
|
lines.setElement(0, 2, stack)
|
|
221
236
|
lines.newRow()
|
|
222
237
|
lines.setElement(0, 1, 'DEBUG')
|
|
223
|
-
lines.setElement(0, 2, `To debug this use args.
|
|
238
|
+
lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}') or DEBUG_CALLID="${args.calls.current()}"`)
|
|
224
239
|
lines.newRow()
|
|
225
240
|
lines.setElement(0, 1, 'ERROR')
|
|
226
241
|
lines.setElement(0, 2, errorMessage)
|
|
@@ -235,6 +250,7 @@ class Generators {
|
|
|
235
250
|
const widths = Lines.addRemainder([10, 10])
|
|
236
251
|
const lines = new Lines(widths)
|
|
237
252
|
lines.setElement(0, 0, 'Generator')
|
|
253
|
+
lines.setElement(0, 1, `#${context.context_id || "?"}`)
|
|
238
254
|
if (generator.index > -1 && generator.km) {
|
|
239
255
|
// lines.setElement(0, 2, `KM '${generator.km}' ordinal: ${generator.index}`)
|
|
240
256
|
lines.setElement(0, 2, generator.toLabel())
|
|
@@ -246,13 +262,13 @@ class Generators {
|
|
|
246
262
|
lines.setElement(0, 2, generator.toString())
|
|
247
263
|
lines.newRow()
|
|
248
264
|
lines.setElement(0, 1, 'RESULT')
|
|
249
|
-
lines.setElement(0, 2, generated)
|
|
265
|
+
lines.setElement(0, 2, generated + " " + pOrRTag)
|
|
250
266
|
lines.newRow()
|
|
251
267
|
lines.setElement(0, 1, 'STACK')
|
|
252
268
|
lines.setElement(0, 2, stack)
|
|
253
269
|
lines.newRow()
|
|
254
270
|
lines.setElement(0, 1, 'DEBUG')
|
|
255
|
-
lines.setElement(0, 2, `To debug this use
|
|
271
|
+
lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}')`)
|
|
256
272
|
lines.newRow()
|
|
257
273
|
lines.setElement(0, 1, 'TO')
|
|
258
274
|
lines.setElement(0, 2, `context_id: ${context.context_id}`)
|
|
@@ -263,11 +279,15 @@ class Generators {
|
|
|
263
279
|
break
|
|
264
280
|
}
|
|
265
281
|
}
|
|
282
|
+
for (const f of finallyList) {
|
|
283
|
+
await f()
|
|
284
|
+
}
|
|
266
285
|
args.calls.pop()
|
|
267
286
|
if (!applied && config.getDebug()) {
|
|
268
287
|
const widths = Lines.addRemainder([10, 10])
|
|
269
288
|
const lines = new Lines(widths)
|
|
270
289
|
lines.setElement(0, 0, 'Generator')
|
|
290
|
+
lines.setElement(0, 1, `#${context.context_id || "?"}`)
|
|
271
291
|
lines.setElement(0, 2, 'No generator applied')
|
|
272
292
|
lines.newRow()
|
|
273
293
|
lines.setElement(0, 1, 'STACK')
|
package/src/helpers.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
const deepEqual = require('deep-equal')
|
|
2
2
|
const stringify = require('json-stable-stringify')
|
|
3
3
|
|
|
4
|
+
|
|
5
|
+
// check if member function of a class have been overriden by an subclass
|
|
6
|
+
class OverrideCheck {
|
|
7
|
+
constructor(base, suffix='API') {
|
|
8
|
+
this.base = base
|
|
9
|
+
this.checks = Object.getOwnPropertyNames(base.prototype).filter(key => typeof base.prototype[key] === 'function' && key.endsWith(suffix));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
check(obj) {
|
|
13
|
+
const overriden = obj.constructor !== obj.baseAPI
|
|
14
|
+
if (!overriden) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
for (const check of this.checks) {
|
|
18
|
+
if (obj[check] == this.base.prototype[check]) {
|
|
19
|
+
throw new Error(`For ${obj.constructor.name} you need to override ${check}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
4
25
|
function pathEquals (p1, p2) {
|
|
5
26
|
if (p1.length !== p2.length) {
|
|
6
27
|
return false
|
|
@@ -523,52 +544,77 @@ const stableId = (tag) => {
|
|
|
523
544
|
}
|
|
524
545
|
|
|
525
546
|
function getByPath(obj, path, defaultValue) {
|
|
547
|
+
const segments = Array.isArray(path) ? path : [path];
|
|
548
|
+
const keys = segments.flatMap((segment) => {
|
|
549
|
+
if (typeof segment !== "string") return [segment];
|
|
550
|
+
return segment
|
|
551
|
+
.replace(/\[(\d+)\]/g, ".$1")
|
|
552
|
+
.split(".")
|
|
553
|
+
.filter((key) => key !== "");
|
|
554
|
+
});
|
|
555
|
+
|
|
526
556
|
let current = obj;
|
|
527
|
-
for (const key of
|
|
557
|
+
for (const key of keys) {
|
|
528
558
|
if (current === null || current === undefined) return defaultValue;
|
|
529
|
-
if (typeof current !==
|
|
559
|
+
if (typeof current !== "object") return defaultValue;
|
|
530
560
|
current = current[key];
|
|
531
561
|
}
|
|
532
562
|
return current === undefined ? defaultValue : current;
|
|
533
563
|
}
|
|
534
564
|
|
|
565
|
+
function normalizePath(path) {
|
|
566
|
+
const segments = Array.isArray(path) ? path : [path];
|
|
567
|
+
return segments.flatMap((segment) => {
|
|
568
|
+
if (typeof segment !== "string") return [segment];
|
|
569
|
+
return segment
|
|
570
|
+
.replace(/\[(\d+)\]/g, ".$1")
|
|
571
|
+
.split(".")
|
|
572
|
+
.filter((key) => key !== "");
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function isArrayIndex(key) {
|
|
577
|
+
if (typeof key === "number") return Number.isInteger(key) && key >= 0;
|
|
578
|
+
return key !== "" && String(key >>> 0) === String(key);
|
|
579
|
+
}
|
|
580
|
+
|
|
535
581
|
/**
|
|
536
|
-
* Set a value in an object by path array.
|
|
582
|
+
* Set a value in an object by path (string or array).
|
|
537
583
|
* Automatically creates missing objects {} or arrays [] as needed.
|
|
538
584
|
*
|
|
539
585
|
* @param {Object} obj - The root object to modify
|
|
540
|
-
* @param {Array<string|number>} path -
|
|
586
|
+
* @param {string|Array<string|number>} path - Path keys/indices
|
|
541
587
|
* @param {*} value - Value to set
|
|
542
588
|
* @returns {*} The set value (for chaining)
|
|
543
589
|
*/
|
|
544
590
|
function setByPath(obj, path, value) {
|
|
545
|
-
|
|
546
|
-
|
|
591
|
+
const keys = normalizePath(path);
|
|
592
|
+
|
|
593
|
+
if (keys.length === 0) {
|
|
594
|
+
throw new Error("Path must be a non-empty string or array");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (obj == null || typeof obj !== "object") {
|
|
598
|
+
throw new Error("Target must be an object or array");
|
|
547
599
|
}
|
|
548
600
|
|
|
549
601
|
let current = obj;
|
|
550
602
|
|
|
551
|
-
for (let i = 0; i <
|
|
552
|
-
const key =
|
|
553
|
-
const isLast = i ===
|
|
603
|
+
for (let i = 0; i < keys.length; i++) {
|
|
604
|
+
const key = keys[i];
|
|
605
|
+
const isLast = i === keys.length - 1;
|
|
554
606
|
|
|
555
607
|
if (isLast) {
|
|
556
|
-
// Final step — just assign
|
|
557
608
|
current[key] = value;
|
|
558
609
|
} else {
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
if (current[key] == null) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
? []
|
|
566
|
-
: {};
|
|
567
|
-
} else if (Array.isArray(current[key]) && typeof nextKey !== 'number') {
|
|
568
|
-
// Safety: if current is array but next key isn't a valid index → convert to object
|
|
610
|
+
const nextKey = keys[i + 1];
|
|
611
|
+
const nextIsIndex = isArrayIndex(nextKey);
|
|
612
|
+
|
|
613
|
+
if (current[key] == null || typeof current[key] !== "object") {
|
|
614
|
+
current[key] = nextIsIndex ? [] : {};
|
|
615
|
+
} else if (Array.isArray(current[key]) && !nextIsIndex) {
|
|
569
616
|
current[key] = { ...current[key] };
|
|
570
|
-
} else if (!Array.isArray(current[key]) &&
|
|
571
|
-
// If next expects array but current is object → convert
|
|
617
|
+
} else if (!Array.isArray(current[key]) && nextIsIndex) {
|
|
572
618
|
current[key] = Object.values(current[key]);
|
|
573
619
|
}
|
|
574
620
|
|
|
@@ -579,7 +625,64 @@ function setByPath(obj, path, value) {
|
|
|
579
625
|
return value;
|
|
580
626
|
}
|
|
581
627
|
|
|
628
|
+
const helpDebugHierarchy = 'In order to get a debug break when a specific hierarchy is created set the DEBUG_HIERARCHY environment variable to the JSON of the child-parent pair to break on. For example DEBUG_HIERARCHY=\'["cat", "mammel"]\''
|
|
629
|
+
|
|
630
|
+
// removeProp.js
|
|
631
|
+
function removeProp(obj, testFn, { maxDepth = Infinity, seen = new WeakSet() } = {}) {
|
|
632
|
+
if (!obj || typeof obj !== 'object' || maxDepth <= 0) return obj;
|
|
633
|
+
if (seen.has(obj)) return obj;
|
|
634
|
+
seen.add(obj);
|
|
635
|
+
|
|
636
|
+
if (Array.isArray(obj)) {
|
|
637
|
+
// ---- ARRAY: process each element (but don't remove elements unless testFn says so)
|
|
638
|
+
let writeIdx = 0;
|
|
639
|
+
for (let i = 0; i < obj.length; i++) {
|
|
640
|
+
const element = obj[i];
|
|
641
|
+
const shouldRemoveElement = testFn(element, i, obj);
|
|
642
|
+
|
|
643
|
+
if (shouldRemoveElement) {
|
|
644
|
+
// Remove the whole array element
|
|
645
|
+
if (element && typeof element === 'object') {
|
|
646
|
+
// Still walk inside it in case testFn wants side effects
|
|
647
|
+
removeProp(element, testFn, { maxDepth: maxDepth - 1, seen });
|
|
648
|
+
}
|
|
649
|
+
// Skip writing it back
|
|
650
|
+
} else {
|
|
651
|
+
// Keep element, but walk into it to remove inner props
|
|
652
|
+
removeProp(element, testFn, { maxDepth: maxDepth - 1, seen });
|
|
653
|
+
if (writeIdx !== i) {
|
|
654
|
+
obj[writeIdx] = element;
|
|
655
|
+
}
|
|
656
|
+
writeIdx++;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
obj.length = writeIdx;
|
|
660
|
+
return obj;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ---- OBJECT: iterate over own keys
|
|
664
|
+
const keys = Reflect.ownKeys(obj);
|
|
665
|
+
for (const key of keys) {
|
|
666
|
+
const val = obj[key];
|
|
667
|
+
const shouldRemove = testFn(val, key, obj);
|
|
668
|
+
|
|
669
|
+
if (shouldRemove) {
|
|
670
|
+
delete obj[key];
|
|
671
|
+
if (val && typeof val === 'object') {
|
|
672
|
+
removeProp(val, testFn, { maxDepth: maxDepth - 1, seen });
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
removeProp(val, testFn, { maxDepth: maxDepth - 1, seen });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return obj;
|
|
680
|
+
}
|
|
681
|
+
|
|
582
682
|
module.exports = {
|
|
683
|
+
removeProp,
|
|
684
|
+
helpDebugHierarchy,
|
|
685
|
+
|
|
583
686
|
stableId,
|
|
584
687
|
ecatch,
|
|
585
688
|
functionsToStrings,
|
|
@@ -611,4 +714,5 @@ module.exports = {
|
|
|
611
714
|
assignAssumed,
|
|
612
715
|
watchProperty,
|
|
613
716
|
pathEquals,
|
|
717
|
+
OverrideCheck,
|
|
614
718
|
}
|
package/src/project2.js
CHANGED
|
@@ -101,6 +101,10 @@ function project(source, filters, path=[]) {
|
|
|
101
101
|
} else if (prop.property && source.hasOwnProperty(prop.property)) {
|
|
102
102
|
// If the property is an object and not null, recursively project it
|
|
103
103
|
if (typeof source[prop.property] === 'object' && source[prop.property] !== null) {
|
|
104
|
+
if (prop.all) {
|
|
105
|
+
result[prop.property] = source[prop.property]
|
|
106
|
+
return
|
|
107
|
+
}
|
|
104
108
|
result[prop.property] = {}
|
|
105
109
|
const instantiatedCheck = []
|
|
106
110
|
for (const check of prop.check) {
|