theprogrammablemind_4wp 9.5.1 → 9.6.0

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.
@@ -97,11 +97,11 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
97
97
  if (args.uuid) {
98
98
  args.objects = args.getObjects(args.uuid)
99
99
  }
100
- if (!hierarchy) {
101
- hierarchy = config.hierarchy
100
+ if (!hierarchy && config.getHierarchy) {
101
+ hierarchy = config.getHierarchy()
102
102
  }
103
103
  // callId
104
- args.calls = new InitCalls(args.isInstance ? `${args.isInstance}#${config.name}` : config.name)
104
+ args.calls = new InitCalls(args.isInstance ? `${args.isInstance}#${config.getName()}` : config.getName())
105
105
  if (global.theprogrammablemind && global.theprogrammablemind.loadForTesting) {
106
106
  args.calls = new InitCalls(Object.keys(global.theprogrammablemind.loadForTesting)[0])
107
107
  }
@@ -112,14 +112,18 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
112
112
  throw new ErrorReason(context)
113
113
  }
114
114
  args.kms = config.getConfigs()
115
- args.config = config
115
+ args.config = config.getPseudoConfig(uuidForScoping, config)
116
116
  args.hierarchy = hierarchy
117
117
  args.isA = isA(hierarchy)
118
118
  // args.listable = listable(hierarchy)
119
119
  // args.asList = asList
120
120
  args.retry = () => { throw new RetryError() }
