@bian-womp/spark-graph 0.3.50 → 0.3.52

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.
Files changed (188) hide show
  1. package/lib/cjs/index.cjs +645 -361
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/types.d.ts +1 -0
  4. package/lib/cjs/src/core/types.d.ts.map +1 -1
  5. package/lib/cjs/src/index.d.ts +3 -3
  6. package/lib/cjs/src/index.d.ts.map +1 -1
  7. package/lib/{src/runtime/utils.d.ts → cjs/src/misc/utils/LevelLogger.d.ts} +1 -16
  8. package/lib/cjs/src/misc/utils/LevelLogger.d.ts.map +1 -0
  9. package/lib/cjs/src/misc/utils/json.d.ts +12 -0
  10. package/lib/cjs/src/misc/utils/json.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/GraphRuntime.d.ts +7 -2
  12. package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/LocalEngine.d.ts.map +1 -1
  14. package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +8 -4
  16. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  17. package/lib/cjs/src/runtime/components/interfaces.d.ts +5 -1
  18. package/lib/cjs/src/runtime/components/interfaces.d.ts.map +1 -1
  19. package/lib/cjs/src/runtime/components/types.d.ts +1 -0
  20. package/lib/cjs/src/runtime/components/types.d.ts.map +1 -1
  21. package/lib/cjs/src/runtime/utils.d.ts +0 -51
  22. package/lib/cjs/src/runtime/utils.d.ts.map +1 -1
  23. package/lib/esm/index.js +644 -362
  24. package/lib/esm/index.js.map +1 -1
  25. package/lib/esm/src/core/types.d.ts +1 -0
  26. package/lib/esm/src/core/types.d.ts.map +1 -1
  27. package/lib/esm/src/index.d.ts +3 -3
  28. package/lib/esm/src/index.d.ts.map +1 -1
  29. package/lib/esm/src/misc/utils/LevelLogger.d.ts +52 -0
  30. package/lib/esm/src/misc/utils/LevelLogger.d.ts.map +1 -0
  31. package/lib/esm/src/misc/utils/json.d.ts +12 -0
  32. package/lib/esm/src/misc/utils/json.d.ts.map +1 -1
  33. package/lib/esm/src/runtime/GraphRuntime.d.ts +7 -2
  34. package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
  35. package/lib/esm/src/runtime/LocalEngine.d.ts.map +1 -1
  36. package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
  37. package/lib/esm/src/runtime/components/NodeExecutor.d.ts +8 -4
  38. package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  39. package/lib/esm/src/runtime/components/interfaces.d.ts +5 -1
  40. package/lib/esm/src/runtime/components/interfaces.d.ts.map +1 -1
  41. package/lib/esm/src/runtime/components/types.d.ts +1 -0
  42. package/lib/esm/src/runtime/components/types.d.ts.map +1 -1
  43. package/lib/esm/src/runtime/utils.d.ts +0 -51
  44. package/lib/esm/src/runtime/utils.d.ts.map +1 -1
  45. package/package.json +2 -2
  46. package/lib/src/builder/GraphBuilder.d.ts +0 -43
  47. package/lib/src/builder/GraphBuilder.d.ts.map +0 -1
  48. package/lib/src/builder/GraphBuilder.js +0 -284
  49. package/lib/src/builder/GraphBuilder.js.map +0 -1
  50. package/lib/src/builder/Registry.d.ts +0 -93
  51. package/lib/src/builder/Registry.d.ts.map +0 -1
  52. package/lib/src/builder/Registry.js +0 -393
  53. package/lib/src/builder/Registry.js.map +0 -1
  54. package/lib/src/core/categories.d.ts +0 -22
  55. package/lib/src/core/categories.d.ts.map +0 -1
  56. package/lib/src/core/categories.js +0 -2
  57. package/lib/src/core/categories.js.map +0 -1
  58. package/lib/src/core/order.d.ts +0 -7
  59. package/lib/src/core/order.d.ts.map +0 -1
  60. package/lib/src/core/order.js +0 -66
  61. package/lib/src/core/order.js.map +0 -1
  62. package/lib/src/core/type-utils.d.ts +0 -29
  63. package/lib/src/core/type-utils.d.ts.map +0 -1
  64. package/lib/src/core/type-utils.js +0 -97
  65. package/lib/src/core/type-utils.js.map +0 -1
  66. package/lib/src/core/types.d.ts +0 -92
  67. package/lib/src/core/types.d.ts.map +0 -1
  68. package/lib/src/core/types.js +0 -2
  69. package/lib/src/core/types.js.map +0 -1
  70. package/lib/src/examples/arrays.d.ts +0 -5
  71. package/lib/src/examples/arrays.d.ts.map +0 -1
  72. package/lib/src/examples/arrays.js +0 -49
  73. package/lib/src/examples/arrays.js.map +0 -1
  74. package/lib/src/examples/async.d.ts +0 -5
  75. package/lib/src/examples/async.d.ts.map +0 -1
  76. package/lib/src/examples/async.js +0 -91
  77. package/lib/src/examples/async.js.map +0 -1
  78. package/lib/src/examples/progress.d.ts +0 -5
  79. package/lib/src/examples/progress.d.ts.map +0 -1
  80. package/lib/src/examples/progress.js +0 -51
  81. package/lib/src/examples/progress.js.map +0 -1
  82. package/lib/src/examples/run.d.ts +0 -2
  83. package/lib/src/examples/run.d.ts.map +0 -1
  84. package/lib/src/examples/run.js +0 -32
  85. package/lib/src/examples/run.js.map +0 -1
  86. package/lib/src/examples/runMode.d.ts +0 -2
  87. package/lib/src/examples/runMode.d.ts.map +0 -1
  88. package/lib/src/examples/runMode.js +0 -223
  89. package/lib/src/examples/runMode.js.map +0 -1
  90. package/lib/src/examples/shared.d.ts +0 -5
  91. package/lib/src/examples/shared.d.ts.map +0 -1
  92. package/lib/src/examples/shared.js +0 -49
  93. package/lib/src/examples/shared.js.map +0 -1
  94. package/lib/src/examples/simple.d.ts +0 -5
  95. package/lib/src/examples/simple.d.ts.map +0 -1
  96. package/lib/src/examples/simple.js +0 -79
  97. package/lib/src/examples/simple.js.map +0 -1
  98. package/lib/src/examples/snapshot.d.ts +0 -4
  99. package/lib/src/examples/snapshot.d.ts.map +0 -1
  100. package/lib/src/examples/snapshot.js +0 -58
  101. package/lib/src/examples/snapshot.js.map +0 -1
  102. package/lib/src/examples/validation.d.ts +0 -5
  103. package/lib/src/examples/validation.d.ts.map +0 -1
  104. package/lib/src/examples/validation.js +0 -105
  105. package/lib/src/examples/validation.js.map +0 -1
  106. package/lib/src/index.d.ts +0 -27
  107. package/lib/src/index.d.ts.map +0 -1
  108. package/lib/src/index.js +0 -19
  109. package/lib/src/index.js.map +0 -1
  110. package/lib/src/misc/base.d.ts +0 -51
  111. package/lib/src/misc/base.d.ts.map +0 -1
  112. package/lib/src/misc/base.js +0 -1122
  113. package/lib/src/misc/base.js.map +0 -1
  114. package/lib/src/misc/utils/json.d.ts +0 -22
  115. package/lib/src/misc/utils/json.d.ts.map +0 -1
  116. package/lib/src/misc/utils/json.js +0 -239
  117. package/lib/src/misc/utils/json.js.map +0 -1
  118. package/lib/src/misc/utils/merge.d.ts +0 -51
  119. package/lib/src/misc/utils/merge.d.ts.map +0 -1
  120. package/lib/src/misc/utils/merge.js +0 -600
  121. package/lib/src/misc/utils/merge.js.map +0 -1
  122. package/lib/src/plugins/composite.d.ts +0 -22
  123. package/lib/src/plugins/composite.d.ts.map +0 -1
  124. package/lib/src/plugins/composite.js +0 -59
  125. package/lib/src/plugins/composite.js.map +0 -1
  126. package/lib/src/plugins/compute.d.ts +0 -5
  127. package/lib/src/plugins/compute.d.ts.map +0 -1
  128. package/lib/src/plugins/compute.js +0 -39
  129. package/lib/src/plugins/compute.js.map +0 -1
  130. package/lib/src/runtime/Engine.d.ts +0 -28
  131. package/lib/src/runtime/Engine.d.ts.map +0 -1
  132. package/lib/src/runtime/Engine.js +0 -2
  133. package/lib/src/runtime/Engine.js.map +0 -1
  134. package/lib/src/runtime/GraphLifecycleApi.d.ts +0 -46
  135. package/lib/src/runtime/GraphLifecycleApi.d.ts.map +0 -1
  136. package/lib/src/runtime/GraphLifecycleApi.js +0 -2
  137. package/lib/src/runtime/GraphLifecycleApi.js.map +0 -1
  138. package/lib/src/runtime/GraphRuntime.d.ts +0 -94
  139. package/lib/src/runtime/GraphRuntime.d.ts.map +0 -1
  140. package/lib/src/runtime/GraphRuntime.js +0 -729
  141. package/lib/src/runtime/GraphRuntime.js.map +0 -1
  142. package/lib/src/runtime/LocalEngine.d.ts +0 -45
  143. package/lib/src/runtime/LocalEngine.d.ts.map +0 -1
  144. package/lib/src/runtime/LocalEngine.js +0 -89
  145. package/lib/src/runtime/LocalEngine.js.map +0 -1
  146. package/lib/src/runtime/components/EdgePropagator.d.ts +0 -101
  147. package/lib/src/runtime/components/EdgePropagator.d.ts.map +0 -1
  148. package/lib/src/runtime/components/EdgePropagator.js +0 -372
  149. package/lib/src/runtime/components/EdgePropagator.js.map +0 -1
  150. package/lib/src/runtime/components/EventEmitter.d.ts +0 -12
  151. package/lib/src/runtime/components/EventEmitter.d.ts.map +0 -1
  152. package/lib/src/runtime/components/EventEmitter.js +0 -33
  153. package/lib/src/runtime/components/EventEmitter.js.map +0 -1
  154. package/lib/src/runtime/components/Graph.d.ts +0 -211
  155. package/lib/src/runtime/components/Graph.d.ts.map +0 -1
  156. package/lib/src/runtime/components/Graph.js +0 -468
  157. package/lib/src/runtime/components/Graph.js.map +0 -1
  158. package/lib/src/runtime/components/HandleResolver.d.ts +0 -36
  159. package/lib/src/runtime/components/HandleResolver.d.ts.map +0 -1
  160. package/lib/src/runtime/components/HandleResolver.js +0 -231
  161. package/lib/src/runtime/components/HandleResolver.js.map +0 -1
  162. package/lib/src/runtime/components/NodeExecutor.d.ts +0 -110
  163. package/lib/src/runtime/components/NodeExecutor.d.ts.map +0 -1
  164. package/lib/src/runtime/components/NodeExecutor.js +0 -659
  165. package/lib/src/runtime/components/NodeExecutor.js.map +0 -1
  166. package/lib/src/runtime/components/RunContextManager.d.ts +0 -86
  167. package/lib/src/runtime/components/RunContextManager.d.ts.map +0 -1
  168. package/lib/src/runtime/components/RunContextManager.js +0 -302
  169. package/lib/src/runtime/components/RunContextManager.js.map +0 -1
  170. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts +0 -31
  171. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts.map +0 -1
  172. package/lib/src/runtime/components/RuntimeValidatorManager.js +0 -55
  173. package/lib/src/runtime/components/RuntimeValidatorManager.js.map +0 -1
  174. package/lib/src/runtime/components/graph-utils.d.ts +0 -33
  175. package/lib/src/runtime/components/graph-utils.d.ts.map +0 -1
  176. package/lib/src/runtime/components/graph-utils.js +0 -292
  177. package/lib/src/runtime/components/graph-utils.js.map +0 -1
  178. package/lib/src/runtime/components/interfaces.d.ts +0 -54
  179. package/lib/src/runtime/components/interfaces.d.ts.map +0 -1
  180. package/lib/src/runtime/components/interfaces.js +0 -2
  181. package/lib/src/runtime/components/interfaces.js.map +0 -1
  182. package/lib/src/runtime/components/types.d.ts +0 -55
  183. package/lib/src/runtime/components/types.d.ts.map +0 -1
  184. package/lib/src/runtime/components/types.js +0 -2
  185. package/lib/src/runtime/components/types.js.map +0 -1
  186. package/lib/src/runtime/utils.d.ts.map +0 -1
  187. package/lib/src/runtime/utils.js +0 -137
  188. package/lib/src/runtime/utils.js.map +0 -1
