theprogrammablemind 9.5.1-beta.3 → 9.5.1-beta.31

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 CHANGED
@@ -12,9 +12,8 @@ const _ = require('lodash')
12
12
  const stringify = require('json-stable-stringify')
13
13
  const Lines = require('./lines')
14
14
  const flattens = require('./src/flatten')
15
- const { appendNoDups, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps } = require('./src/helpers')
15
+ const { sortJson, appendNoDups, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps } = require('./src/helpers')
16
16
  const runtime = require('./runtime')
17
- const sortJson = runtime.sortJson
18
17
  const debug = require('./src/debug')
19
18
 
20
19
  const getConfig_getObjectsCheck = (config, testConfig) => {
@@ -46,33 +45,33 @@ const getSuggestionMessage = (suggestion) => {
46
45
  return `Try adding this to the associations: { context: ${JSON.stringify(getSuggestion(suggestion))}, choose: <indexOfMainElement> },\n If that does not work look at the logs and check when the operators become wrong during an interation. Deduce the change based on the previous iteration and what operator was applied.`
47
46
  }
48
47
 
49
- const getConfig_getContextCheck = (testConfig) => {
50
- return (testConfig.checks && testConfig.checks.context) || []
51
- }
52
-
53
48
  const pickContext = (contextChecks) => (context) => {
54
49
  return project2(context, contextChecks)
55
50
  }
56
51
 
57
52
  const pickObjects = (config, testConfig, getObjects) => {
58
- /*
59
- let testConfigName = config.name
60
- if (testConfig.testModuleName) {
61
- objects = getObjects(config.config.objects)(config.getConfigs()[testConfig.testModuleName].uuid)
62
- testConfigName = testConfig.testModuleName
63
- }
64
- */
65
53
  const checks = getConfig_getObjectsCheck(config, testConfig)
54
+ const contextChecks = config.getContextChecks()
66
55
  const projection = {}
67
56
  for (const km in checks) {
68
57
  const objects = getObjects(km)
69
58
  if (!objects) {
70
59
  throw new Error(`In the checks for ${config.name} the KM ${km} does not exist`)
71
60
  }
72
- if (checks[km] && checks[km].find((check) => check.match && check.apply)) {
73
- projection[km] = project2(objects, checks[km])
61
+
62
+ if (false) {
63
+ if (checks[km] && checks[km].find((check) => check.match && check.apply)) {
64
+ projection[km] = project2(objects, checks[km])
65
+ } else {
66
+ projection[km] = project(objects, checks[km])
67
+ }
74
68
  } else {
75
- projection[km] = project(objects, checks[km])
69
+ const lastIndex = contextChecks.length - 1
70
+ const last = contextChecks[lastIndex]
71
+ contextChecks[lastIndex] = { match: last.match, apply: () => [...new Set([...(checks[km] || []), ...last.apply()])] }
72
+
73
+ // projection[km] = project2(objects, [...checks[km], ...contextChecks])
74
+ projection[km] = project2(objects, contextChecks)
76
75
  }
77
76
  }
78
77
  return projection
@@ -316,6 +315,7 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
316
315
  }
317
316
 
318
317
  let startCounter = 0
318
+ let contextIdCounter = 0
319
319
  while (true) {
320
320
  if (queries.length === 0) {
321
321
  break
@@ -356,8 +356,9 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
356
356
  }
357
357
  const summary = { summaries: json.summaries, length: json.contexts.length }
358
358
  summaries.push(summary)
359
- const { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime } =
360
- await processContextsB({ isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
359
+ const { updatedContextIdCounter, contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime } =
360
+ await processContextsB({ contextIdCounter, isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
361
+ contextIdCounter = updatedContextIdCounter
361
362
  if (isTest) {
362
363
  const end = runtime.performance.performance.now()
363
364
  clientSideTime = end - start
@@ -475,6 +476,7 @@ const runTest = async (config, expected, { args, verbose, testConfig, debug, tim
475
476
  return
476
477
  }
477
478
  // initialize in between test so state is not preserved since the test was adding without state
479
+ config.testConfig.testModuleName = testConfig.testModuleName
478
480
  await config.rebuild()
479
481
  const errorHandler = (error) => {
480
482
  if (error.metadata) {
@@ -838,7 +840,7 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
838
840
  }
839
841
  console.log(responses.trace)
840
842
 
841
- if (true) {
843
+ if (false) {
842
844
  if (global.beforeObjects) {
843
845
  console.log('objects', runtime.jsonDiff.diffString(global.beforeObjects, config.get('objects')))
844
846
  } else {
@@ -913,7 +915,7 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
913
915
  console.log('')
914
916
  const screen_width = process.stdout.columns
915
917
  // || 0 for when running without a console
916
- const widths = [70, 10, Math.max(80, screen_width - 71 || 0)]
918
+ const widths = Lines.addRemainder([70, 10])
917
919
  const lines = new Lines(widths)
918
920
  lines.setElement(0, 0, '--- The paraphrases are ----------')
919
921
  lines.setElement(0, 2, '--- The response strings are ----------')
@@ -970,7 +972,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
970
972
  if (instance.fragments) {
971
973
  pr = instance.fragments[index]
972
974
  }
973
- return Object.assign({}, toProperties(query), { property: 'fragments', previousResults: pr, skipSemantics: false })
975
+ return Object.assign({}, toProperties(query), { property: 'fragments', previousResults: pr, skipSemantics: true })
974
976
  }
975
977
 
976
978
  const looper = async (configs) => {
@@ -1324,13 +1326,32 @@ const knowledgeModuleImpl = async ({
1324
1326
  parser.add_argument('-cl', '--checkForLoop', { nargs: '?', help: 'Check for loops in the priorities, Optional argument is list of operator keys to consider. For example [["banana", 0], ["food", 1]]' })
1325
1327
  parser.add_argument('-r', '--reset', { action: 'store_true', help: 'Get the server to bypass the cache and rebuild everything' })
1326
1328
  parser.add_argument('-q', '--query', { help: 'Run the specified query' })
1329
+ parser.add_argument('-f', '--filter', { help: 'for -pd only the data for the knowledge modules that start with this string will be shown' })
1327
1330
  parser.add_argument('-ip ', '--server', { help: 'Server to run against' })
1328
1331
  parser.add_argument('-qp ', '--queryParams', { help: 'Query params for the server call' })
1329
1332
  parser.add_argument('-dt', '--deleteTest', { help: 'Delete the specified query from the tests file.' })
1330
1333
  parser.add_argument('--parenthesized', { action: 'store_true', help: 'Show the generated phrases with parenthesis.' })
1331
1334
  parser.add_argument('-c', '--clean', { help: 'Remove data from the test files. a === association' })
1332
1335
  parser.add_argument('-od', '--objectDiff', { action: 'store_true', help: 'When showing the objects use a colour diff' })
1333
- parser.add_argument('-p', '--print', { help: 'Print the specified elements c === config, w === words, b === bridges, o === operators d === objects (d for data), h === hierarchy, ha === hierarchy ancestors g === generators, s === semantics, l === load t=tests ordering p === priorities a === associations j === JSON sent to server. for example --print wb' })
1336
+ parser.add_argument('-p', '--print', { help:
1337
+ `Print the specified elements
1338
+ a === associations
1339
+ b === bridges,
1340
+ c === config,
1341
+ cc === test checks,
1342
+ d === objects (d for data),
1343
+ g === generators,
1344
+ h === hierarchy,
1345
+ ha === hierarchy ancestors,
1346
+ j === JSON sent to server,
1347
+ l === load ordering,
1348
+ o === operators,
1349
+ p === priorities,
1350
+ s === semantics,
1351
+ t === tests ordering,
1352
+ w === words,
1353
+ for example --print wb' })
1354
+ ` })
1334
1355
  parser.add_argument('-s', '--save', { action: 'store_true', help: 'When running with the --query flag this will save the current run to the test file. When running without the --query flag all tests will be run and resaved.' })
1335
1356
  parser.add_argument('-fr', '--failRebuild', { action: 'store_true', help: 'If a rebuild is required fail out.' })
1336
1357
  parser.add_argument('-sd', '--saveDeveloper', { action: 'store_true', help: 'Same as -s but the query will not show up in the info command.' })
@@ -1491,6 +1512,12 @@ const knowledgeModuleImpl = async ({
1491
1512
  counter += 1
1492
1513
  }
1493
1514
  }
1515
+ if (hasArg('cc')) {
1516
+ for (const cc of config.getContextChecks()) {
1517
+ const printable = { ...cc, match: cc.match.toString(), apply: cc.apply.toString() }
1518
+ console.log(JSON.stringify(printable, null, 2))
1519
+ }
1520
+ }
1494
1521
  if (hasArg('c')) {
1495
1522
  const { data } = setupProcessB({ config })
1496
1523
  console.log('Config as sent to server')
@@ -1554,8 +1581,19 @@ const knowledgeModuleImpl = async ({
1554
1581
  }
1555
1582
 
1556
1583
  if (hasArg('d')) {
1557
- console.log('objects (data) ================')
1558
- console.log(JSON.stringify(config.config.objects, null, 2))
1584
+ if (args.filter) {
1585
+ console.log(`objects (data) filtered by ${args.filter} ================`)
1586
+ const projection = { namespaced: {} }
1587
+ for (const key of Object.keys(config.config.objects.namespaced)) {
1588
+ if (key.startsWith(args.filter)) {
1589
+ projection.namespaced[key] = config.config.objects.namespaced[key]
1590
+ }
1591
+ }
1592
+ console.log(JSON.stringify(projection, null, 2))
1593
+ } else {
1594
+ console.log('objects (data) ================')
1595
+ console.log(JSON.stringify(config.config.objects, null, 2))
1596
+ }
1559
1597
  }
1560
1598
 
1561
1599
  if (hasArg('p')) {
@@ -2008,9 +2046,15 @@ const ensureTestFile = (module, name, type) => {
2008
2046
  }
2009
2047
 
2010
2048
  const knowledgeModule = async (...args) => {
2011
- await knowledgeModuleImpl(...args).catch((e) => {
2049
+ await knowledgeModuleImpl(...args).catch(async (e) => {
2012
2050
  console.error(e)
2013
- process.exit(-1)
2051
+ function sleep(ms) {
2052
+ return new Promise((resolve) => {
2053
+ setTimeout(resolve, ms);
2054
+ });
2055
+ }
2056
+ await sleep(1) // get the stderr to flush
2057
+ await process.exit(-1); // tiny trick: empty write forces flush of console.error buffer
2014
2058
  })
2015
2059
  }
2016
2060
 
@@ -2035,5 +2079,6 @@ module.exports = {
2035
2079
  gs,
2036
2080
  flattens,
2037
2081
  writeTest,
2038
- getConfigForTest
2082
+ getConfigForTest,
2083
+ debug,
2039
2084
  }
package/lines.js CHANGED
@@ -5,6 +5,13 @@ class Lines {
5
5
  this.rows = []
6
6
  }
7
7
 
8
+ static SCREEN_WIDTH = 164
9
+
10
+ static addRemainder(widths) {
11
+ const sum = widths.reduce((a, b) => a + b)
12
+ return [...widths, Lines.SCREEN_WIDTH - sum]
13
+ }
14
+
8
15
  addLine () {
9
16
  this.lines.push(this.widths.map((width) => ''.padEnd(width)))
10
17
  }
package/package.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "@eslint/js": "^9.21.0",
4
4
  "@typescript-eslint/eslint-plugin": "^4.28.4",
5
5
  "@typescript-eslint/parser": "^4.28.4",
6
+ "argparse": "^2.0.1",
6
7
  "eslint": "^7.32.0",
7
8
  "eslint-config-standard": "^16.0.3",
8
9
  "eslint-plugin-import": "^2.23.4",
9
10
  "eslint-plugin-node": "^11.1.0",
10
11
  "eslint-plugin-promise": "^5.1.0",
11
12
  "globals": "^16.0.0",
12
- "jest": "^29.7.0",
13
- "argparse": "^2.0.1"
13
+ "jest": "^30.2.0"
14
14
  },
15
15
  "scripts": {
16
16
  "to:debug": "node inspect node_modules/.bin/jest --runInBand -t NEO23",
@@ -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",
@@ -61,6 +62,7 @@
61
62
  "dependencies": {
62
63
  "base-64": "^1.0.0",
63
64
  "deep-equal": "^2.0.4",
65
+ "flatted": "^3.3.3",
64
66
  "fs": "0.0.1-security",
65
67
  "json-diff": "^1.0.3",
66
68
  "json-stable-stringify": "^1.0.1",
@@ -68,9 +70,8 @@
68
70
  "node-fetch": "^2.6.1",
69
71
  "readline": "^1.3.0",
70
72
  "scriptjs": "^2.5.9",
71
- "sort-json": "^2.0.0",
72
73
  "uuid": "^8.3.2"
73
74
  },
74
- "version": "9.5.1-beta.3",
75
+ "version": "9.5.1-beta.31",
75
76
  "license": "UNLICENSED"
76
77
  }
package/runtime.js CHANGED
@@ -13,7 +13,6 @@ module.exports = {
13
13
  ArgumentParser,
14
14
  readline: require('readline'),
15
15
  jsonDiff: require('json-diff'),
16
- sortJson: require('sort-json'),
17
16
  util: require('util'),
18
17
  performance: require('perf_hooks')
19
18
  }
package/src/config.js CHANGED
@@ -10,6 +10,7 @@ const { ecatch } = require('./helpers')
10
10
  const runtime = require('../runtime')
11
11
  const _ = require('lodash')
12
12
  const db = require('./debug')
13
+ const { fragmentInstantiator, fragmentMapperInstantiator } = require('./fragments')
13
14
 
14
15
  const debugBreak = () => {
15
16
  // debugger
@@ -20,8 +21,6 @@ const bags = [
20
21
  'semantics'
21
22
  ]
22
23
 
23
- global.GORDO = true
24
-
25
24
  const indent = (string, indent) => {
26
25
  return string.replace(/^/gm, ' '.repeat(indent))
27
26
  }
@@ -347,6 +346,8 @@ const handleBridgeProps = (config, bridge, { addFirst, uuid } = {}) => {
347
346
  config.testConfig.checks.context = []
348
347
  }
349
348
  config.testConfig.checks.context.push({
349
+ 'bridge.id': bridge.id,
350
+ 'bridge.check': bridge.check,
350
351
  match: ({context}) => context.marker == bridge.id,
351
352
  exported: true,
352
353
  apply: ({context}) => bridge.check,
@@ -401,11 +402,15 @@ const handleBridgeProps = (config, bridge, { addFirst, uuid } = {}) => {
401
402
  if (bridge.generatorp) {
402
403
  const match = bridge.generatorp.match || (() => true)
403
404
  const apply = typeof bridge.generatorp === 'function' ? bridge.generatorp : bridge.generatorp.apply || bridge.generatorp
404
- const level = bridge.generatorp.level >= 0 ? bridge.generatorp.level : bridge.level + 1
405
+ let level = bridge.generatorp.level >= 0 ? bridge.generatorp.level : bridge.level + 1
406
+ if (!bridge.bridge) {
407
+ level = 0
408
+ }
405
409
 
406
410
  const generator = {
407
411
  where: bridge.generatorp.where || bridge.where || helpers.where(4),
408
- match: async (args) => bridge.id === args.context.marker && args.context.level === level && args.context.paraphrase && await match(args),
412
+ // match: async (args) => bridge.id === args.context.marker && args.context.level === level && args.context.paraphrase && await match(args),
413
+ match: async (args) => args.isA(args.context.marker, bridge.id) && args.context.level === level && args.context.paraphrase && await match(args),
409
414
  apply: (args) => apply(args),
410
415
  applyWrapped: apply,
411
416
  property: 'generatorp'
@@ -422,7 +427,8 @@ const handleBridgeProps = (config, bridge, { addFirst, uuid } = {}) => {
422
427
  const level = bridge.generatorr.level >= 0 ? bridge.generatorr.level : bridge.level + 1
423
428
  const generator = {
424
429
  where: bridge.generatorr.where || bridge.where || helpers.where(4),
425
- match: async (args) => bridge.id === args.context.marker && args.context.level === level && !args.context.paraphrase && (args.context.response || args.context.isResponse) && await match(args),
430
+ // match: async (args) => bridge.id === args.context.marker && args.context.level === level && !args.context.paraphrase && (args.context.response || args.context.isResponse) && await match(args),
431
+ match: async (args) => args.isA(args.context.marker, bridge.id) && args.context.level === level && !args.context.paraphrase && (args.context.response || args.context.isResponse) && await match(args),
426
432
  apply: (args) => apply(args),
427
433
  applyWrapped: apply,
428
434
  property: 'generatorr'
@@ -509,8 +515,43 @@ const handleCalculatedProps = (baseConfig, moreConfig, { addFirst, uuid } = {})
509
515
  if (moreConfig.bridges) {
510
516
  moreConfig.bridges = moreConfig.bridges.map((bridge) => {
511
517
  bridge = { ...bridge }
512
- const valid = ['after', 'conditional', 'associations', 'before', 'bridge', 'check', 'disabled', 'scope', 'skipable', 'return_type_selector', 'evaluator', 'evaluators', 'generatorp', 'generatorr', 'generatorpr', 'generators', 'operator', 'id', 'convolution', 'inverted', 'isA', 'children', 'parents',
513
- 'level', 'optional', 'selector', 'separators', 'semantic', 'semantics', 'words', /Bridge$/, 'localHierarchy', 'levelSpecificHierarchy', 'where', 'uuid']
518
+ const valid = [
519
+ 'after',
520
+ 'associations',
521
+ 'before',
522
+ 'bridge',
523
+ 'check',
524
+ 'children',
525
+ 'conditional',
526
+ 'convolution',
527
+ 'disabled',
528
+ 'enhanced_associations',
529
+ 'evaluator',
530
+ 'evaluators',
531
+ 'generatorp',
532
+ 'generatorpr',
533
+ 'generatorr',
534
+ 'generators',
535
+ 'id',
536
+ 'inverted',
537
+ 'isA',
538
+ 'level',
539
+ 'levelSpecificHierarchy',
540
+ 'localHierarchy',
541
+ 'operator',
542
+ 'optional',
543
+ 'parents',
544
+ 'return_type_selector',
545
+ 'scope',
546
+ 'selector',
547
+ 'semantic',
548
+ 'semantics',
549
+ 'separators',
550
+ 'skipable',
551
+ 'uuid',
552
+ 'where',
553
+ 'words', /Bridge$/,
554
+ ]
514
555
  helpers.validProps(valid, bridge, 'bridge')
515
556
  handleBridgeProps(baseConfig, bridge, { addFirst, uuid })
516
557
  return bridge
@@ -673,7 +714,7 @@ function setWordsUUIDs (words, uuid) {
673
714
  for (const key in literals) {
674
715
  literals[key] = literals[key].map((o) => Object.assign(o, { uuid }))
675
716
  }
676
- const patterns = words.patterns
717
+ const patterns = words.patterns || []
677
718
  for (const pattern of patterns) {
678
719
  pattern.defs.map((def) => Object.assign(def, { uuid }))
679
720
  }
@@ -902,6 +943,10 @@ class Config {
902
943
  return config_toServer(config)
903
944
  }
904
945
 
946
+ async run(handler) {
947
+ return configHelpers.run(this, handler)
948
+ }
949
+
905
950
  async fixtures () {
906
951
  if (this.testConfig?.fixtures) {
907
952
  const args = {}
@@ -935,6 +980,7 @@ class Config {
935
980
 
936
981
  getPseudoConfig (uuid, config) {
937
982
  return {
983
+ pseudo: true,
938
984
  description: 'this is a pseudo config that has limited functionality due to being available in the initializer and fixtures function context',
939
985
  addAssociation: (...args) => this.addAssociation(...args),
940
986
  addAssociations: (...args) => this.addAssociations(...args),
@@ -948,6 +994,14 @@ class Config {
948
994
  removeSemantic: (...args) => this.removeSemantic(...args, uuid, config.name),
949
995
  addWord: (...args) => this.addWord(...args, uuid),
950
996
  addPattern: (...args) => this.addPattern(...args, uuid),
997
+ updateBridge: (...args) => this.updateBridge(...args),
998
+ processContext: (...args) => this.processContext(...args),
999
+
1000
+ semantics: () => this.config.semantics,
1001
+ getConfigs: () => this.configs,
1002
+ getTests: () => this.getTests(),
1003
+ getName: () => this.getName(),
1004
+ getDescription: () => this.getDescription(),
951
1005
 
952
1006
  getHierarchy: (...args) => this.config.hierarchy,
953
1007
  getBridges: (...args) => this.config.bridges,
@@ -957,10 +1011,33 @@ class Config {
957
1011
  fragment: (...args) => this.fragment(...args),
958
1012
  server: (...args) => this.server(...args),
959
1013
  exists: (...args) => this.exists(...args),
960
- addAPI: (...args) => this.addAPI(...args)
1014
+ addAPI: (...args) => this.addAPI(...args),
1015
+
1016
+ getParenthesized: () => this.getParenthesized(),
1017
+ setParenthesized: (...args) => this.setParenthesized(...args),
961
1018
  }
962
1019
  }
963
1020
 
1021
+ getName() {
1022
+ return this.name
1023
+ }
1024
+
1025
+ getDescription() {
1026
+ return this.description
1027
+ }
1028
+
1029
+ getTests() {
1030
+ return this.tests
1031
+ }
1032
+
1033
+ getParenthesized(value) {
1034
+ return this.parenthesized
1035
+ }
1036
+
1037
+ setParenthesized(value) {
1038
+ this.parenthesized = value
1039
+ }
1040
+
964
1041
  inDevelopmentMode (call) {
965
1042
  config.developmentModeOn += 1
966
1043
  try {
@@ -1107,59 +1184,52 @@ class Config {
1107
1184
  return instance
1108
1185
  }
1109
1186
 
1110
- fragmentInstantiator (args, contexts) {
1111
- return new Object({
1112
- contexts: () => contexts,
1113
- instantiate: async (mappings) => {
1114
- const instantiated = _.cloneDeep(contexts)
1115
- // const todo = [...instantiated]
1116
- // const todo = [...instantiated]
1117
- const todo = _.clone(instantiated)
1118
- args = { ...args }
1119
- while (todo.length > 0) {
1120
- const context = todo.pop()
1121
- args.context = context
1122
- for (const mapping of mappings) {
1123
- if (await mapping.match(args)) {
1124
- await mapping.apply(args)
1125
- }
1126
- }
1127
- for (const key of Object.keys(context)) {
1128
- // if (['number', 'string', 'boolean'].includes(typeof (context[key]))) {
1129
- if (!helpers.isCompound(context[key])) {
1130
- continue
1131
- }
1132
- if (context[key].instantiated) {
1133
- continue
1134
- }
1135
- todo.push(context[key])
1136
- }
1137
- }
1138
- return instantiated
1139
- }
1140
- })
1141
- }
1142
-
1143
- fragment (args, query) {
1187
+ getFragment(query) {
1144
1188
  for (const instance of (this.instances || [])) {
1145
1189
  for (const fragment of (instance.fragments || [])) {
1146
1190
  if (fragment.query === query) {
1147
- return this.fragmentInstantiator(args, fragment.contexts)
1191
+ return fragment
1148
1192
  }
1149
1193
  }
1150
1194
  for (const fragment of (instance.resultss || [])) {
1151
1195
  if (fragment.isFragment && fragment.query === query) {
1152
- return this.fragmentInstantiator(args, fragment.contexts)
1196
+ return fragment
1153
1197
  }
1154
1198
  }
1155
1199
  for (const fragment of (this.fragmentsBeingBuilt || [])) {
1156
1200
  if (fragment.query === query) {
1157
- return this.fragmentInstantiator(args, fragment.contexts)
1201
+ return fragment
1158
1202
  }
1159
1203
  }
1160
1204
  }
1161
1205
  }
1162
1206
 
1207
+ // mappings are optional
1208
+ async fragment (args, query, mappings) {
1209
+ const fragment = this.getFragment(query)
1210
+ if (fragment) {
1211
+ let fi = fragmentInstantiator(args, fragment.contexts)
1212
+ if (mappings) {
1213
+ fi = await fi
1214
+ return fi.instantiate([{
1215
+ match: ({context}) => !!mappings[context.value], // is the value in the mappings
1216
+ apply: ({context}) => Object.assign(context, mappings[context.value]),
1217
+ }])
1218
+ } else {
1219
+ return fi
1220
+ }
1221
+ }
1222
+ }
1223
+
1224
+ fragmentMapper (args, values, fromModelQuery, toModelQuery) {
1225
+ const fromModelFragment = this.getFragment(fromModelQuery)
1226
+ console.dir(fromModelFragment)
1227
+ const toModelFragment = this.getFragment(toModelQuery)
1228
+ console.dir(toModelFragment)
1229
+ const mapper = fragmentMapperInstantiator(values, fromModelFragment.contexts, toModelFragment.contexts)
1230
+ return mapper
1231
+ }
1232
+
1163
1233
  // { rebuild: false, isModule: false }
1164
1234
  needsRebuild (template, instance, options) {
1165
1235
  if (options.rebuild) {
@@ -1518,6 +1588,11 @@ class Config {
1518
1588
  }
1519
1589
  }
1520
1590
 
1591
+ updateBridge(id, updater) {
1592
+ const bridge = this.config.bridges.find((b) => b.id === id)
1593
+ updater({ config: this, bridge })
1594
+ }
1595
+
1521
1596
  addBridge (bridge, uuid) {
1522
1597
  if (!this.config.bridges) {
1523
1598
  this.config.bridges = []
@@ -1819,7 +1894,7 @@ class Config {
1819
1894
 
1820
1895
  // set the args in the api's
1821
1896
  setArgs (args) {
1822
- const setArgs = (config) => {
1897
+ const setConfigArgs = (config) => {
1823
1898
  if (!config._api) {
1824
1899
  return
1825
1900
  }
@@ -1832,10 +1907,10 @@ class Config {
1832
1907
  }
1833
1908
  }
1834
1909
 
1835
- setArgs(this)
1910
+ setConfigArgs(this)
1836
1911
  for (const config of this.configs) {
1837
1912
  if (config.config instanceof Config) {
1838
- setArgs(config.config)
1913
+ setConfigArgs(config.config)
1839
1914
  }
1840
1915
  }
1841
1916
  }
@@ -1864,6 +1939,16 @@ class Config {
1864
1939
  }
1865
1940
  }
1866
1941
 
1942
+ getObjects () {
1943
+ const configs = {}
1944
+ const ns = this.config.objects.namespaced
1945
+ configs[this.name] = this
1946
+ for (const config of this.configs) {
1947
+ configs[config._name] = ns[config._uuid]
1948
+ }
1949
+ return configs
1950
+ }
1951
+
1867
1952
  getConfig (name) {
1868
1953
  if (this.name === name) {
1869
1954
  return this
@@ -1911,12 +1996,7 @@ class Config {
1911
1996
  } else if (this.scope == 'development') {
1912
1997
  new_result = !(element.scope === 'testing')
1913
1998
  }
1914
- if (global.GORDO && old_result !== new_result) {
1915
- global.GORDO = false
1916
- console.log("THERE WAS A DIFFERENCE ------------------------------------------------")
1917
- debugger // greg23old
1918
- }
1919
- return old_result
1999
+ return new_result
1920
2000
  }
1921
2001
 
1922
2002
  config.operators = config.operators.filter((element) => keep(element))
@@ -2707,19 +2787,37 @@ class Config {
2707
2787
 
2708
2788
  getContextChecks() {
2709
2789
  const allChecks = []
2710
- for (const name of this.loadOrdering) {
2711
- const checks = this.kms[name].testConfig?.checks?.context || []
2712
- for (const check of checks) {
2713
- if (check.exported) {
2714
- allChecks.push(check)
2790
+ let defaults = () => []
2791
+ if (this.loadOrdering) {
2792
+ for (const name of this.loadOrdering) {
2793
+ const checks = this.kms[name].testConfig?.checks?.context || []
2794
+ const oldDefaults = defaults
2795
+ for (const check of checks) {
2796
+ if (!check.match) {
2797
+ const oldDefaults = defaults
2798
+ defaults = () => [...new Set([...check.apply(), ...oldDefaults()])]
2799
+ continue
2800
+ }
2801
+ if (check.exported) {
2802
+ allChecks.push(check)
2803
+ }
2715
2804
  }
2716
2805
  }
2717
2806
  }
2718
2807
  for (const check of this.testConfig?.checks?.context || []) {
2808
+ if (!check.match) {
2809
+ const oldDefaults = defaults
2810
+ defaults = () => [...new Set([...check.apply(), ...oldDefaults()])]
2811
+ continue
2812
+ }
2719
2813
  if (!check.exported) {
2720
2814
  allChecks.push(check)
2721
2815
  }
2722
2816
  }
2817
+ allChecks.push({
2818
+ match: () => true,
2819
+ apply: defaults
2820
+ })
2723
2821
  return allChecks
2724
2822
  }
2725
2823
 
@@ -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.testConfig?.testModuleName) {
141
+ args.testModuleName = config.testConfig.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) {
@@ -181,6 +200,7 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
181
200
 
182
201
  Object.assign(args, args.getUUIDScoped(uuidForScoping || config.uuid))
183
202
  args.apis = args.apis || ((name) => config.getConfig(name).api)
203
+ // args.apis = (name) => config.getAPIs(name)
184
204
  /*
185
205
  if (uuidForScoping) {
186
206
  Object.assign(args, args.getUUIDScoped(uuidForScoping))
@@ -199,6 +219,22 @@ const getObjects = (objects) => {
199
219
  }
200
220
  }
201
221
 
222
+ const run = async (config, handler) => {
223
+ // map to hash
224
+ config = config || {}
225
+ if (config.config) {
226
+ config = config
227
+ }
228
+
229
+ const hierarchy = new DigraphInternal((config.config || {}).hierarchy || [])
230
+
231
+ const objects = config.config.objects.namespaced[config.uuid]
232
+ const logs = []
233
+ const args = {}
234
+ setupArgs(args, config, logs, hierarchy)
235
+ return handler(args)
236
+ }
237
+
202
238
  const processContext = async (context, { objects = {}, config, logs = [] }) => {
203
239
  const generators = config.getGenerators(logs)
204
240
  const semantics = config.getSemantics(logs)
@@ -291,7 +327,7 @@ const setupContexts = (rawContexts) => {
291
327
  return contexts
292
328
  }
293
329
 
294
- const processContextsB = async ({ config, hierarchy, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate }) => {
330
+ const processContextsB = async ({ config, hierarchy, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate, contextIdCounter }) => {
295
331
  // TODO fix this name to contextsPrime
296
332
  const contextsPrime = []
297
333
  const generatedPrime = []
@@ -311,14 +347,13 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
311
347
  args.insert = (context) => toDo.unshift(context)
312
348
  let overlap, lastRange
313
349
  config.debugLoops = commandLineArgs && commandLineArgs.debugLoops
314
- let context_id_counter = 0
315
350
  while (toDo.length > 0) {
316
351
  const context = toDo.shift()
317
352
  args.calls.next()
318
353
  let contextPrime = context
319
354
  context.topLevel = true
320
- context_id_counter += 1
321
- context.context_id = context_id_counter
355
+ contextIdCounter += 1
356
+ context.context_id = contextIdCounter
322
357
  try {
323
358
  if (json.has_errors) {
324
359
  throw new Error('There are errors in the logs. Run with the -d flag and grep for Error')
@@ -328,6 +363,9 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
328
363
  const semantics = config.getSemantics(json.logs)
329
364
  try {
330
365
  contextPrime = await semantics.apply(args, context)
366
+ // contextPrime.greg = 'yes'
367
+ // console.log("context_id", context.context_id)
368
+ // console.log("semantics.apply", JSON.stringify(contextPrime, null, 2))
331
369
  } catch (e) {
332
370
  if (e.message == 'Maximum call stack size exceeded') {
333
371
  const mostCalled = semantics.getMostCalled()
@@ -368,21 +406,21 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
368
406
  const generated = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
369
407
  let generatedParenthesized = []
370
408
  if (generateParenthesized) {
371
- config.parenthesized = true
409
+ config.setParenthesized(true)
372
410
  generatedParenthesized = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
373
- config.parenthesized = false
411
+ config.setParenthesized(false)
374
412
  }
375
413
  // assumed = { paraphrase: true, response: false };
376
414
  assumed = { paraphrase: true, isResponse: false, response: false }
377
415
  if (generateParenthesized) {
378
- config.parenthesized = false
416
+ config.setParenthesized(false)
379
417
  }
380
418
  const paraphrases = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
381
419
  let paraphrasesParenthesized = []
382
420
  if (generateParenthesized) {
383
- config.parenthesized = true
421
+ config.setParenthesized(true)
384
422
  paraphrasesParenthesized = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
385
- config.parenthesized = false
423
+ config.setParenthesized(false)
386
424
  }
387
425
  contextsPrime.push(contextPrime)
388
426
  generatedPrime.push(generated)
@@ -421,7 +459,7 @@ const processContextsB = async ({ config, hierarchy, semantics, generators, json
421
459
  throw e
422
460
  }
423
461
  }
424
- return { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime }
462
+ return { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime, updatedContextIdCounter: contextIdCounter }
425
463
  }
426
464
 
427
465
  // instance template loadTemplate
@@ -479,6 +517,9 @@ const loadInstance = async (config, instance) => {
479
517
  args.isModule = true
480
518
  args.isProcess = false
481
519
  }
520
+ if (instance.name == config.testConfig.testModuleName) {
521
+ args.isTesting = true
522
+ }
482
523
  await results.apply(args)
483
524
  } else if (results.isFragment) {
484
525
  } else {
@@ -490,6 +531,9 @@ const loadInstance = async (config, instance) => {
490
531
  args.instance = ''
491
532
  args.isProcess = !config.isModule
492
533
  args.isModule = !!config.isModule
534
+ if (instance.name == config.testConfig.testModuleName) {
535
+ args.isTesting = true
536
+ }
493
537
  await processContextsB(args)
494
538
  if (results.skipSemantics) {
495
539
  config.config.skipSemantics = null
@@ -505,6 +549,7 @@ module.exports = {
505
549
  // listable,
506
550
  setupArgs,
507
551
  processContext,
552
+ run,
508
553
  getObjects,
509
554
  gs,
510
555
  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
@@ -182,10 +182,10 @@ class Generators {
182
182
  const generator = this.generators[igenerator]
183
183
  if (await generator.matches(args, objects, context, hierarchy, config, options)) {
184
184
  const log = (message) => { this.logs.push(message) }
185
- // this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${JSON.stringify(context)}`)
185
+ // this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${stringify(context)}`)
186
186
  let errorMessage = 'The apply function did not return a value'
187
187
  try {
188
- generated = await generator.apply(args, objects, context, hierarchy, config, response, log)
188
+ generated = await generator.apply(args, objects, context, hierarchy, config, response, log, options)
189
189
  } catch (e) {
190
190
  // the next if handle this
191
191
  generated = null
@@ -202,7 +202,7 @@ class Generators {
202
202
  }
203
203
  }
204
204
  if (!generated && generated !== '') {
205
- const widths = [10, 10, 90]
205
+ const widths = Lines.addRemainder([10, 10])
206
206
  const lines = new Lines(widths)
207
207
  lines.setElement(0, 0, 'Generator')
208
208
  const source = `${generator.km}/#${generator.index}`
@@ -212,7 +212,7 @@ class Generators {
212
212
  lines.newRow()
213
213
  lines.setElement(0, 1, 'TO')
214
214
  lines.setElement(0, 2, `context_id: ${context.context_id}`)
215
- lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 25 }), null, 2))
215
+ lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 10 }), null, 2))
216
216
  lines.newRow()
217
217
  lines.setElement(0, 1, 'STACK')
218
218
  lines.setElement(0, 2, stack)
@@ -230,7 +230,7 @@ class Generators {
230
230
  throw { error: [message], logs: this.logs }
231
231
  }
232
232
  if (((config || {}).config || {}).debug) {
233
- const widths = [10, 10, 90]
233
+ const widths = Lines.addRemainder([10, 10])
234
234
  const lines = new Lines(widths)
235
235
  lines.setElement(0, 0, 'Generator')
236
236
  if (generator.index > -1 && generator.km) {
@@ -263,7 +263,7 @@ class Generators {
263
263
  }
264
264
  args.calls.pop()
265
265
  if (!applied && ((config || {}).config || {}).debug) {
266
- const widths = [10, 10, 90]
266
+ const widths = Lines.addRemainder([10, 10])
267
267
  const lines = new Lines(widths)
268
268
  lines.setElement(0, 0, 'Generator')
269
269
  lines.setElement(0, 2, 'No generator applied')
@@ -275,7 +275,11 @@ class Generators {
275
275
  lines.setElement(0, 2, JSON.stringify(context, null, 2))
276
276
  this.logs.push(lines.toString())
277
277
  }
278
- return ((config || {}).parenthesized ? '(' + generated + ')' : generated)
278
+ let parenthesized = false
279
+ if (config && config.getParenthesized()) {
280
+ parenthesized = true
281
+ }
282
+ return parenthesized ? '(' + generated + ')' : generated
279
283
  }
280
284
  }
281
285
 
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,
@@ -463,4 +565,6 @@ module.exports = {
463
565
  w,
464
566
  suggestAssociationsFix,
465
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];
package/src/semantics.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { args: contextArgs, normalizeGenerator, normalizeSemantic } = require('./helpers')
2
2
  const Lines = require('../lines')
3
3
  const helpers = require('./helpers')
4
+ const debug = require('./debug')
4
5
 
5
6
  class Semantic {
6
7
  // constructor ({match, apply, uuid, index, km, notes}) {
@@ -70,6 +71,7 @@ class Semantic {
70
71
  }
71
72
 
72
73
  async matches (args, context, options = {}) {
74
+ args = {...args}
73
75
  this.fixUpArgs(args, context)
74
76
  const matches = await this.matcher(args)
75
77
  if (matches && (options.debug || {}).match || args.callId === this.callId) {
@@ -81,6 +83,7 @@ class Semantic {
81
83
  }
82
84
 
83
85
  async apply (args, context, s, options = {}) {
86
+ args = {...args}
84
87
  const { config } = args
85
88
  if (config && config.debugLoops) {
86
89
  console.log('apply', this.toLabel())
@@ -218,7 +221,7 @@ class Semantics {
218
221
  errorMessage = e.toString()
219
222
  }
220
223
 
221
- const widths = [10, 10, 90]
224
+ const widths = Lines.addRemainder([10, 10])
222
225
  const lines = new Lines(widths)
223
226
  lines.setElement(0, 0, 'Semantic')
224
227
  const source = `${semantic.km}/#${semantic.index}`
@@ -248,7 +251,7 @@ class Semantics {
248
251
  args.calls.touch(contextPrime)
249
252
  // this.logs.push(`Semantics: applied ${semantic.toString()}\n to\n ${JSON.stringify(context)}\n the result was ${JSON.stringify(contextPrime)}\n`)
250
253
  if (((config || {}).config || {}).debug) {
251
- const widths = [10, 10, 132]
254
+ const widths = Lines.addRemainder([10, 10])
252
255
  const lines = new Lines(widths)
253
256
  lines.setElement(0, 0, 'Semantic')
254
257
  if (semantic.index > -1 && semantic.km) {
@@ -295,7 +298,7 @@ class Semantics {
295
298
  }
296
299
  args.calls.pop()
297
300
  if (!applied && ((config || {}).config || {}).debug) {
298
- const widths = [10, 10, 90]
301
+ const widths = Lines.addRemainder([10, 10])
299
302
  const lines = new Lines(widths)
300
303
  lines.setElement(0, 0, 'Semantic')
301
304
  lines.setElement(0, 2, 'No semantic applied')