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