package/lib/cjs/index.cjs CHANGED
@@ -998,39 +998,494 @@ const LOG_LEVEL_VALUES = {
998
998
  silent: 4,
999
999
  };
1000
1000
 
1001
- /**
1002
- * Shared utility functions for runtime components
1003
- */
1004
- /**
1005
- * Type guard to check if a value is a Promise
1006
- */
1007
- function isPromise(value) {
1008
- return !!value && typeof value.then === "function";
1001
+ function parseJsonPath(path) {
1002
+ if (typeof path === "string") {
1003
+ return path.split(".").flatMap((segment) => {
1004
+ const arrayMatch = segment.match(/^(.+)\[(\d+)\]$/);
1005
+ if (arrayMatch) {
1006
+ const index = parseInt(arrayMatch[2], 10);
1007
+ return arrayMatch[1] ? [arrayMatch[1], index] : [index];
1008
+ }
1009
+ return [segment];
1010
+ });
1011
+ }
1012
+ return path;
1009
1013
  }
1010
- /**
1011
- * Unwrap a value that might be a Promise
1012
- */
1013
- async function unwrapMaybePromise(value) {
1014
- return isPromise(value) ? await value : value;
1014
+ function getValueAtPath(obj, pathSegments) {
1015
+ if (pathSegments.length === 0) {
1016
+ return { value: obj, parent: null, key: "" };
1017
+ }
1018
+ let current = obj;
1019
+ for (let i = 0; i < pathSegments.length - 1; i++) {
1020
+ const segment = pathSegments[i];
1021
+ if (current === null ||
1022
+ current === undefined ||
1023
+ typeof current !== "object") {
1024
+ return null;
1025
+ }
1026
+ if (typeof segment === "string") {
1027
+ if (Array.isArray(current)) {
1028
+ const index = parseInt(segment, 10);
1029
+ if (isNaN(index))
1030
+ return null;
1031
+ current = current[index];
1032
+ }
1033
+ else {
1034
+ current = current[segment];
1035
+ }
1036
+ }
1037
+ else if (typeof segment === "number") {
1038
+ if (Array.isArray(current)) {
1039
+ if (segment >= 0 && segment < current.length) {
1040
+ current = current[segment];
1041
+ }
1042
+ else {
1043
+ return null;
1044
+ }
1045
+ }
1046
+ else {
1047
+ return null;
1048
+ }
1049
+ }
1050
+ else if (segment instanceof RegExp) {
1051
+ if (Array.isArray(current)) {
1052
+ return null;
1053
+ }
1054
+ const obj = current;
1055
+ const matchingKey = Object.keys(obj).find((key) => segment.test(key));
1056
+ if (!matchingKey)
1057
+ return null;
1058
+ current = obj[matchingKey];
1059
+ }
1060
+ else {
1061
+ return null;
1062
+ }
1063
+ }
1064
+ const lastSegment = pathSegments[pathSegments.length - 1];
1065
+ if (typeof lastSegment === "string") {
1066
+ if (Array.isArray(current)) {
1067
+ const index = parseInt(lastSegment, 10);
1068
+ if (isNaN(index))
1069
+ return null;
1070
+ return { value: current[index], parent: current, key: index };
1071
+ }
1072
+ else if (current !== null &&
1073
+ current !== undefined &&
1074
+ typeof current === "object") {
1075
+ return {
1076
+ value: current[lastSegment],
1077
+ parent: current,
1078
+ key: lastSegment,
1079
+ };
1080
+ }
1081
+ }
1082
+ else if (typeof lastSegment === "number") {
1083
+ if (Array.isArray(current)) {
1084
+ if (lastSegment >= 0 && lastSegment < current.length) {
1085
+ return {
1086
+ value: current[lastSegment],
1087
+ parent: current,
1088
+ key: lastSegment,
1089
+ };
1090
+ }
1091
+ }
1092
+ return null;
1093
+ }
1094
+ else if (lastSegment instanceof RegExp) {
1095
+ if (Array.isArray(current)) {
1096
+ return null;
1097
+ }
1098
+ const obj = current;
1099
+ const matchingKey = Object.keys(obj).find((key) => lastSegment.test(key));
1100
+ if (!matchingKey)
1101
+ return null;
1102
+ return { value: obj[matchingKey], parent: current, key: matchingKey };
1103
+ }
1104
+ return null;
1105
+ }
1106
+ function setValueAtPath(obj, pathSegments, newValue) {
1107
+ const result = getValueAtPath(obj, pathSegments);
1108
+ if (!result || result.parent === null)
1109
+ return false;
1110
+ if (Array.isArray(result.parent)) {
1111
+ result.parent[result.key] = newValue;
1112
+ }
1113
+ else {
1114
+ result.parent[result.key] = newValue;
1115
+ }
1116
+ return true;
1015
1117
  }
1016
1118
  /**
1017
- * Shallow/deep-ish equality check to avoid unnecessary runs on identical values
1119
+ * Sets a value at a path, creating intermediate objects as needed.
1120
+ * Mutates the root object in place.
1121
+ * @param root - The root object to modify (must be an object, will be initialized if needed)
1122
+ * @param pathSegments - The path segments to traverse
1123
+ * @param value - The value to set, or null to delete the path
1124
+ * @throws Error if path cannot be created (e.g., array indices not supported, invalid parent types)
1018
1125
  */
1019
- function valuesEqual(a, b) {
1020
- if (a === b)
1021
- return true;
1022
- if (typeof a !== typeof b)
1023
- return false;
1024
- if (a && b && typeof a === "object") {
1025
- try {
1026
- return JSON.stringify(a) === JSON.stringify(b);
1126
+ function setValueAtPathWithCreation(root, pathSegments, value) {
1127
+ if (value === null) {
1128
+ const result = getValueAtPath(root, pathSegments);
1129
+ if (result && result.parent !== null && !Array.isArray(result.parent)) {
1130
+ delete result.parent[result.key];
1027
1131
  }
1028
- catch {
1132
+ return;
1133
+ }
1134
+ if (!root || typeof root !== "object" || Array.isArray(root)) {
1135
+ throw new Error("Root must be an object");
1136
+ }
1137
+ let current = root;
1138
+ for (let i = 0; i < pathSegments.length - 1; i++) {
1139
+ const segment = pathSegments[i];
1140
+ if (typeof segment === "string") {
1141
+ if (!current ||
1142
+ typeof current !== "object" ||
1143
+ Array.isArray(current) ||
1144
+ !(segment in current) ||
1145
+ typeof current[segment] !== "object" ||
1146
+ current[segment] === null ||
1147
+ Array.isArray(current[segment])) {
1148
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
1149
+ throw new Error(`Cannot create path: parent at segment ${i} is not an object`);
1150
+ }
1151
+ current[segment] = {};
1152
+ }
1153
+ current = current[segment];
1154
+ }
1155
+ else {
1156
+ throw new Error("Array indices not supported in extData paths");
1157
+ }
1158
+ }
1159
+ const lastSegment = pathSegments[pathSegments.length - 1];
1160
+ if (typeof lastSegment === "string") {
1161
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
1162
+ throw new Error(`Cannot set value: parent at final segment is not an object`);
1163
+ }
1164
+ current[lastSegment] = value;
1165
+ }
1166
+ else {
1167
+ throw new Error("Array indices not supported in extData paths");
1168
+ }
1169
+ }
1170
+ function findMatchingPaths(obj, pathSegments, currentPath = []) {
1171
+ if (pathSegments.length === 0) {
1172
+ return [{ path: currentPath, value: obj }];
1173
+ }
1174
+ const [currentSegment, ...remainingSegments] = pathSegments;
1175
+ const results = [];
1176
+ if (currentSegment === undefined) {
1177
+ return results;
1178
+ }
1179
+ if (typeof currentSegment === "string") {
1180
+ if (Array.isArray(obj)) {
1181
+ const index = parseInt(currentSegment, 10);
1182
+ if (!isNaN(index) && index >= 0 && index < obj.length) {
1183
+ results.push(...findMatchingPaths(obj[index], remainingSegments, [
1184
+ ...currentPath,
1185
+ index,
1186
+ ]));
1187
+ }
1188
+ }
1189
+ else if (obj !== null && obj !== undefined && typeof obj === "object") {
1190
+ const objRecord = obj;
1191
+ if (currentSegment in objRecord) {
1192
+ results.push(...findMatchingPaths(objRecord[currentSegment], remainingSegments, [
1193
+ ...currentPath,
1194
+ currentSegment,
1195
+ ]));
1196
+ }
1197
+ }
1198
+ }
1199
+ else if (typeof currentSegment === "number") {
1200
+ if (Array.isArray(obj)) {
1201
+ if (currentSegment >= 0 && currentSegment < obj.length) {
1202
+ results.push(...findMatchingPaths(obj[currentSegment], remainingSegments, [
1203
+ ...currentPath,
1204
+ currentSegment,
1205
+ ]));
1206
+ }
1207
+ }
1208
+ }
1209
+ else if (currentSegment instanceof RegExp) {
1210
+ if (Array.isArray(obj)) {
1211
+ for (let i = 0; i < obj.length; i++) {
1212
+ results.push(...findMatchingPaths(obj[i], remainingSegments, [...currentPath, i]));
1213
+ }
1214
+ }
1215
+ else if (obj !== null && obj !== undefined && typeof obj === "object") {
1216
+ const objRecord = obj;
1217
+ for (const key of Object.keys(objRecord)) {
1218
+ if (currentSegment.test(key)) {
1219
+ results.push(...findMatchingPaths(objRecord[key], remainingSegments, [
1220
+ ...currentPath,
1221
+ key,
1222
+ ]));
1223
+ }
1224
+ }
1225
+ }
1226
+ }
1227
+ return results;
1228
+ }
1229
+ function stringifyJson(obj, oneLiner) {
1230
+ // No formatting requested: behave exactly like native JSON.stringify.
1231
+ if (!oneLiner)
1232
+ return JSON.stringify(obj);
1233
+ const indentSize = Number.isFinite(oneLiner.indent)
1234
+ ? Math.max(0, Math.floor(oneLiner.indent))
1235
+ : 2;
1236
+ const maxDepth = typeof oneLiner.maxDepth === "number" && Number.isFinite(oneLiner.maxDepth)
1237
+ ? oneLiner.maxDepth
1238
+ : undefined;
1239
+ const patterns = (oneLiner.paths || []).filter(Boolean);
1240
+ // Preserve JSON.stringify semantics for things like toJSON(), dropping functions/undefined, etc.
1241
+ // Note: this still throws on circular structures (same as JSON.stringify).
1242
+ const base = JSON.stringify(obj);
1243
+ if (base === undefined)
1244
+ return base;
1245
+ const value = JSON.parse(base);
1246
+ const pathMatchers = patterns.map((p) => compilePathMatcher(String(p)));
1247
+ const formatKey = (k) => JSON.stringify(k);
1248
+ const shouldInline = (path, key, v, depth) => {
1249
+ if (maxDepth !== undefined && depth > maxDepth)
1250
+ return true;
1251
+ if (oneLiner.criteria?.({ path, key, value: v, depth }))
1252
+ return true;
1253
+ if (!pathMatchers.length)
1029
1254
  return false;
1255
+ const tokensWithRoot = tokenizePath(path);
1256
+ const tokensNoRoot = tokensWithRoot[0] === "$" ? tokensWithRoot.slice(1) : tokensWithRoot;
1257
+ return pathMatchers.some((m) => m(tokensWithRoot, tokensNoRoot));
1258
+ };
1259
+ const stringifyInline = (v, depth, path) => {
1260
+ if (v === null)
1261
+ return "null";
1262
+ const t = typeof v;
1263
+ if (t === "string")
1264
+ return JSON.stringify(v);
1265
+ if (t === "number" || t === "boolean")
1266
+ return String(v);
1267
+ if (Array.isArray(v)) {
1268
+ if (!v.length)
1269
+ return "[]";
1270
+ const parts = v.map((vv, i) => stringifyInline(vv));
1271
+ return `[ ${parts.join(", ")} ]`;
1272
+ }
1273
+ if (t === "object") {
1274
+ const keys = Object.keys(v);
1275
+ if (!keys.length)
1276
+ return "{}";
1277
+ const parts = keys.map((k) => `${formatKey(k)}: ${stringifyInline(v[k])}`);
1278
+ return `{ ${parts.join(", ")} }`;
1279
+ }
1280
+ // Shouldn't happen after JSON.parse(JSON.stringify(...)), but keep output valid JSON.
1281
+ return "null";
1282
+ };
1283
+ const stringifyPretty = (v, depth, path, key) => {
1284
+ if (shouldInline(path, key, v, depth))
1285
+ return stringifyInline(v);
1286
+ if (v === null)
1287
+ return "null";
1288
+ const t = typeof v;
1289
+ if (t === "string")
1290
+ return JSON.stringify(v);
1291
+ if (t === "number" || t === "boolean")
1292
+ return String(v);
1293
+ const indentCur = " ".repeat(indentSize * depth);
1294
+ const indentInner = " ".repeat(indentSize * (depth + 1));
1295
+ if (Array.isArray(v)) {
1296
+ if (!v.length)
1297
+ return "[]";
1298
+ // Compact array style: `[{...}, {...}]` while still allowing multi-line objects within.
1299
+ const parts = v.map((vv, i) => stringifyPretty(vv, depth + 1, `${path}[${i}]`, String(i)));
1300
+ return `[ ${parts.join(", ")} ]`;
1301
+ }
1302
+ if (t === "object") {
1303
+ const keys = Object.keys(v);
1304
+ if (!keys.length)
1305
+ return "{}";
1306
+ const lines = keys.map((k, idx) => {
1307
+ const childPath = `${path}.${k}`;
1308
+ const rendered = stringifyPretty(v[k], depth + 1, childPath, k);
1309
+ const comma = idx === keys.length - 1 ? "" : ",";
1310
+ return `${indentInner}${formatKey(k)}: ${rendered}${comma}`;
1311
+ });
1312
+ return `{\n${lines.join("\n")}\n${indentCur}}`;
1030
1313
  }
1314
+ return "null";
1315
+ };
1316
+ return stringifyPretty(value, 0, "$", "$");
1317
+ }
1318
+ function tokenizePath(path) {
1319
+ // Path format we generate: `$`, `$.a.b[0].c`
1320
+ // Tokens: ["$", "a", "b", "[0]", "c"]
1321
+ const tokens = [];
1322
+ let i = 0;
1323
+ if (path.startsWith("$")) {
1324
+ tokens.push("$");
1325
+ i = 1;
1326
+ }
1327
+ while (i < path.length) {
1328
+ const ch = path[i];
1329
+ if (ch === ".") {
1330
+ i++;
1331
+ const start = i;
1332
+ while (i < path.length && path[i] !== "." && path[i] !== "[")
1333
+ i++;
1334
+ if (i > start)
1335
+ tokens.push(path.slice(start, i));
1336
+ continue;
1337
+ }
1338
+ if (ch === "[") {
1339
+ const end = path.indexOf("]", i + 1);
1340
+ if (end < 0) {
1341
+ tokens.push(path.slice(i));
1342
+ break;
1343
+ }
1344
+ tokens.push(path.slice(i, end + 1));
1345
+ i = end + 1;
1346
+ continue;
1347
+ }
1348
+ // Unexpected char; skip.
1349
+ i++;
1031
1350
  }
1032
- return false;
1351
+ return tokens.filter((t) => t.length);
1033
1352
  }
1353
+ function tokenizePattern(pattern) {
1354
+ // Pattern format: `$`, `$.a.*.b`, `**.graph.**`, `arr[2].z`
1355
+ // Tokens: ["$", "a", "*", "b"] etc. Brackets become their own token: ["arr","[2]","z"]
1356
+ const tokens = [];
1357
+ let i = 0;
1358
+ if (pattern.startsWith("$")) {
1359
+ tokens.push("$");
1360
+ i = 1;
1361
+ }
1362
+ while (i < pattern.length) {
1363
+ const ch = pattern[i];
1364
+ if (ch === ".") {
1365
+ i++;
1366
+ continue;
1367
+ }
1368
+ if (ch === "[") {
1369
+ const end = pattern.indexOf("]", i + 1);
1370
+ if (end < 0) {
1371
+ tokens.push(pattern.slice(i));
1372
+ break;
1373
+ }
1374
+ tokens.push(pattern.slice(i, end + 1));
1375
+ i = end + 1;
1376
+ continue;
1377
+ }
1378
+ const start = i;
1379
+ while (i < pattern.length && pattern[i] !== "." && pattern[i] !== "[")
1380
+ i++;
1381
+ if (i > start)
1382
+ tokens.push(pattern.slice(start, i));
1383
+ }
1384
+ return tokens.filter((t) => t.length);
1385
+ }
1386
+ function compilePathMatcher(pattern) {
1387
+ // Wildcard semantics (case-insensitive):
1388
+ // - `*` matches exactly 1 path segment (key or `[index]`)
1389
+ // - `**` matches 0 or more segments
1390
+ // - `#` matches exactly 1 numeric segment (e.g. `"0"` or `[0]`)
1391
+ // - `##` matches 0 or more numeric segments
1392
+ // - `[*]` matches exactly 1 index segment
1393
+ const pat = tokenizePattern(pattern);
1394
+ const expectsRoot = pat[0] === "$";
1395
+ const eq = (a, b) => a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
1396
+ const isIndex = (t) => t.startsWith("[") && t.endsWith("]");
1397
+ const isNumericKey = (t) => /^[0-9]+$/.test(t);
1398
+ const isNumericSegment = (t) => isNumericKey(t) || (isIndex(t) && /^[0-9]+$/.test(t.slice(1, -1)));
1399
+ const match = (patTokens, pathTokens) => {
1400
+ const memo = new Map();
1401
+ const go = (pi, ti) => {
1402
+ const key = `${pi},${ti}`;
1403
+ const cached = memo.get(key);
1404
+ if (cached !== undefined)
1405
+ return cached;
1406
+ let res = false;
1407
+ if (pi === patTokens.length) {
1408
+ res = ti === pathTokens.length;
1409
+ }
1410
+ else {
1411
+ const p = patTokens[pi];
1412
+ if (p === "**") {
1413
+ // Match any number of segments, including zero.
1414
+ for (let k = ti; k <= pathTokens.length; k++) {
1415
+ if (go(pi + 1, k)) {
1416
+ res = true;
1417
+ break;
1418
+ }
1419
+ }
1420
+ }
1421
+ else if (p === "##") {
1422
+ // Match any number of numeric segments, including zero.
1423
+ for (let k = ti; k <= pathTokens.length; k++) {
1424
+ let ok = true;
1425
+ for (let j = ti; j < k; j++) {
1426
+ if (!isNumericSegment(pathTokens[j])) {
1427
+ ok = false;
1428
+ break;
1429
+ }
1430
+ }
1431
+ if (ok && go(pi + 1, k)) {
1432
+ res = true;
1433
+ break;
1434
+ }
1435
+ }
1436
+ }
1437
+ else if (p === "*") {
1438
+ res = ti < pathTokens.length && go(pi + 1, ti + 1);
1439
+ }
1440
+ else if (p === "#") {
1441
+ res =
1442
+ ti < pathTokens.length &&
1443
+ isNumericSegment(pathTokens[ti]) &&
1444
+ go(pi + 1, ti + 1);
1445
+ }
1446
+ else if (p === "[*]") {
1447
+ res =
1448
+ ti < pathTokens.length &&
1449
+ isIndex(pathTokens[ti]) &&
1450
+ go(pi + 1, ti + 1);
1451
+ }
1452
+ else {
1453
+ res =
1454
+ ti < pathTokens.length &&
1455
+ eq(p.toLowerCase(), pathTokens[ti].toLowerCase()) &&
1456
+ go(pi + 1, ti + 1);
1457
+ }
1458
+ }
1459
+ memo.set(key, res);
1460
+ return res;
1461
+ };
1462
+ return go(0, 0);
1463
+ };
1464
+ return (tokensWithRoot, tokensNoRoot) => match(pat, expectsRoot ? tokensWithRoot : tokensNoRoot);
1465
+ }
1466
+ function stringifySceneAndOps(obj) {
1467
+ return stringifyJson(obj, {
1468
+ indent: 2,
1469
+ paths: [
1470
+ "**.camera.animation.*",
1471
+ "**.animations.*",
1472
+ "**.nodes.#.*",
1473
+ "**.graphs.#.*",
1474
+ "**.instances.#.*",
1475
+ "**.shaders.#.*",
1476
+ "**.materials.#.*",
1477
+ "**.ext.#",
1478
+ "**.ext.*.#.*",
1479
+ "**.perUserExt.*.#",
1480
+ "**.perUserExt.*.*.#",
1481
+ ],
1482
+ criteria: ({ value, key }) => (typeof value === "object" &&
1483
+ value &&
1484
+ Object.keys(value).sort().join(",") === "x,y,z") ||
1485
+ key === "value",
1486
+ });
1487
+ }
1488
+
1034
1489
  /**
1035
1490
  * A reusable logger class that supports configurable log levels and prefixes.
1036
1491
  * Can be instantiated with a default log level and optionally override per call.
@@ -1108,7 +1563,7 @@ class LevelLogger {
1108
1563
  const contextStr = rest && Object.keys(rest).length > 0
1109
1564
  ? `${formatJson ? "\n" : " "}${Object.entries(rest)
1110
1565
  .map(([k, v]) => formatJson
1111
- ? `${k}=${JSON.stringify(v, undefined, 2)}`
1566
+ ? `${k}=${stringifySceneAndOps(v)}`
1112
1567
  : `${k}=${JSON.stringify(v)}`)
1113
1568
  .join(formatJson ? "\n" : " ")}`
1114
1569
  : "";
@@ -1438,6 +1893,40 @@ class RunContextManager {
1438
1893
  }
1439
1894
  }
1440
1895
 
1896
+ /**
1897
+ * Shared utility functions for runtime components
1898
+ */
1899
+ /**
1900
+ * Type guard to check if a value is a Promise
1901
+ */
1902
+ function isPromise(value) {
1903
+ return !!value && typeof value.then === "function";
1904
+ }
1905
+ /**
1906
+ * Unwrap a value that might be a Promise
1907
+ */
1908
+ async function unwrapMaybePromise(value) {
1909
+ return isPromise(value) ? await value : value;
1910
+ }
1911
+ /**
1912
+ * Shallow/deep-ish equality check to avoid unnecessary runs on identical values
1913
+ */
1914
+ function valuesEqual(a, b) {
1915
+ if (a === b)
1916
+ return true;
1917
+ if (typeof a !== typeof b)
1918
+ return false;
1919
+ if (a && b && typeof a === "object") {
1920
+ try {
1921
+ return JSON.stringify(a) === JSON.stringify(b);
1922
+ }
1923
+ catch {
1924
+ return false;
1925
+ }
1926
+ }
1927
+ return false;
1928
+ }
1929
+
1441
1930
  function tryHandleResolving(def, registry, environment) {
1442
1931
  const out = new Map();
1443
1932
  const pending = new Set();
@@ -2154,7 +2643,7 @@ class EdgePropagator {
2154
2643
  // Set input value (respecting skipPropagateValues)
2155
2644
  const shouldSetValue = this.shouldSetInputValue(effectiveRunContexts);
2156
2645
  if (shouldSetValue && valueChanged) {
2157
- this.setTargetInput(edge, dstNode, processedValue);
2646
+ this.setTargetInput(edge, processedValue);
2158
2647
  }
2159
2648
  else if (shouldSetValue && !valueChanged) {
2160
2649
  // Even if value didn't change, update timestamp if we're forcing execution
@@ -2213,7 +2702,7 @@ class EdgePropagator {
2213
2702
  /**
2214
2703
  * Set target input value and emit event
2215
2704
  */
2216
- setTargetInput(edge, dstNode, value) {
2705
+ setTargetInput(edge, value) {
2217
2706
  this.graph.updateNodeInput(edge.target.nodeId, edge.target.handle, value);
2218
2707
  this.handleResolver.scheduleRecomputeHandles(edge.target.nodeId);
2219
2708
  }
@@ -2224,7 +2713,10 @@ class EdgePropagator {
2224
2713
  // Determine if we should propagate
2225
2714
  const shouldPropagate = this.shouldPropagateExecution(effectiveRunContexts);
2226
2715
  if (shouldPropagate && this.graph.allInboundHaveValue(targetNodeId)) {
2227
- this.nodeExecutor.execute(targetNodeId, effectiveRunContexts);
2716
+ this.nodeExecutor.execute(targetNodeId, {
2717
+ runContextIds: effectiveRunContexts,
2718
+ reason: "executeDownstream",
2719
+ });
2228
2720
  }
2229
2721
  }
2230
2722
  /**
@@ -2354,7 +2846,7 @@ class NodeExecutor {
2354
2846
  /**
2355
2847
  * Create an execution context for a node
2356
2848
  */
2357
- createExecutionContext(nodeId, node, inputs, runId, abortSignal, runContextIds, options) {
2849
+ createExecutionContext(nodeId, inputs, runId, abortSignal, runContextIds, options) {
2358
2850
  const emitHandler = options?.emitHandler ??
2359
2851
  ((handle, value) => {
2360
2852
  this.edgePropagator.propagate(nodeId, handle, value, runContextIds);
@@ -2366,8 +2858,9 @@ class NodeExecutor {
2366
2858
  });
2367
2859
  });
2368
2860
  // Create log function that respects node's logLevel using LevelLogger
2369
- const nodeLogLevel = node.logLevel ?? "info";
2370
- const logger = new LevelLogger(nodeLogLevel, `[node:${runId || nodeId}:${node.typeId}]`);
2861
+ const node = this.graph.getNode(nodeId);
2862
+ const nodeLogLevel = node?.logLevel ?? "info";
2863
+ const logger = new LevelLogger(nodeLogLevel, `[node:${runId || nodeId}:${node?.typeId ?? ""}]`);
2371
2864
  const log = (level, message, context) => {
2372
2865
  switch (level) {
2373
2866
  case "debug":
@@ -2386,7 +2879,7 @@ class NodeExecutor {
2386
2879
  };
2387
2880
  return {
2388
2881
  nodeId,
2389
- state: node.state,
2882
+ state: node?.state,
2390
2883
  setState: (next) => this.graph.updateNodeState(nodeId, next),
2391
2884
  emit: emitHandler,
2392
2885
  invalidateDownstream: () => {
@@ -2401,7 +2894,10 @@ class NodeExecutor {
2401
2894
  this.runContextManager.createRunContext(nodeId, opts),
2402
2895
  ]);
2403
2896
  }
2404
- this.execute(nodeId, runContextIdsToUse);
2897
+ this.execute(nodeId, {
2898
+ runContextIds: runContextIdsToUse,
2899
+ reason: opts?.reason ?? "executeFromContext",
2900
+ });
2405
2901
  }
2406
2902
  },
2407
2903
  getInput: (handle) => inputs[handle],
@@ -2413,7 +2909,7 @@ class NodeExecutor {
2413
2909
  this.eventEmitter.emit("stats", {
2414
2910
  kind: "node-custom-data",
2415
2911
  nodeId,
2416
- typeId: node.typeId,
2912
+ typeId: node?.typeId ?? "",
2417
2913
  runId,
2418
2914
  data,
2419
2915
  });
@@ -2424,13 +2920,14 @@ class NodeExecutor {
2424
2920
  /**
2425
2921
  * Internal method for executing inputs changed (also used by GraphRuntime)
2426
2922
  */
2427
- execute(nodeId, runContextIds, canSkipHandleResolution) {
2923
+ execute(nodeId, opts) {
2924
+ let { runContextIds, canSkipHandleResolution, reason = "" } = opts ?? {};
2428
2925
  const node = this.graph.getNode(nodeId);
2429
2926
  if (!node)
2430
2927
  return;
2431
2928
  const runMode = this.runtime.getRunMode();
2432
2929
  if (!runMode) {
2433
- console.trace("NodeExecutor.execute: no runMode, skipping execution");
2930
+ console.trace(`NodeExecutor.execute[${nodeId}:${reason}]: no runMode, skipping execution`);
2434
2931
  return;
2435
2932
  }
2436
2933
  // In manual mode, require runContextIds unless autoRun policy is set
@@ -2442,12 +2939,12 @@ class NodeExecutor {
2442
2939
  ]);
2443
2940
  }
2444
2941
  else {
2445
- console.trace("NodeExecutor.execute: no runContextIds provided in manual mode, skipping execution");
2942
+ console.trace(`NodeExecutor.execute[${nodeId}:${reason}]: no runContextIds provided in manual mode, skipping execution`);
2446
2943
  return;
2447
2944
  }
2448
2945
  }
2449
2946
  if (runMode === "auto" && runContextIds && runContextIds.size > 0) {
2450
- console.trace("NodeExecutor.execute: runContextIds provided in auto mode, ignoring");
2947
+ console.trace(`NodeExecutor.execute[${nodeId}:${reason}]: runContextIds provided in auto mode, ignoring`);
2451
2948
  runContextIds = undefined;
2452
2949
  }
2453
2950
  // Early validation for auto-mode paused state
@@ -2490,7 +2987,11 @@ class NodeExecutor {
2490
2987
  // Re-check node still exists and conditions
2491
2988
  const nodeAfter = this.graph.getNode(nodeId);
2492
2989
  if (nodeAfter) {
2493
- this.execute(nodeId, runContextIds, true);
2990
+ this.execute(nodeId, {
2991
+ runContextIds,
2992
+ canSkipHandleResolution: true,
2993
+ reason: opts?.reason,
2994
+ });
2494
2995
  }
2495
2996
  if (runContextIds && runContextIds.size > 0) {
2496
2997
  for (const id of runContextIds) {
@@ -2502,19 +3003,22 @@ class NodeExecutor {
2502
3003
  }
2503
3004
  // Handle debouncing
2504
3005
  const now = Date.now();
2505
- if (this.shouldDebounce(node, now)) {
2506
- this.handleDebouncedSchedule(node, nodeId, now, runContextIds);
3006
+ if (this.shouldDebounce(nodeId, now)) {
3007
+ this.handleDebouncedSchedule(nodeId, now, runContextIds, reason);
2507
3008
  return;
2508
3009
  }
2509
3010
  // Prepare execution plan
2510
- const executionPlan = this.prepareExecutionPlan(node, nodeId, runContextIds, now);
3011
+ const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
2511
3012
  // Route to appropriate concurrency handler
2512
- this.routeToConcurrencyHandler(node, nodeId, executionPlan);
3013
+ this.routeToConcurrencyHandler(nodeId, executionPlan);
2513
3014
  }
2514
3015
  /**
2515
3016
  * Check if execution should be debounced
2516
3017
  */
2517
- shouldDebounce(node, now) {
3018
+ shouldDebounce(nodeId, now) {
3019
+ const node = this.graph.getNode(nodeId);
3020
+ if (!node)
3021
+ return false;
2518
3022
  const policy = node.policy ?? {};
2519
3023
  const lastScheduledAt = node.lastScheduledAt;
2520
3024
  return !!(policy.debounceMs &&
@@ -2524,80 +3028,78 @@ class NodeExecutor {
2524
3028
  /**
2525
3029
  * Handle debounced scheduling by replacing the latest queued item
2526
3030
  */
2527
- handleDebouncedSchedule(node, nodeId, now, runContextIds) {
3031
+ handleDebouncedSchedule(nodeId, now, runContextIds, reason) {
3032
+ const node = this.graph.getNode(nodeId);
3033
+ if (!node)
3034
+ return;
2528
3035
  // Decrement pendingQueued for any existing queued items before replacing
2529
3036
  if (node.queue.length > 0) {
2530
3037
  this.decrementQueuedForPlans(node.queue, nodeId);
2531
3038
  }
2532
- const effectiveInputs = this.getEffectiveInputs(nodeId);
2533
- const runSeq = this.graph.incrementNodeRunSeq(nodeId);
2534
- const runId = `${nodeId}:${runSeq}:${now}`;
2535
- const policySnapshot = node.policy ? { ...node.policy } : undefined;
2536
- const plan = {
2537
- runId,
2538
- effectiveInputs,
2539
- runContextIdsForRun: runContextIds,
2540
- timestamp: now,
2541
- policy: policySnapshot,
2542
- };
2543
- this.graph.replaceNodeQueue(nodeId, [plan]);
3039
+ const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
3040
+ this.graph.replaceNodeQueue(nodeId, [executionPlan]);
2544
3041
  }
2545
3042
  /**
2546
3043
  * Prepare execution plan with all necessary information
2547
3044
  */
2548
- prepareExecutionPlan(node, nodeId, runContextIds, now) {
3045
+ prepareExecutionPlan(nodeId, runContextIds, now, reason) {
3046
+ const node = this.graph.getNode(nodeId);
2549
3047
  this.graph.setNodeLastScheduledAt(nodeId, now);
2550
3048
  const runSeq = this.graph.incrementNodeRunSeq(nodeId);
2551
3049
  const runId = `${nodeId}:${runSeq}:${now}`;
2552
3050
  this.graph.setNodeLatestRunId(nodeId, runId);
2553
3051
  const effectiveInputs = this.getEffectiveInputs(nodeId);
2554
3052
  // Take a shallow snapshot of the current policy for this run
2555
- const policySnapshot = node.policy ? { ...node.policy } : undefined;
3053
+ const policySnapshot = node?.policy ? { ...node.policy } : undefined;
2556
3054
  return {
2557
3055
  runId,
2558
3056
  effectiveInputs,
2559
3057
  runContextIdsForRun: runContextIds,
2560
3058
  timestamp: now,
2561
3059
  policy: policySnapshot,
3060
+ reason,
2562
3061
  };
2563
3062
  }
2564
3063
  /**
2565
3064
  * Route execution to appropriate concurrency handler
2566
3065
  */
2567
- routeToConcurrencyHandler(node, nodeId, plan) {
3066
+ routeToConcurrencyHandler(nodeId, plan) {
2568
3067
  const mode = plan.policy?.asyncConcurrency ?? "switch";
2569
3068
  switch (mode) {
2570
3069
  case "drop":
2571
- this.handleDropMode(node, nodeId, plan);
3070
+ this.handleDropMode(nodeId, plan);
2572
3071
  break;
2573
3072
  case "queue":
2574
- this.handleQueueMode(node, nodeId, plan);
3073
+ this.handleQueueMode(nodeId, plan);
2575
3074
  break;
2576
3075
  case "switch":
2577
3076
  case "merge":
2578
3077
  default:
2579
- this.startRun(node, nodeId, plan);
3078
+ this.startRun(nodeId, plan);
2580
3079
  break;
2581
3080
  }
2582
3081
  }
2583
3082
  /**
2584
3083
  * Handle drop mode - drop execution if node is already running, otherwise start run
2585
3084
  */
2586
- handleDropMode(node, nodeId, plan) {
3085
+ handleDropMode(nodeId, plan) {
3086
+ const node = this.graph.getNode(nodeId);
3087
+ if (!node)
3088
+ return;
2587
3089
  // Drop if node is already running
2588
3090
  if (node.activeControllers.size > 0) {
2589
3091
  return; // Don't increment pendingCount if we're dropping this run
2590
3092
  }
2591
3093
  // Start run if node is not running
2592
- this.startRun(node, nodeId, plan);
3094
+ this.startRun(nodeId, plan);
2593
3095
  }
2594
3096
  /**
2595
3097
  * Handle queue mode - add to queue and process sequentially
2596
3098
  */
2597
- handleQueueMode(node, nodeId, plan) {
3099
+ handleQueueMode(nodeId, plan) {
2598
3100
  const maxQ = plan.policy?.maxQueue ?? 8;
2599
- const currentNode = this.graph.getNode(nodeId);
2600
- if (!currentNode)
3101
+ const node = this.graph.getNode(nodeId);
3102
+ if (!node)
2601
3103
  return;
2602
3104
  // Keep the originating run-context alive while work is queued by
2603
3105
  // incrementing queued counters for the plan's run-contexts.
@@ -2607,18 +3109,18 @@ class NodeExecutor {
2607
3109
  }
2608
3110
  }
2609
3111
  this.graph.addToNodeQueue(nodeId, plan);
2610
- if (currentNode.queue.length > maxQ) {
3112
+ if (node.queue.length > maxQ) {
2611
3113
  const dropped = this.graph.shiftNodeQueue(nodeId);
2612
3114
  if (dropped) {
2613
3115
  this.decrementQueuedForPlans(dropped, nodeId);
2614
3116
  }
2615
3117
  }
2616
- this.processQueue(node, nodeId);
3118
+ this.processQueue(nodeId);
2617
3119
  }
2618
3120
  /**
2619
3121
  * Process queued executions sequentially
2620
3122
  */
2621
- processQueue(node, nodeId) {
3123
+ processQueue(nodeId) {
2622
3124
  const processNext = () => {
2623
3125
  const node = this.graph.getNode(nodeId);
2624
3126
  if (!node)
@@ -2631,7 +3133,7 @@ class NodeExecutor {
2631
3133
  this.graph.setNodeLatestRunId(nodeId, next.runId);
2632
3134
  // Start the run first (which increments pendingNodes), then decrement
2633
3135
  // pendingQueued to ensure the run context stays alive.
2634
- this.startRun(node, nodeId, next, () => {
3136
+ this.startRun(nodeId, next, () => {
2635
3137
  setTimeout(processNext, 0);
2636
3138
  });
2637
3139
  this.decrementQueuedForPlans(next, nodeId);
@@ -2641,19 +3143,19 @@ class NodeExecutor {
2641
3143
  /**
2642
3144
  * Start a node execution run
2643
3145
  */
2644
- startRun(node, nodeId, plan, onDone) {
3146
+ startRun(nodeId, plan, onDone) {
2645
3147
  // Track run-contexts
2646
3148
  this.trackRunContextStart(nodeId, plan.runContextIdsForRun);
2647
3149
  // Setup execution controller
2648
- const controller = this.createExecutionController(nodeId, node, plan.runId);
3150
+ const controller = this.createExecutionController(nodeId, plan.runId);
2649
3151
  // Handle concurrency mode
2650
- this.applyConcurrencyMode(nodeId, node, controller, plan);
3152
+ this.applyConcurrencyMode(nodeId, controller, plan);
2651
3153
  // Setup timeout if needed
2652
- const timeoutId = this.setupTimeout(node, controller, plan);
3154
+ const timeoutId = this.setupTimeout(controller, plan);
2653
3155
  // Create execution context
2654
- const execCtx = this.createExecutionContext(nodeId, node, plan.effectiveInputs, plan.runId, controller.signal, plan.runContextIdsForRun, this.createEmitAndProgressHandlers(node, nodeId, plan));
3156
+ const execCtx = this.createExecutionContext(nodeId, plan.effectiveInputs, plan.runId, controller.signal, plan.runContextIdsForRun, this.createEmitAndProgressHandlers(nodeId, plan));
2655
3157
  // Execute
2656
- this.executeNode(node, nodeId, execCtx, plan, controller, timeoutId, onDone);
3158
+ this.executeNode(nodeId, execCtx, plan, controller, timeoutId, onDone);
2657
3159
  }
2658
3160
  /**
2659
3161
  * Track run-context start for pending nodes
@@ -2682,12 +3184,13 @@ class NodeExecutor {
2682
3184
  /**
2683
3185
  * Create execution controller and update node stats
2684
3186
  */
2685
- createExecutionController(nodeId, node, runId) {
3187
+ createExecutionController(nodeId, runId) {
2686
3188
  const controller = new AbortController();
2687
3189
  const now = Date.now();
3190
+ const node = this.graph.getNode(nodeId);
2688
3191
  this.graph.updateNodeStats(nodeId, {
2689
- runs: node.stats.runs + 1,
2690
- active: node.stats.active + 1,
3192
+ runs: (node?.stats.runs ?? 0) + 1,
3193
+ active: (node?.stats.active ?? 0) + 1,
2691
3194
  lastStartAt: now,
2692
3195
  progress: 0,
2693
3196
  });
@@ -2697,7 +3200,7 @@ class NodeExecutor {
2697
3200
  /**
2698
3201
  * Apply concurrency mode (switch mode aborts other controllers)
2699
3202
  */
2700
- applyConcurrencyMode(nodeId, node, controller, plan) {
3203
+ applyConcurrencyMode(nodeId, controller, plan) {
2701
3204
  const mode = plan.policy?.asyncConcurrency ?? "switch";
2702
3205
  if (mode === "switch") {
2703
3206
  const controllers = this.graph.getNodeControllers(nodeId);
@@ -2710,7 +3213,7 @@ class NodeExecutor {
2710
3213
  /**
2711
3214
  * Setup timeout for execution if configured
2712
3215
  */
2713
- setupTimeout(node, controller, plan) {
3216
+ setupTimeout(controller, plan) {
2714
3217
  const policy = plan.policy ?? {};
2715
3218
  if (policy.timeoutMs && policy.timeoutMs > 0) {
2716
3219
  return setTimeout(() => controller.abort("timeout"), policy.timeoutMs);
@@ -2720,7 +3223,7 @@ class NodeExecutor {
2720
3223
  /**
2721
3224
  * Create emit and progress handlers for execution context
2722
3225
  */
2723
- createEmitAndProgressHandlers(node, nodeId, plan) {
3226
+ createEmitAndProgressHandlers(nodeId, plan) {
2724
3227
  const policy = plan.policy ?? {};
2725
3228
  return {
2726
3229
  emitHandler: (handle, value) => {
@@ -2755,7 +3258,10 @@ class NodeExecutor {
2755
3258
  /**
2756
3259
  * Execute the node with retry logic and cleanup
2757
3260
  */
2758
- executeNode(node, nodeId, ctx, plan, controller, timeoutId, onDone) {
3261
+ executeNode(nodeId, ctx, plan, controller, timeoutId, onDone) {
3262
+ const node = this.graph.getNode(nodeId);
3263
+ if (!node)
3264
+ return;
2759
3265
  // Fire node-start event
2760
3266
  this.eventEmitter.emit("stats", {
2761
3267
  kind: "node-start",
@@ -2763,7 +3269,11 @@ class NodeExecutor {
2763
3269
  typeId: node.typeId,
2764
3270
  runId: plan.runId,
2765
3271
  });
2766
- ctx.log("debug", "node-start");
3272
+ ctx.log("debug", "node-start", {
3273
+ inputs: node.inputs,
3274
+ effectiveInputs: plan.effectiveInputs,
3275
+ reason: plan.reason,
3276
+ });
2767
3277
  const exec = async (attempt) => {
2768
3278
  let hadError = false;
2769
3279
  try {
@@ -2801,7 +3311,7 @@ class NodeExecutor {
2801
3311
  });
2802
3312
  }
2803
3313
  finally {
2804
- this.cleanupExecution(node, nodeId, ctx, plan, controller, timeoutId, hadError, onDone);
3314
+ this.cleanupExecution(nodeId, ctx, plan, controller, timeoutId, hadError, onDone);
2805
3315
  }
2806
3316
  };
2807
3317
  exec(0);
@@ -2809,7 +3319,7 @@ class NodeExecutor {
2809
3319
  /**
2810
3320
  * Cleanup after execution completes
2811
3321
  */
2812
- cleanupExecution(node, nodeId, ctx, plan, controller, timeoutId, hadError, onDone) {
3322
+ cleanupExecution(nodeId, ctx, plan, controller, timeoutId, hadError, onDone) {
2813
3323
  // Decrement pendingNodes count for all relevant run-contexts
2814
3324
  if (plan.runContextIdsForRun && plan.runContextIdsForRun.size > 0) {
2815
3325
  for (const id of plan.runContextIdsForRun) {
@@ -2823,19 +3333,19 @@ class NodeExecutor {
2823
3333
  if (timeoutId)
2824
3334
  clearTimeout(timeoutId);
2825
3335
  this.graph.removeNodeController(nodeId, controller);
2826
- const currentNode = this.graph.getNode(nodeId);
2827
- if (!currentNode)
3336
+ const node = this.graph.getNode(nodeId);
3337
+ if (!node)
2828
3338
  return;
2829
3339
  const controllers = this.graph.getNodeControllers(nodeId);
2830
3340
  const lastEndAt = Date.now();
2831
- const lastDurationMs = currentNode.stats.lastStartAt && lastEndAt
2832
- ? lastEndAt - currentNode.stats.lastStartAt
3341
+ const lastDurationMs = node.stats.lastStartAt && lastEndAt
3342
+ ? lastEndAt - node.stats.lastStartAt
2833
3343
  : undefined;
2834
3344
  this.graph.updateNodeStats(nodeId, {
2835
3345
  active: Math.max(0, controllers.size),
2836
3346
  lastEndAt,
2837
3347
  lastDurationMs,
2838
- lastError: hadError ? currentNode.stats.lastError : undefined,
3348
+ lastError: hadError ? node.stats.lastError : undefined,
2839
3349
  });
2840
3350
  // Track successful completion time (for detecting stale inputs)
2841
3351
  const isCancelled = controller.signal.aborted &&
@@ -2847,26 +3357,22 @@ class NodeExecutor {
2847
3357
  }
2848
3358
  // Only emit node-done if not cancelled (cancellation events emitted separately)
2849
3359
  if (!isCancelled) {
2850
- if (currentNode) {
3360
+ if (node) {
2851
3361
  this.eventEmitter.emit("stats", {
2852
3362
  kind: "node-done",
2853
3363
  nodeId,
2854
- typeId: currentNode.typeId,
3364
+ typeId: node.typeId,
2855
3365
  runId: plan.runId,
2856
- durationMs: currentNode.stats.lastDurationMs,
3366
+ durationMs: node.stats.lastDurationMs,
2857
3367
  });
2858
3368
  }
2859
3369
  }
2860
- if (currentNode) {
3370
+ if (node) {
2861
3371
  ctx.log("debug", "node-done", {
2862
- durationMs: currentNode.stats.lastDurationMs,
3372
+ durationMs: node.stats.lastDurationMs,
3373
+ outputs: node.outputs,
2863
3374
  hadError,
2864
- inputs: currentNode.inputs
2865
- ? structuredClone(currentNode.inputs)
2866
- : undefined,
2867
- outputs: currentNode.outputs
2868
- ? structuredClone(currentNode.outputs)
2869
- : undefined,
3375
+ reason: plan.reason,
2870
3376
  });
2871
3377
  }
2872
3378
  if (onDone)
@@ -2875,14 +3381,13 @@ class NodeExecutor {
2875
3381
  /**
2876
3382
  * Cancel all active runs for a node
2877
3383
  */
2878
- cancelNodeActiveRuns(node, reason) {
2879
- const nodeId = node.nodeId;
3384
+ cancelNodeActiveRuns(nodeId, reason) {
3385
+ const node = this.graph.getNode(nodeId);
3386
+ if (!node)
3387
+ return;
2880
3388
  const controllers = this.graph.getNodeControllers(nodeId);
2881
3389
  for (const controller of controllers) {
2882
- const currentNode = this.graph.getNode(nodeId);
2883
- if (!currentNode)
2884
- continue;
2885
- const runId = currentNode.controllerRunIds.get(controller);
3390
+ const runId = node.controllerRunIds.get(controller);
2886
3391
  if (runId) {
2887
3392
  // Track cancelled runIds for snapshot and user-cancelled operations
2888
3393
  // (to drop emits from cancelled runs)
@@ -2892,8 +3397,8 @@ class NodeExecutor {
2892
3397
  // Emit cancellation event
2893
3398
  this.eventEmitter.emit("stats", {
2894
3399
  kind: "node-done",
2895
- nodeId: currentNode.nodeId,
2896
- typeId: currentNode.typeId,
3400
+ nodeId: nodeId,
3401
+ typeId: node.typeId,
2897
3402
  runId,
2898
3403
  cancelled: true,
2899
3404
  });
@@ -2941,10 +3446,7 @@ class NodeExecutor {
2941
3446
  }
2942
3447
  // Cancel runs for all affected nodes
2943
3448
  for (const nodeId of toCancel) {
2944
- const node = this.graph.getNode(nodeId);
2945
- if (!node)
2946
- continue;
2947
- this.cancelNodeActiveRuns(node, reason);
3449
+ this.cancelNodeActiveRuns(nodeId, reason);
2948
3450
  const runSeq = this.graph.incrementNodeRunSeq(nodeId);
2949
3451
  const now = Date.now();
2950
3452
  const suffix = reason === "snapshot" ? "snapshot" : "cancelled";
@@ -3184,7 +3686,7 @@ class GraphRuntime {
3184
3686
  // However, if autoRun policy is set, nodes run automatically even in manual mode.
3185
3687
  if (anyChanged) {
3186
3688
  this.handleResolver.scheduleRecomputeHandles(nodeId);
3187
- this.executeNodeAutoRun(nodeId);
3689
+ this.executeNodeAutoRun(nodeId, { reason: "setInputs" });
3188
3690
  }
3189
3691
  }
3190
3692
  getOutput(nodeId, output) {
@@ -3195,7 +3697,7 @@ class GraphRuntime {
3195
3697
  this.graph.forEachNode((node) => {
3196
3698
  const effectiveInputs = this.nodeExecutor.getEffectiveInputs(node.nodeId);
3197
3699
  const ctrl = new AbortController();
3198
- const execCtx = this.nodeExecutor.createExecutionContext(node.nodeId, node, effectiveInputs, `${node.nodeId}:init`, ctrl.signal);
3700
+ const execCtx = this.nodeExecutor.createExecutionContext(node.nodeId, effectiveInputs, `${node.nodeId}:init`, ctrl.signal);
3199
3701
  if (node.lifecycle?.prepare) {
3200
3702
  execCtx.log("debug", "prepare-start");
3201
3703
  node.lifecycle.prepare(node.params ?? {}, execCtx);
@@ -3206,7 +3708,7 @@ class GraphRuntime {
3206
3708
  if (this.runMode === "auto" && invalidate) {
3207
3709
  for (const nodeId of this.graph.getNodeIds()) {
3208
3710
  if (this.graph.allInboundHaveValue(nodeId))
3209
- this.execute(nodeId);
3711
+ this.execute(nodeId, { reason: "launch" });
3210
3712
  }
3211
3713
  }
3212
3714
  if (startPaused) {
@@ -3221,7 +3723,7 @@ class GraphRuntime {
3221
3723
  if (this.isInvalidateEvent(event)) {
3222
3724
  // Check if node has all inbound inputs (required for execution)
3223
3725
  if (this.graph.allInboundHaveValue(nodeId)) {
3224
- this.execute(nodeId);
3726
+ this.execute(nodeId, { reason: "triggerExternal" });
3225
3727
  }
3226
3728
  return;
3227
3729
  }
@@ -3345,17 +3847,20 @@ class GraphRuntime {
3345
3847
  setTimeout(check, 10);
3346
3848
  });
3347
3849
  }
3348
- async runFromHereContext(startNodeId, options) {
3850
+ async runFromHereContext(startNodeId, opts) {
3349
3851
  const node = this.graph.getNode(startNodeId);
3350
3852
  if (!node)
3351
3853
  return;
3352
3854
  return new Promise((resolve) => {
3353
3855
  const id = this.runContextManager.createRunContext(startNodeId, {
3354
3856
  resolve,
3355
- ...options,
3857
+ ...opts,
3356
3858
  });
3357
3859
  this.graph.addNodeRunContextId(startNodeId, id);
3358
- this.execute(startNodeId, new Set([id]));
3860
+ this.execute(startNodeId, {
3861
+ runContextIds: new Set([id]),
3862
+ reason: opts?.reason ?? "runFromHereContext",
3863
+ });
3359
3864
  });
3360
3865
  }
3361
3866
  setRunMode(runMode) {
@@ -3402,7 +3907,7 @@ class GraphRuntime {
3402
3907
  }
3403
3908
  }
3404
3909
  }
3405
- executeNodeAutoRun(nodeId) {
3910
+ executeNodeAutoRun(nodeId, opts) {
3406
3911
  const node = this.graph.getNode(nodeId);
3407
3912
  const shouldAutoRun = this.runMode === "auto" || node?.policy?.autoRun === true;
3408
3913
  let runContextIdsToUse = undefined;
@@ -3412,7 +3917,10 @@ class GraphRuntime {
3412
3917
  ]);
3413
3918
  }
3414
3919
  if (shouldAutoRun && this.graph.allInboundHaveValue(nodeId)) {
3415
- this.execute(nodeId, runContextIdsToUse);
3920
+ this.execute(nodeId, {
3921
+ runContextIds: runContextIdsToUse,
3922
+ reason: opts?.reason ?? "executeNodeAutoRun",
3923
+ });
3416
3924
  }
3417
3925
  }
3418
3926
  copyOutputs(fromNodeId, toNodeId, options) {
@@ -3421,7 +3929,7 @@ class GraphRuntime {
3421
3929
  return;
3422
3930
  this.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { invalidate: !options?.dry });
3423
3931
  this.handleResolver.scheduleRecomputeHandles(toNodeId);
3424
- this.executeNodeAutoRun(toNodeId);
3932
+ this.executeNodeAutoRun(toNodeId, { reason: "copyOutputs" });
3425
3933
  }
3426
3934
  hydrate(payload, opts) {
3427
3935
  const releasePause = this.requestPause();
@@ -3479,7 +3987,7 @@ class GraphRuntime {
3479
3987
  const node = this.graph.getNode(nodeId);
3480
3988
  if (!node)
3481
3989
  continue;
3482
- this.nodeExecutor.cancelNodeActiveRuns(node, "node-deleted");
3990
+ this.nodeExecutor.cancelNodeActiveRuns(nodeId, "node-deleted");
3483
3991
  this.runContextManager.cancelNodeInRunContexts(nodeId, true);
3484
3992
  node.runtime.onDeactivated?.();
3485
3993
  node.runtime.dispose?.();
@@ -3540,7 +4048,7 @@ class GraphRuntime {
3540
4048
  this.graph.setNode(n.nodeId, newNode);
3541
4049
  const effectiveInputs = this.nodeExecutor.getEffectiveInputs(newNode.nodeId);
3542
4050
  const ctrl = new AbortController();
3543
- const execCtx = this.nodeExecutor.createExecutionContext(newNode.nodeId, newNode, effectiveInputs, `${newNode.nodeId}:init`, ctrl.signal);
4051
+ const execCtx = this.nodeExecutor.createExecutionContext(newNode.nodeId, effectiveInputs, `${newNode.nodeId}:init`, ctrl.signal);
3544
4052
  if (newNode.lifecycle?.prepare) {
3545
4053
  execCtx.log("debug", "prepare-start");
3546
4054
  newNode.lifecycle.prepare(newNode.params ?? {}, execCtx);
@@ -3651,7 +4159,7 @@ class GraphRuntime {
3651
4159
  this.edgePropagator.clearArrayBuckets(nodeId);
3652
4160
  // Trigger handle resolution when inputs are removed
3653
4161
  this.handleResolver.scheduleRecomputeHandles(nodeId);
3654
- this.executeNodeAutoRun(nodeId);
4162
+ this.executeNodeAutoRun(nodeId, { reason: "graphUpdate" });
3655
4163
  }
3656
4164
  }
3657
4165
  // Propagate changes on edges added
@@ -3721,8 +4229,8 @@ class GraphRuntime {
3721
4229
  });
3722
4230
  this.graph.clear();
3723
4231
  }
3724
- execute(nodeId, runContextIds, canSkipHandleResolution) {
3725
- this.nodeExecutor.execute(nodeId, runContextIds, canSkipHandleResolution);
4232
+ execute(nodeId, opts) {
4233
+ this.nodeExecutor.execute(nodeId, opts);
3726
4234
  }
3727
4235
  propagate(srcNodeId, srcHandle, value, runContextIds) {
3728
4236
  this.edgePropagator.propagate(srcNodeId, srcHandle, value, runContextIds);
@@ -4085,6 +4593,7 @@ class LocalEngine {
4085
4593
  await this.graphRuntime.runFromHereContext(nodeId, {
4086
4594
  skipPropagateValues: options?.skipPropagateValues ?? false,
4087
4595
  propagate: false, // Don't schedule downstream nodes
4596
+ reason: "computeNode",
4088
4597
  });
4089
4598
  }
4090
4599
  /**
@@ -4093,7 +4602,9 @@ class LocalEngine {
4093
4602
  * Uses run-context system for dynamic graph updates.
4094
4603
  */
4095
4604
  async runFromHere(nodeId) {
4096
- await this.graphRuntime.runFromHereContext(nodeId);
4605
+ await this.graphRuntime.runFromHereContext(nodeId, {
4606
+ reason: "runFromHere",
4607
+ });
4097
4608
  }
4098
4609
  setRunMode(runMode) {
4099
4610
  this.graphRuntime.setRunMode(runMode);
@@ -5531,235 +6042,6 @@ function createValidationGraphRegistry(id) {
5531
6042
  return registry;
5532
6043
  }
5533
6044
 
5534
- function parseJsonPath(path) {
5535
- if (typeof path === "string") {
5536
- return path.split(".").flatMap((segment) => {
5537
- const arrayMatch = segment.match(/^(.+)\[(\d+)\]$/);
5538
- if (arrayMatch) {
5539
- const index = parseInt(arrayMatch[2], 10);
5540
- return arrayMatch[1] ? [arrayMatch[1], index] : [index];
5541
- }
5542
- return [segment];
5543
- });
5544
- }
5545
- return path;
5546
- }
5547
- function getValueAtPath(obj, pathSegments) {
5548
- if (pathSegments.length === 0) {
5549
- return { value: obj, parent: null, key: "" };
5550
- }
5551
- let current = obj;
5552
- for (let i = 0; i < pathSegments.length - 1; i++) {
5553
- const segment = pathSegments[i];
5554
- if (current === null ||
5555
- current === undefined ||
5556
- typeof current !== "object") {
5557
- return null;
5558
- }
5559
- if (typeof segment === "string") {
5560
- if (Array.isArray(current)) {
5561
- const index = parseInt(segment, 10);
5562
- if (isNaN(index))
5563
- return null;
5564
- current = current[index];
5565
- }
5566
- else {
5567
- current = current[segment];
5568
- }
5569
- }
5570
- else if (typeof segment === "number") {
5571
- if (Array.isArray(current)) {
5572
- if (segment >= 0 && segment < current.length) {
5573
- current = current[segment];
5574
- }
5575
- else {
5576
- return null;
5577
- }
5578
- }
5579
- else {
5580
- return null;
5581
- }
5582
- }
5583
- else if (segment instanceof RegExp) {
5584
- if (Array.isArray(current)) {
5585
- return null;
5586
- }
5587
- const obj = current;
5588
- const matchingKey = Object.keys(obj).find((key) => segment.test(key));
5589
- if (!matchingKey)
5590
- return null;
5591
- current = obj[matchingKey];
5592
- }
5593
- else {
5594
- return null;
5595
- }
5596
- }
5597
- const lastSegment = pathSegments[pathSegments.length - 1];
5598
- if (typeof lastSegment === "string") {
5599
- if (Array.isArray(current)) {
5600
- const index = parseInt(lastSegment, 10);
5601
- if (isNaN(index))
5602
- return null;
5603
- return { value: current[index], parent: current, key: index };
5604
- }
5605
- else if (current !== null &&
5606
- current !== undefined &&
5607
- typeof current === "object") {
5608
- return {
5609
- value: current[lastSegment],
5610
- parent: current,
5611
- key: lastSegment,
5612
- };
5613
- }
5614
- }
5615
- else if (typeof lastSegment === "number") {
5616
- if (Array.isArray(current)) {
5617
- if (lastSegment >= 0 && lastSegment < current.length) {
5618
- return {
5619
- value: current[lastSegment],
5620
- parent: current,
5621
- key: lastSegment,
5622
- };
5623
- }
5624
- }
5625
- return null;
5626
- }
5627
- else if (lastSegment instanceof RegExp) {
5628
- if (Array.isArray(current)) {
5629
- return null;
5630
- }
5631
- const obj = current;
5632
- const matchingKey = Object.keys(obj).find((key) => lastSegment.test(key));
5633
- if (!matchingKey)
5634
- return null;
5635
- return { value: obj[matchingKey], parent: current, key: matchingKey };
5636
- }
5637
- return null;
5638
- }
5639
- function setValueAtPath(obj, pathSegments, newValue) {
5640
- const result = getValueAtPath(obj, pathSegments);
5641
- if (!result || result.parent === null)
5642
- return false;
5643
- if (Array.isArray(result.parent)) {
5644
- result.parent[result.key] = newValue;
5645
- }
5646
- else {
5647
- result.parent[result.key] = newValue;
5648
- }
5649
- return true;
5650
- }
5651
- /**
5652
- * Sets a value at a path, creating intermediate objects as needed.
5653
- * Mutates the root object in place.
5654
- * @param root - The root object to modify (must be an object, will be initialized if needed)
5655
- * @param pathSegments - The path segments to traverse
5656
- * @param value - The value to set, or null to delete the path
5657
- * @throws Error if path cannot be created (e.g., array indices not supported, invalid parent types)
5658
- */
5659
- function setValueAtPathWithCreation(root, pathSegments, value) {
5660
- if (value === null) {
5661
- const result = getValueAtPath(root, pathSegments);
5662
- if (result && result.parent !== null && !Array.isArray(result.parent)) {
5663
- delete result.parent[result.key];
5664
- }
5665
- return;
5666
- }
5667
- if (!root || typeof root !== "object" || Array.isArray(root)) {
5668
- throw new Error("Root must be an object");
5669
- }
5670
- let current = root;
5671
- for (let i = 0; i < pathSegments.length - 1; i++) {
5672
- const segment = pathSegments[i];
5673
- if (typeof segment === "string") {
5674
- if (!current ||
5675
- typeof current !== "object" ||
5676
- Array.isArray(current) ||
5677
- !(segment in current) ||
5678
- typeof current[segment] !== "object" ||
5679
- current[segment] === null ||
5680
- Array.isArray(current[segment])) {
5681
- if (!current || typeof current !== "object" || Array.isArray(current)) {
5682
- throw new Error(`Cannot create path: parent at segment ${i} is not an object`);
5683
- }
5684
- current[segment] = {};
5685
- }
5686
- current = current[segment];
5687
- }
5688
- else {
5689
- throw new Error("Array indices not supported in extData paths");
5690
- }
5691
- }
5692
- const lastSegment = pathSegments[pathSegments.length - 1];
5693
- if (typeof lastSegment === "string") {
5694
- if (!current || typeof current !== "object" || Array.isArray(current)) {
5695
- throw new Error(`Cannot set value: parent at final segment is not an object`);
5696
- }
5697
- current[lastSegment] = value;
5698
- }
5699
- else {
5700
- throw new Error("Array indices not supported in extData paths");
5701
- }
5702
- }
5703
- function findMatchingPaths(obj, pathSegments, currentPath = []) {
5704
- if (pathSegments.length === 0) {
5705
- return [{ path: currentPath, value: obj }];
5706
- }
5707
- const [currentSegment, ...remainingSegments] = pathSegments;
5708
- const results = [];
5709
- if (currentSegment === undefined) {
5710
- return results;
5711
- }
5712
- if (typeof currentSegment === "string") {
5713
- if (Array.isArray(obj)) {
5714
- const index = parseInt(currentSegment, 10);
5715
- if (!isNaN(index) && index >= 0 && index < obj.length) {
5716
- results.push(...findMatchingPaths(obj[index], remainingSegments, [
5717
- ...currentPath,
5718
- index,
5719
- ]));
5720
- }
5721
- }
5722
- else if (obj !== null && obj !== undefined && typeof obj === "object") {
5723
- const objRecord = obj;
5724
- if (currentSegment in objRecord) {
5725
- results.push(...findMatchingPaths(objRecord[currentSegment], remainingSegments, [
5726
- ...currentPath,
5727
- currentSegment,
5728
- ]));
5729
- }
5730
- }
5731
- }
5732
- else if (typeof currentSegment === "number") {
5733
- if (Array.isArray(obj)) {
5734
- if (currentSegment >= 0 && currentSegment < obj.length) {
5735
- results.push(...findMatchingPaths(obj[currentSegment], remainingSegments, [
5736
- ...currentPath,
5737
- currentSegment,
5738
- ]));
5739
- }
5740
- }
5741
- }
5742
- else if (currentSegment instanceof RegExp) {
5743
- if (Array.isArray(obj)) {
5744
- for (let i = 0; i < obj.length; i++) {
5745
- results.push(...findMatchingPaths(obj[i], remainingSegments, [...currentPath, i]));
5746
- }
5747
- }
5748
- else if (obj !== null && obj !== undefined && typeof obj === "object") {
5749
- const objRecord = obj;
5750
- for (const key of Object.keys(objRecord)) {
5751
- if (currentSegment.test(key)) {
5752
- results.push(...findMatchingPaths(objRecord[key], remainingSegments, [
5753
- ...currentPath,
5754
- key,
5755
- ]));
5756
- }
5757
- }
5758
- }
5759
- }
5760
- return results;
5761
- }
5762
-
5763
6045
  function mergeGraphDefinitions(target, source) {
5764
6046
  const existingNodeIds = new Set(target.nodes.map((n) => n.nodeId));
5765
6047
  const existingEdgeIds = new Set(target.edges.map((e) => e.id));
@@ -6398,6 +6680,8 @@ exports.registerDelayNode = registerDelayNode;
6398
6680
  exports.registerProgressNodes = registerProgressNodes;
6399
6681
  exports.setValueAtPath = setValueAtPath;
6400
6682
  exports.setValueAtPathWithCreation = setValueAtPathWithCreation;
6683
+ exports.stringifyJson = stringifyJson;
6684
+ exports.stringifySceneAndOps = stringifySceneAndOps;
6401
6685
  exports.typed = typed;
6402
6686
  exports.unwrapTypeId = unwrapTypeId;
6403
6687
  exports.unwrapValue = unwrapValue;