121
- args.fragments = (query) => {
122
- return config.fragment(args, query)
121
+ // mappings are optional
122
+ args.fragments = (query, mappings) => {
123
+ return config.fragment(args, query, mappings)
124
+ }
125
+ args.fragmentMapper = (values, fromModelQuery, toModelQuery) => {
126
+ return config.fragmentMapper(args, values, fromModelQuery, toModelQuery)
123
127
  }
124
128
  args.breakOnSemantics = false
125
129
  args.theDebugger = {
@@ -133,20 +137,35 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
133
137
  args.addPattern = (pattern, def) => config.addPattern(pattern, args.uuid)
134
138
  args.addGenerator = (generator) => config.addGenerator(generator, args.uuid, config.name)
135
139
 
140
+ if (config.getTestConfig()?.testModuleName) {
141
+ args.testModuleName = config.getTestConfig().testModuleName
142
+ }
136
143
  args.addAssumedScoped = (args, assumed) => {
137
144
  const addAssumed = (args, ...moreAssumed) => {
138
145
  return { ...args, assumed: Object.assign({}, assumed, (args.assumed || {}), ...moreAssumed) }
139
146
  }
140
147
 
141
- args.s = (c) => config.getSemantics(logs).apply(args, c)
142
- args.g = (c, a = {}) => {
143
- return config.getGenerators(logs).apply(addAssumed(args, a), c, a)
148
+ args.s = (c, options = {}) => config.getSemantics(logs).apply(args, c, options)
149
+ args.g = (c, rest = {}) => {
150
+ // if (JSON.stringify(rest) !== '{}' && !(rest.assumed || rest.options)) {
151
+ // debugger
152
+ // }
153
+ const { assumed = {}, options = {} } = rest
154
+ return config.getGenerators(logs).apply(addAssumed(args, assumed), c, assumed, options)
144
155
  }
145
- args.gp = (c, a = {}) => {
146
- return config.getGenerators(logs).apply(addAssumed(args, a, { paraphrase: true, isResponse: false, response: false }), c, { paraphrase: true, isResponse: false, response: false })
156
+ args.gp = (c, rest = {}) => {
157
+ // if (JSON.stringify(rest) !== '{}' && !(rest.assumed || rest.options)) {
158
+ // debugger
159
+ // }
160
+ const { assumed = {}, options = {} } = rest
161
+ return config.getGenerators(logs).apply(addAssumed(args, assumed, { paraphrase: true, isResponse: false, response: false }), c, { paraphrase: true, isResponse: false, response: false }, options)
147
162
  }
148
- args.gr = (c, a = {}) => {
149
- return config.getGenerators(logs).apply(addAssumed(args, a, { paraphrase: false, isResponse: true }), { ...c, paraphrase: false, isResponse: true })
163
+ args.gr = (c, rest = {}) => {
164
+ // if (JSON.stringify(rest) !== '{}' && !(rest.assumed || rest.options)) {
165
+ // debugger
166
+ // }
167
+ const { assumed = {}, options = {} } = rest
168
+ return config.getGenerators(logs).apply(addAssumed(args, assumed, { paraphrase: false, isResponse: true }), { ...c, paraphrase: false, isResponse: true }, options)
150
169
  }
151
170
  args.e = (c) => {
152
171
  if (!c) {
@@ -179,14 +198,8 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
179
198
  }
180
199
  config.getAddedArgs(args)
181
200
 
182
- Object.assign(args, args.getUUIDScoped(uuidForScoping || config.uuid))
201
+ Object.assign(args, args.getUUIDScoped(uuidForScoping || config.getUUID()))
183
202
  args.apis = args.apis || ((name) => config.getConfig(name).api)
184
- /*
185
- if (uuidForScoping) {
186
- Object.assign(args, args.getUUIDScoped(uuidForScoping))
187
- }
188
- */
189
- // sets args for all the API. that make a copy so the args must be fully setup by here except for scoped
190
203
  config.setArgs(args)
191
204
  }
192
205
 
@@ -199,6 +212,22 @@ const getObjects = (objects) => {
199
212
  }
200
213
  }
201
214
 
215
+ const run = async (config, handler) => {
216
+ // map to hash
217
+ config = config || {}
218
+ if (config.config) {
219
+ config = config
220
+ }
221
+
222
+ const hierarchy = new DigraphInternal((config.config || {}).hierarchy || [])
223
+
224
+ const objects = config.config.objects.namespaced[config.uuid]
225
+ const logs = []
226
+ const args = {}
227
+ setupArgs(args, config, logs, hierarchy)
228
+ return handler(args)
229
+ }
230
+
202
231
  const processContext = async (context, { objects = {}, config, logs = [] }) => {
203
232
  const generators = config.getGenerators(logs)
204
233
  const semantics = config.getSemantics(logs)
@@ -291,7 +320,7 @@ const setupContexts = (rawContexts) => {
291
320
  return contexts
292
321
  }
293
322
 
294
- const processContextsB = async ({ config, hierarchy, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate }) => {
323
+ const processContextsB = async ({ config, hierarchy, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate, contextIdCounter }) => {
295
324
  // TODO fix this name to contextsPrime
296
325
  const contextsPrime = []
297
326
  const generatedPrime = []
@@ -311,14 +340,13 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
311
340
  args.insert = (context) => toDo.unshift(context)
312
341
  let overlap, lastRange
313
342
  config.debugLoops = commandLineArgs && commandLineArgs.debugLoops
314
- let context_id_counter = 0
315
343
  while (toDo.length > 0) {
316
344
  const context = toDo.shift()
317
345
  args.calls.next()
318
346
  let contextPrime = context
319
347
  context.topLevel = true
320
- context_id_counter += 1
321
- context.context_id = context_id_counter
348
+ contextIdCounter += 1
349
+ context.context_id = contextIdCounter
322
350
  try {
323
351
  if (json.has_errors) {
324
352
  throw new Error('There are errors in the logs. Run with the -d flag and grep for Error')
@@ -328,6 +356,9 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
328
356
  const semantics = config.getSemantics(json.logs)
329
357
  try {
330
358
  contextPrime = await semantics.apply(args, context)
359
+ // contextPrime.greg = 'yes'
360
+ // console.log("context_id", context.context_id)
361
+ // console.log("semantics.apply", JSON.stringify(contextPrime, null, 2))
331
362
  } catch (e) {
332
363
  if (e.message == 'Maximum call stack size exceeded') {
333
364
  const mostCalled = semantics.getMostCalled()
@@ -368,21 +399,21 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
368
399
  const generated = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
369
400
  let generatedParenthesized = []
370
401
  if (generateParenthesized) {
371
- config.parenthesized = true
402
+ config.setParenthesized(true)
372
403
  generatedParenthesized = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
373
- config.parenthesized = false
404
+ config.setParenthesized(false)
374
405
  }
375
406
  // assumed = { paraphrase: true, response: false };
376
407
  assumed = { paraphrase: true, isResponse: false, response: false }
377
408
  if (generateParenthesized) {
378
- config.parenthesized = false
409
+ config.setParenthesized(false)
379
410
  }
380
411
  const paraphrases = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
381
412
  let paraphrasesParenthesized = []
382
413
  if (generateParenthesized) {
383
- config.parenthesized = true
414
+ config.setParenthesized(true)
384
415
  paraphrasesParenthesized = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
385
- config.parenthesized = false
416
+ config.setParenthesized(false)
386
417
  }
387
418
  contextsPrime.push(contextPrime)
388
419
  generatedPrime.push(generated)
@@ -421,7 +452,7 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
421
452
  throw e
422
453
  }
423
454
  }
424
- return { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime }
455
+ return { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime, updatedContextIdCounter: contextIdCounter }
425
456
  }
426
457
 
427
458
  // instance template loadTemplate
@@ -429,11 +460,6 @@ const loadInstance = async (config, instance) => {
429
460
  const transitoryMode = global.transitoryMode
430
461
  global.transitoryMode = false
431
462
 
432
- /*
433
- if (config.name == 'people' && instance.name == 'people') {
434
- debugger
435
- }
436
- */
437
463
  const rl = instance.resultss.length
438
464
  if (rl > 0) {
439
465
  config.addAssociations(instance.resultss[instance.resultss.length - 1].rtf_associations || [])
@@ -479,6 +505,9 @@ const loadInstance = async (config, instance) => {
479
505
  args.isModule = true
480
506
  args.isProcess = false
481
507
  }
508
+ if (instance.name == config.testConfig.testModuleName) {
509
+ args.isTesting = true
510
+ }
482
511
  await results.apply(args)
483
512
  } else if (results.isFragment) {
484
513
  } else {
@@ -490,6 +519,9 @@ const loadInstance = async (config, instance) => {
490
519
  args.instance = ''
491
520
  args.isProcess = !config.isModule
492
521
  args.isModule = !!config.isModule
522
+ if (instance.name == config.testConfig.testModuleName) {
523
+ args.isTesting = true
524
+ }
493
525
  await processContextsB(args)
494
526
  if (results.skipSemantics) {
495
527
  config.config.skipSemantics = null
@@ -505,6 +537,7 @@ module.exports = {
505
537
  // listable,
506
538
  setupArgs,
507
539
  processContext,
540
+ run,
508
541
  getObjects,
509
542
  gs,
510
543
  processContextsB,
@@ -0,0 +1,83 @@
1
+ const _ = require('lodash')
2
+ const helpers = require('./helpers')
3
+
4
+ function fragmentInstantiator (args, contexts) {
5
+ return new Object({
6
+ contexts: () => {
7
+ return _.cloneDeep(contexts)
8
+ },
9
+ instantiate: async (mappings) => {
10
+ const instantiated = _.cloneDeep(contexts)
11
+ const todo = [{ context: instantiated, path: [] }]
12
+ args = { ...args }
13
+ while (todo.length > 0) {
14
+ const { context, path } = todo.pop()
15
+ args.context = context
16
+ args.path = path
17
+ for (const mapping of mappings) {
18
+ if (await mapping.match(args)) {
19
+ await mapping.apply(args)
20
+ }
21
+ }
22
+ for (const key of Object.keys(context)) {
23
+ // if (['number', 'string', 'boolean'].includes(typeof (context[key]))) {
24
+ if (!helpers.isCompound(context[key])) {
25
+ continue
26
+ }
27
+ if (context[key].instantiated) {
28
+ continue
29
+ }
30
+ todo.push({ context: context[key], path: [...path, key] })
31
+ }
32
+ }
33
+ if (contexts.length == 1 && instantiated.length == 1) {
34
+ return instantiated[0]
35
+ } else {
36
+ return instantiated
37
+ }
38
+ }
39
+ })
40
+ }
41
+
42
+ async function fragmentMapperInstantiator(values, modelFrom, modelTo) {
43
+ const paths = {}
44
+ for (const value of values) {
45
+ paths[value] = { value }
46
+ }
47
+
48
+ {
49
+ const fi = fragmentInstantiator({paths}, modelFrom)
50
+ await fi.instantiate([
51
+ {
52
+ match: ({context, path}) => values.includes(context.value),
53
+ apply: ({context, path}) => paths[context.value].from = path
54
+ },
55
+ ])
56
+ }
57
+
58
+ {
59
+ const fi = fragmentInstantiator({paths}, modelTo)
60
+ await fi.instantiate([
61
+ {
62
+ match: ({context, path}) => values.includes(context.value),
63
+ apply: ({context, path}) => paths[context.value].to = path
64
+ },
65
+ ])
66
+ }
67
+ return {
68
+ instantiate: (actualFrom) => {
69
+ const actualTo = structuredClone(modelTo)
70
+ for (const value in paths) {
71
+ const { from, to } = paths[value]
72
+ const actualValue = helpers.getByPath(actualFrom, from, null)
73
+ helpers.setByPath(actualTo, to, actualValue)
74
+ }
75
+ return actualTo
76
+ }
77
+ }
78
+ }
79
+
80
+ module.exports = {
81
+ fragmentInstantiator,
82
+ fragmentMapperInstantiator,
83
+ }
package/src/generators.js CHANGED
@@ -26,11 +26,13 @@ class Generator {
26
26
  }
27
27
  }
28
28
 
29
+ /*
29
30
  getAPIs (config) {
30
31
  if (config && config._api && config._api.multiApi) {
31
32
  return config._api.apis
32
33
  }
33
34
  }
35
+ */
34
36
 
35
37
  toLabel () {
36
38
  const where = this.where ? `where: "${this.where}"` : ''
@@ -59,7 +61,7 @@ class Generator {
59
61
  context,
60
62
  callId,
61
63
  api: this.getAPI(config),
62
- apis: this.getAPIs(config)
64
+ apis: config.getAPIs(),
63
65
  }
64
66
  const args = Object.assign({}, baseArgs, moreArgs, (baseArgs.getUUIDScoped || (() => { return {} }))(this.uuid))
65
67
  // return this.match(args)
@@ -78,11 +80,11 @@ class Generator {
78
80
  if (!log) {
79
81
  throw new Error('generators.apply argument log is required')
80
82
  }
81
- if (baseArgs.call && config && baseArgs.calls.stack.length > config.maxDepth) {
82
- throw new Error(`Max depth of ${config.maxDepth} for calls has been exceeded. maxDepth can be set on the config object. To see the calls run with the --dl or set the debugLoops property on the config`)
83
+ if (baseArgs.call && config && baseArgs.calls.stack.length > config.getMaxDepth()) {
84
+ throw new Error(`Max depth of ${config.getMaxDepth()} for calls has been exceeded. maxDepth can be set on the config object. To see the calls run with the --dl or set the debugLoops property on the config`)
83
85
  }
84
86
 
85
- if (config && config.debugLoops) {
87
+ if (config && config.getDebugLoops()) {
86
88
  console.log('apply', this.toLabel())
87
89
  }
88
90
  // this.getAPI(config)
@@ -113,7 +115,7 @@ class Generator {
113
115
  config,
114
116
  response,
115
117
  api: this.getAPI(config),
116
- apis: this.getAPIs(config)
118
+ apis: config.getAPIs()
117
119
  }
118
120
  const args = Object.assign({}, baseArgs, moreArgs, (baseArgs.getUUIDScoped || (() => { return {} }))(this.uuid))
119
121
  if (this.property === 'generatorp') {
@@ -182,10 +184,10 @@ class Generators {
182
184
  const generator = this.generators[igenerator]
183
185
  if (await generator.matches(args, objects, context, hierarchy, config, options)) {
184
186
  const log = (message) => { this.logs.push(message) }
185
- // this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${JSON.stringify(context)}`)
187
+ // this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${stringify(context)}`)
186
188
  let errorMessage = 'The apply function did not return a value'
187
189
  try {
188
- generated = await generator.apply(args, objects, context, hierarchy, config, response, log)
190
+ generated = await generator.apply(args, objects, context, hierarchy, config, response, log, options)
189
191
  } catch (e) {
190
192
  // the next if handle this
191
193
  generated = null
@@ -202,7 +204,7 @@ class Generators {
202
204
  }
203
205
  }
204
206
  if (!generated && generated !== '') {
205
- const widths = [10, 10, 90]
207
+ const widths = Lines.addRemainder([10, 10])
206
208
  const lines = new Lines(widths)
207
209
  lines.setElement(0, 0, 'Generator')
208
210
  const source = `${generator.km}/#${generator.index}`
@@ -212,7 +214,7 @@ class Generators {
212
214
  lines.newRow()
213
215
  lines.setElement(0, 1, 'TO')
214
216
  lines.setElement(0, 2, `context_id: ${context.context_id}`)
215
- lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 25 }), null, 2))
217
+ lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 10 }), null, 2))
216
218
  lines.newRow()
217
219
  lines.setElement(0, 1, 'STACK')
218
220
  lines.setElement(0, 2, stack)
@@ -229,8 +231,8 @@ class Generators {
229
231
  args.calls.pop()
230
232
  throw { error: [message], logs: this.logs }
231
233
  }
232
- if (((config || {}).config || {}).debug) {
233
- const widths = [10, 10, 90]
234
+ if (config.getDebug()) {
235
+ const widths = Lines.addRemainder([10, 10])
234
236
  const lines = new Lines(widths)
235
237
  lines.setElement(0, 0, 'Generator')
236
238
  if (generator.index > -1 && generator.km) {
@@ -262,8 +264,8 @@ class Generators {
262
264
  }
263
265
  }
264
266
  args.calls.pop()
265
- if (!applied && ((config || {}).config || {}).debug) {
266
- const widths = [10, 10, 90]
267
+ if (!applied && config.getDebug()) {
268
+ const widths = Lines.addRemainder([10, 10])
267
269
  const lines = new Lines(widths)
268
270
  lines.setElement(0, 0, 'Generator')
269
271
  lines.setElement(0, 2, 'No generator applied')
@@ -275,7 +277,11 @@ class Generators {
275
277
  lines.setElement(0, 2, JSON.stringify(context, null, 2))
276
278
  this.logs.push(lines.toString())
277
279
  }
278
- return ((config || {}).parenthesized ? '(' + generated + ')' : generated)
280
+ let parenthesized = false
281
+ if (config && config.getParenthesized()) {
282
+ parenthesized = true
283
+ }
284
+ return parenthesized ? '(' + generated + ')' : generated
279
285
  }
280
286
  }
281
287
 
package/src/helpers.js CHANGED
@@ -292,8 +292,53 @@ const hashCode = (str) => {
292
292
  return hash
293
293
  }
294
294
 
295
- const sortJson = (json) => {
296
- return json
295
+ /**
296
+ * Recursively sorts object keys alphabetically.
297
+ * Fully handles arrays (including objects inside arrays) and preserves Date/other non-plain objects.
298
+ *
299
+ * @param {any} input - The value to sort (object, array, primitive, etc.)
300
+ * @param {Object} [options]
301
+ * - ignoreCase: boolean (default false) – case-insensitive sort
302
+ * - reverse: boolean (default false) – reverse alphabetical order
303
+ * @returns {any} New sorted value
304
+ */
305
+ function sortJson(input, options = {}) {
306
+ const { ignoreCase = false, reverse = false } = options;
307
+
308
+ // Helper: is this a plain object {} (not Array, Date, Map, null, etc.)
309
+ function isPlainObject(value) {
310
+ return value !== null && typeof value === 'object' && value.constructor === Object;
311
+ }
312
+
313
+ // Handle arrays: map over elements and recurse
314
+ if (Array.isArray(input)) {
315
+ return input.map(item => sortJson(item, options));
316
+ }
317
+
318
+ // If not a plain object, return unchanged (preserves Date, Map, Set, primitives, etc.)
319
+ if (!isPlainObject(input)) {
320
+ return input;
321
+ }
322
+
323
+ // Sorter for keys
324
+ const sorter = (a, b) => {
325
+ const A = ignoreCase ? a.toLowerCase() : a;
326
+ const B = ignoreCase ? b.toLowerCase() : b;
327
+ return reverse ? B.localeCompare(A) : A.localeCompare(B);
328
+ };
329
+
330
+ // Build new sorted object
331
+ const sorted = {};
332
+
333
+ Object.keys(input)
334
+ .sort(sorter)
335
+ .forEach(key => {
336
+ const value = input[key];
337
+ // Always recurse: handles nested objects and arrays properly
338
+ sorted[key] = sortJson(value, options);
339
+ });
340
+
341
+ return sorted;
297
342
  }
298
343
 
299
344
  const validProps = (valids, object, type) => {
@@ -436,6 +481,63 @@ const stableId = (tag) => {
436
481
  return id
437
482
  }
438
483
 
484
+ function getByPath(obj, path, defaultValue) {
485
+ let current = obj;
486
+ for (const key of path) {
487
+ if (current === null || current === undefined) return defaultValue;
488
+ if (typeof current !== 'object') return defaultValue;
489
+ current = current[key];
490
+ }
491
+ return current === undefined ? defaultValue : current;
492
+ }
493
+
494
+ /**
495
+ * Set a value in an object by path array.
496
+ * Automatically creates missing objects {} or arrays [] as needed.
497
+ *
498
+ * @param {Object} obj - The root object to modify
499
+ * @param {Array<string|number>} path - Array of keys/indices
500
+ * @param {*} value - Value to set
501
+ * @returns {*} The set value (for chaining)
502
+ */
503
+ function setByPath(obj, path, value) {
504
+ if (!Array.isArray(path) || path.length === 0) {
505
+ throw new Error('Path must be a non-empty array');
506
+ }
507
+
508
+ let current = obj;
509
+
510
+ for (let i = 0; i < path.length; i++) {
511
+ const key = path[i];
512
+ const isLast = i === path.length - 1;
513
+
514
+ if (isLast) {
515
+ // Final step — just assign
516
+ current[key] = value;
517
+ } else {
518
+ // Not last — ensure next level exists
519
+ const nextKey = path[i + 1];
520
+
521
+ if (current[key] == null) {
522
+ // Auto-create: array if next key is number, otherwise object
523
+ current[key] = typeof nextKey === 'number' || String(nextKey >>> 0) === nextKey
524
+ ? []
525
+ : {};
526
+ } else if (Array.isArray(current[key]) && typeof nextKey !== 'number') {
527
+ // Safety: if current is array but next key isn't a valid index → convert to object
528
+ current[key] = { ...current[key] };
529
+ } else if (!Array.isArray(current[key]) && typeof nextKey === 'number') {
530
+ // If next expects array but current is object → convert
531
+ current[key] = Object.values(current[key]);
532
+ }
533
+
534
+ current = current[key];
535
+ }
536
+ }
537
+
538
+ return value;
539
+ }
540
+
439
541
  module.exports = {
440
542
  stableId,
441
543
  ecatch,
@@ -462,5 +564,7 @@ module.exports = {
462
564
  where,
463
565
  w,
464
566
  suggestAssociationsFix,
465
- suggestAssociationsFixFromSummaries
567
+ suggestAssociationsFixFromSummaries,
568
+ getByPath,
569
+ setByPath,
466
570
  }
package/src/project2.js CHANGED
@@ -1,7 +1,32 @@
1
- function project(source, filters) {
1
+ function areFirstNEqual(arr1, arr2, n) {
2
+ if (n <= 0) return true;
3
+ if (arr1.length < n || arr2.length < n) return false;
4
+
5
+ for (let i = 0; i < n; i++) {
6
+ if (arr1[i] !== arr2[i]) {
7
+ return false;
8
+ }
9
+ }
10
+ return true;
11
+ }
12
+
13
+ function project(source, filters, path=[]) {
2
14
  if (['string', 'number'].includes(typeof source)) {
3
15
  return source
4
16
  }
17
+ if (Array.isArray(source)) {
18
+ const result = []
19
+ for (const value of source) {
20
+ result.push(project(value, filters, [...path, '*']))
21
+ }
22
+ return result
23
+ }
24
+ function isPlainObject(obj) {
25
+ return Object.prototype.toString.call(obj) === '[object Object]';
26
+ }
27
+ if (!isPlainObject(source) && !Array.isArray(source)) {
28
+ return source
29
+ }
5
30
 
6
31
  if (Object.keys(source).length === 0 && filters.length === 0) {
7
32
  return {};
@@ -11,7 +36,7 @@ function project(source, filters) {
11
36
  const filter = filters.find(f => f.match({ context: source }));
12
37
  if (!filter) {
13
38
  if (Array.isArray(source)) {
14
- return source.map((element) => project(element, filters))
39
+ return source.map((element) => project(element, filters, [...path, '*']))
15
40
  }
16
41
  return {};
17
42
  }
@@ -22,6 +47,7 @@ function project(source, filters) {
22
47
  // update
23
48
  const updatedProperties = []
24
49
  for (const property of properties) {
50
+ // property that contains a list of properties to be checked
25
51
  if (property.properties) {
26
52
  for (const moreProperty of source[property.properties] || []) {
27
53
  updatedProperties.push(moreProperty)
@@ -35,10 +61,34 @@ function project(source, filters) {
35
61
  // Build the result object
36
62
  const result = {};
37
63
  properties.forEach(prop => {
38
- if (source.hasOwnProperty(prop)) {
64
+ if (prop.path && (prop.path.length === path.length + 1) && areFirstNEqual(path, prop.path, path.length) && source.hasOwnProperty(prop.path[path.length])) {
65
+ const endProp = prop.path[path.length]
66
+ if (Array.isArray(source[endProp])) {
67
+ result[endProp] = []
68
+ for (const key in source[endProp]) {
69
+ result[endProp].push(project(source[endProp][key], filters, [...path, endProp, key]))
70
+ }
71
+ } else {
72
+ result[endProp] = {}
73
+ for (const key in source[endProp]) {
74
+ result[endProp][key] = project(source[endProp][key], filters, [...path, endProp, key])
75
+ }
76
+ }
77
+ } else if (source.hasOwnProperty(prop)) {
39
78
  // If the property is an object and not null, recursively project it
40
79
  if (typeof source[prop] === 'object' && source[prop] !== null) {
41
- result[prop] = project(source[prop], filters);
80
+ result[prop] = project(source[prop], filters, [...path, prop]);
81
+ } else {
82
+ // Copy primitive or null properties directly
83
+ result[prop] = source[prop];
84
+ }
85
+ } else if (prop.property && source.hasOwnProperty(prop.property)) {
86
+ // If the property is an object and not null, recursively project it
87
+ if (typeof source[prop.property] === 'object' && source[prop.property] !== null) {
88
+ result[prop.property] = {}
89
+ for (const key of prop.check) {
90
+ result[prop.property][key] = project(source[prop.property][key], filters, [...path, prop.property, key]);
91
+ }
42
92
  } else {
43
93
  // Copy primitive or null properties directly
44
94
  result[prop] = source[prop];