@bian-womp/spark-graph 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/index.cjs +415 -92
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/cjs/src/index.d.ts +1 -0
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts +15 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +3 -2
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts +7 -1
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/interfaces.d.ts +3 -0
- package/lib/cjs/src/runtime/components/interfaces.d.ts.map +1 -1
- package/lib/cjs/src/runtime/utils.d.ts +51 -0
- package/lib/cjs/src/runtime/utils.d.ts.map +1 -1
- package/lib/esm/index.js +415 -93
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/esm/src/index.d.ts +1 -0
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts +15 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts +3 -2
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/RunContextManager.d.ts +7 -1
- package/lib/esm/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/interfaces.d.ts +3 -0
- package/lib/esm/src/runtime/components/interfaces.d.ts.map +1 -1
- package/lib/esm/src/runtime/utils.d.ts +51 -0
- package/lib/esm/src/runtime/utils.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -932,15 +932,166 @@ class EventEmitter {
|
|
|
932
932
|
}
|
|
933
933
|
}
|
|
934
934
|
|
|
935
|
+
const LOG_LEVEL_VALUES = {
|
|
936
|
+
debug: 0,
|
|
937
|
+
info: 1,
|
|
938
|
+
warn: 2,
|
|
939
|
+
error: 3,
|
|
940
|
+
silent: 4,
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Shared utility functions for runtime components
|
|
945
|
+
*/
|
|
946
|
+
/**
|
|
947
|
+
* Type guard to check if a value is a Promise
|
|
948
|
+
*/
|
|
949
|
+
function isPromise(value) {
|
|
950
|
+
return !!value && typeof value.then === "function";
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Unwrap a value that might be a Promise
|
|
954
|
+
*/
|
|
955
|
+
async function unwrapMaybePromise(value) {
|
|
956
|
+
return isPromise(value) ? await value : value;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Shallow/deep-ish equality check to avoid unnecessary runs on identical values
|
|
960
|
+
*/
|
|
961
|
+
function valuesEqual(a, b) {
|
|
962
|
+
if (a === b)
|
|
963
|
+
return true;
|
|
964
|
+
if (typeof a !== typeof b)
|
|
965
|
+
return false;
|
|
966
|
+
if (a && b && typeof a === "object") {
|
|
967
|
+
try {
|
|
968
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
969
|
+
}
|
|
970
|
+
catch {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* A reusable logger class that supports configurable log levels and prefixes.
|
|
978
|
+
* Can be instantiated with a default log level and optionally override per call.
|
|
979
|
+
*/
|
|
980
|
+
class LevelLogger {
|
|
981
|
+
constructor(defaultLevel = "info", prefix = "") {
|
|
982
|
+
this.defaultLevel = defaultLevel;
|
|
983
|
+
this.prefix = prefix;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Sets the prefix for log messages
|
|
987
|
+
*/
|
|
988
|
+
setPrefix(prefix) {
|
|
989
|
+
this.prefix = prefix;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Gets the current prefix
|
|
993
|
+
*/
|
|
994
|
+
getPrefix() {
|
|
995
|
+
return this.prefix;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Sets the default log level for this logger instance
|
|
999
|
+
*/
|
|
1000
|
+
setLevel(level) {
|
|
1001
|
+
this.defaultLevel = level;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Gets the current default log level
|
|
1005
|
+
*/
|
|
1006
|
+
getLevel() {
|
|
1007
|
+
return this.defaultLevel;
|
|
1008
|
+
}
|
|
1009
|
+
getLevelValue() {
|
|
1010
|
+
return LevelLogger.levelValues[this.defaultLevel];
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Logs a debug message
|
|
1014
|
+
*/
|
|
1015
|
+
debug(message, context, overrideLevel) {
|
|
1016
|
+
this.log("debug", message, context, overrideLevel);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Logs an info message
|
|
1020
|
+
*/
|
|
1021
|
+
info(message, context, overrideLevel) {
|
|
1022
|
+
this.log("info", message, context, overrideLevel);
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Logs a warning message
|
|
1026
|
+
*/
|
|
1027
|
+
warn(message, context, overrideLevel) {
|
|
1028
|
+
this.log("warn", message, context, overrideLevel);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Logs an error message
|
|
1032
|
+
*/
|
|
1033
|
+
error(message, context, overrideLevel) {
|
|
1034
|
+
this.log("error", message, context, overrideLevel);
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Core logging method that respects the log level and applies prefix
|
|
1038
|
+
*/
|
|
1039
|
+
log(requestedLevel, message, context, overrideLevel) {
|
|
1040
|
+
const effectiveLevel = overrideLevel ?? this.defaultLevel;
|
|
1041
|
+
// Silent level suppresses all logs
|
|
1042
|
+
if (effectiveLevel === "silent") {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const requestedValue = LevelLogger.levelValues[requestedLevel] ?? 1;
|
|
1046
|
+
const effectiveValue = LevelLogger.levelValues[effectiveLevel] ?? 1;
|
|
1047
|
+
// Only log if the requested level is >= effective level
|
|
1048
|
+
if (requestedValue >= effectiveValue) {
|
|
1049
|
+
const contextStr = context
|
|
1050
|
+
? ` ${Object.entries(context)
|
|
1051
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
1052
|
+
.join(" ")}`
|
|
1053
|
+
: "";
|
|
1054
|
+
const prefixedMessage = this.prefix
|
|
1055
|
+
? `${this.prefix} ${message}${contextStr}`
|
|
1056
|
+
: `${message}${contextStr}`;
|
|
1057
|
+
switch (requestedLevel) {
|
|
1058
|
+
case "debug":
|
|
1059
|
+
console.info(prefixedMessage);
|
|
1060
|
+
break;
|
|
1061
|
+
case "info":
|
|
1062
|
+
console.info(prefixedMessage);
|
|
1063
|
+
break;
|
|
1064
|
+
case "warn":
|
|
1065
|
+
console.warn(prefixedMessage);
|
|
1066
|
+
break;
|
|
1067
|
+
case "error":
|
|
1068
|
+
console.error(prefixedMessage);
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Maps log levels to numeric values for comparison
|
|
1076
|
+
*/
|
|
1077
|
+
LevelLogger.levelValues = LOG_LEVEL_VALUES;
|
|
1078
|
+
|
|
935
1079
|
/**
|
|
936
1080
|
* RunContextManager component - manages run-context lifecycle
|
|
937
1081
|
*/
|
|
938
1082
|
class RunContextManager {
|
|
939
|
-
constructor(graph) {
|
|
1083
|
+
constructor(graph, logLevel) {
|
|
940
1084
|
this.graph = graph;
|
|
941
1085
|
this.runContexts = new Map();
|
|
942
1086
|
this.runContextCounter = 0;
|
|
943
1087
|
this.graph = graph;
|
|
1088
|
+
this.logger = new LevelLogger(logLevel ?? "info", "[RunContextManager]");
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Set the log level for this manager
|
|
1092
|
+
*/
|
|
1093
|
+
setLogLevel(logLevel) {
|
|
1094
|
+
this.logger.setLevel(logLevel);
|
|
944
1095
|
}
|
|
945
1096
|
/**
|
|
946
1097
|
* Create a new run-context for runFromHere
|
|
@@ -959,6 +1110,12 @@ class RunContextManager {
|
|
|
959
1110
|
resolve,
|
|
960
1111
|
};
|
|
961
1112
|
this.runContexts.set(id, ctx);
|
|
1113
|
+
this.logger.info("create-run-context", {
|
|
1114
|
+
id,
|
|
1115
|
+
startNodeId,
|
|
1116
|
+
skipPropagateValues: ctx.skipPropagateValues,
|
|
1117
|
+
propagate: ctx.propagate,
|
|
1118
|
+
});
|
|
962
1119
|
return id;
|
|
963
1120
|
}
|
|
964
1121
|
/**
|
|
@@ -981,41 +1138,101 @@ class RunContextManager {
|
|
|
981
1138
|
}
|
|
982
1139
|
startNodeRun(id, nodeId) {
|
|
983
1140
|
const ctx = this.runContexts.get(id);
|
|
984
|
-
if (!ctx)
|
|
1141
|
+
if (!ctx) {
|
|
1142
|
+
this.logger.debug("start-node-run-context-not-found", {
|
|
1143
|
+
runContextId: id,
|
|
1144
|
+
nodeId,
|
|
1145
|
+
});
|
|
985
1146
|
return;
|
|
1147
|
+
}
|
|
986
1148
|
ctx.pendingNodes++;
|
|
1149
|
+
this.logger.debug("start-node-run", {
|
|
1150
|
+
runContextId: id,
|
|
1151
|
+
nodeId,
|
|
1152
|
+
pendingNodes: ctx.pendingNodes,
|
|
1153
|
+
});
|
|
987
1154
|
}
|
|
988
1155
|
finishNodeRun(id, nodeId) {
|
|
989
1156
|
const ctx = this.runContexts.get(id);
|
|
990
|
-
if (!ctx)
|
|
1157
|
+
if (!ctx) {
|
|
1158
|
+
this.logger.debug("finish-node-run-context-not-found", {
|
|
1159
|
+
runContextId: id,
|
|
1160
|
+
nodeId,
|
|
1161
|
+
});
|
|
991
1162
|
return;
|
|
1163
|
+
}
|
|
992
1164
|
ctx.pendingNodes--;
|
|
1165
|
+
this.logger.debug("finish-node-run", {
|
|
1166
|
+
runContextId: id,
|
|
1167
|
+
nodeId,
|
|
1168
|
+
pendingNodes: ctx.pendingNodes,
|
|
1169
|
+
});
|
|
993
1170
|
this.finishRunContextIfPossible(id);
|
|
994
1171
|
}
|
|
995
1172
|
startEdgeConversion(id, edgeId) {
|
|
996
1173
|
const ctx = this.runContexts.get(id);
|
|
997
|
-
if (!ctx)
|
|
1174
|
+
if (!ctx) {
|
|
1175
|
+
this.logger.debug("start-edge-conversion-context-not-found", {
|
|
1176
|
+
runContextId: id,
|
|
1177
|
+
edgeId,
|
|
1178
|
+
});
|
|
998
1179
|
return;
|
|
1180
|
+
}
|
|
999
1181
|
ctx.pendingEdges++;
|
|
1182
|
+
this.logger.debug("start-edge-conversion", {
|
|
1183
|
+
runContextId: id,
|
|
1184
|
+
edgeId,
|
|
1185
|
+
pendingEdges: ctx.pendingEdges,
|
|
1186
|
+
});
|
|
1000
1187
|
}
|
|
1001
1188
|
finishEdgeConversion(id, edgeId) {
|
|
1002
1189
|
const ctx = this.runContexts.get(id);
|
|
1003
|
-
if (!ctx)
|
|
1190
|
+
if (!ctx) {
|
|
1191
|
+
this.logger.debug("finish-edge-conversion-context-not-found", {
|
|
1192
|
+
runContextId: id,
|
|
1193
|
+
edgeId,
|
|
1194
|
+
});
|
|
1004
1195
|
return;
|
|
1196
|
+
}
|
|
1005
1197
|
ctx.pendingEdges--;
|
|
1198
|
+
this.logger.debug("finish-edge-conversion", {
|
|
1199
|
+
runContextId: id,
|
|
1200
|
+
edgeId,
|
|
1201
|
+
pendingEdges: ctx.pendingEdges,
|
|
1202
|
+
});
|
|
1006
1203
|
this.finishRunContextIfPossible(id);
|
|
1007
1204
|
}
|
|
1008
1205
|
startHandleResolution(id, nodeId) {
|
|
1009
1206
|
const ctx = this.runContexts.get(id);
|
|
1010
|
-
if (!ctx)
|
|
1207
|
+
if (!ctx) {
|
|
1208
|
+
this.logger.debug("start-handle-resolution-context-not-found", {
|
|
1209
|
+
runContextId: id,
|
|
1210
|
+
nodeId,
|
|
1211
|
+
});
|
|
1011
1212
|
return;
|
|
1213
|
+
}
|
|
1012
1214
|
ctx.pendingResolvers++;
|
|
1215
|
+
this.logger.debug("start-handle-resolution", {
|
|
1216
|
+
runContextId: id,
|
|
1217
|
+
nodeId,
|
|
1218
|
+
pendingResolvers: ctx.pendingResolvers,
|
|
1219
|
+
});
|
|
1013
1220
|
}
|
|
1014
1221
|
finishHandleResolution(id, nodeId) {
|
|
1015
1222
|
const ctx = this.runContexts.get(id);
|
|
1016
|
-
if (!ctx)
|
|
1223
|
+
if (!ctx) {
|
|
1224
|
+
this.logger.debug("finish-handle-resolution-context-not-found", {
|
|
1225
|
+
runContextId: id,
|
|
1226
|
+
nodeId,
|
|
1227
|
+
});
|
|
1017
1228
|
return;
|
|
1229
|
+
}
|
|
1018
1230
|
ctx.pendingResolvers--;
|
|
1231
|
+
this.logger.debug("finish-handle-resolution", {
|
|
1232
|
+
runContextId: id,
|
|
1233
|
+
nodeId,
|
|
1234
|
+
pendingResolvers: ctx.pendingResolvers,
|
|
1235
|
+
});
|
|
1019
1236
|
this.finishRunContextIfPossible(id);
|
|
1020
1237
|
}
|
|
1021
1238
|
/**
|
|
@@ -1023,13 +1240,22 @@ class RunContextManager {
|
|
|
1023
1240
|
*/
|
|
1024
1241
|
finishRunContextIfPossible(id) {
|
|
1025
1242
|
const ctx = this.runContexts.get(id);
|
|
1026
|
-
if (!ctx)
|
|
1243
|
+
if (!ctx) {
|
|
1244
|
+
this.logger.debug("finish-run-context-not-found", {
|
|
1245
|
+
runContextId: id,
|
|
1246
|
+
});
|
|
1027
1247
|
return;
|
|
1248
|
+
}
|
|
1028
1249
|
if (ctx.pendingNodes > 0 ||
|
|
1029
1250
|
ctx.pendingEdges > 0 ||
|
|
1030
1251
|
ctx.pendingResolvers > 0) {
|
|
1031
1252
|
return; // Still has pending work
|
|
1032
1253
|
}
|
|
1254
|
+
this.logger.info("finish-run-context", {
|
|
1255
|
+
runContextId: id,
|
|
1256
|
+
startNodes: Array.from(ctx.startNodes),
|
|
1257
|
+
cancelledNodes: Array.from(ctx.cancelledNodes),
|
|
1258
|
+
});
|
|
1033
1259
|
// Clean up activeRunContexts from all nodes
|
|
1034
1260
|
this.graph.forEachNode((node) => {
|
|
1035
1261
|
this.graph.removeNodeRunContextId(node.nodeId, id);
|
|
@@ -1067,6 +1293,12 @@ class RunContextManager {
|
|
|
1067
1293
|
});
|
|
1068
1294
|
}
|
|
1069
1295
|
}
|
|
1296
|
+
this.logger.debug("cancel-node-in-run-contexts", {
|
|
1297
|
+
nodeId,
|
|
1298
|
+
includeDownstream,
|
|
1299
|
+
cancelledNodes: Array.from(toCancel),
|
|
1300
|
+
affectedRunContexts: Array.from(this.runContexts.keys()),
|
|
1301
|
+
});
|
|
1070
1302
|
// Mark nodes as cancelled in all run-contexts
|
|
1071
1303
|
for (const ctx of this.runContexts.values()) {
|
|
1072
1304
|
for (const id of toCancel) {
|
|
@@ -1082,6 +1314,11 @@ class RunContextManager {
|
|
|
1082
1314
|
* Resolve all pending run-context promises (for cleanup)
|
|
1083
1315
|
*/
|
|
1084
1316
|
resolveAll() {
|
|
1317
|
+
const count = this.runContexts.size;
|
|
1318
|
+
this.logger.info("resolve-all-run-contexts", {
|
|
1319
|
+
count,
|
|
1320
|
+
runContextIds: Array.from(this.runContexts.keys()),
|
|
1321
|
+
});
|
|
1085
1322
|
for (const ctx of this.runContexts.values()) {
|
|
1086
1323
|
if (ctx.resolve)
|
|
1087
1324
|
ctx.resolve();
|
|
@@ -1091,52 +1328,12 @@ class RunContextManager {
|
|
|
1091
1328
|
* Clear all run-contexts
|
|
1092
1329
|
*/
|
|
1093
1330
|
clear() {
|
|
1331
|
+
const count = this.runContexts.size;
|
|
1332
|
+
this.logger.info("clear-all-run-contexts", { count });
|
|
1094
1333
|
this.runContexts.clear();
|
|
1095
1334
|
}
|
|
1096
1335
|
}
|
|
1097
1336
|
|
|
1098
|
-
const LOG_LEVEL_VALUES = {
|
|
1099
|
-
debug: 0,
|
|
1100
|
-
info: 1,
|
|
1101
|
-
warn: 2,
|
|
1102
|
-
error: 3,
|
|
1103
|
-
silent: 4,
|
|
1104
|
-
};
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Shared utility functions for runtime components
|
|
1108
|
-
*/
|
|
1109
|
-
/**
|
|
1110
|
-
* Type guard to check if a value is a Promise
|
|
1111
|
-
*/
|
|
1112
|
-
function isPromise(value) {
|
|
1113
|
-
return !!value && typeof value.then === "function";
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Unwrap a value that might be a Promise
|
|
1117
|
-
*/
|
|
1118
|
-
async function unwrapMaybePromise(value) {
|
|
1119
|
-
return isPromise(value) ? await value : value;
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Shallow/deep-ish equality check to avoid unnecessary runs on identical values
|
|
1123
|
-
*/
|
|
1124
|
-
function valuesEqual(a, b) {
|
|
1125
|
-
if (a === b)
|
|
1126
|
-
return true;
|
|
1127
|
-
if (typeof a !== typeof b)
|
|
1128
|
-
return false;
|
|
1129
|
-
if (a && b && typeof a === "object") {
|
|
1130
|
-
try {
|
|
1131
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
1132
|
-
}
|
|
1133
|
-
catch {
|
|
1134
|
-
return false;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
1337
|
function tryHandleResolving(def, registry, environment) {
|
|
1141
1338
|
const out = new Map();
|
|
1142
1339
|
const pending = new Set();
|
|
@@ -1334,6 +1531,8 @@ class HandleResolver {
|
|
|
1334
1531
|
this.registry = registry;
|
|
1335
1532
|
this.recomputeTokenByNode = new Map();
|
|
1336
1533
|
this.environment = {};
|
|
1534
|
+
this.pendingResolutions = new Map();
|
|
1535
|
+
this.pendingResolutionRunContexts = new Map();
|
|
1337
1536
|
this.environment = environment ?? {};
|
|
1338
1537
|
}
|
|
1339
1538
|
setRegistry(registry) {
|
|
@@ -1342,6 +1541,33 @@ class HandleResolver {
|
|
|
1342
1541
|
setEnvironment(environment) {
|
|
1343
1542
|
this.environment = environment;
|
|
1344
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Check if handle resolution is pending for a node
|
|
1546
|
+
*/
|
|
1547
|
+
isResolvingHandles(nodeId) {
|
|
1548
|
+
return this.pendingResolutions.has(nodeId);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Get the promise for pending handle resolution, or null if none
|
|
1552
|
+
*/
|
|
1553
|
+
getPendingResolution(nodeId) {
|
|
1554
|
+
return this.pendingResolutions.get(nodeId) || null;
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Track additional run contexts for a pending resolution
|
|
1558
|
+
*/
|
|
1559
|
+
trackRunContextsForPendingResolution(nodeId, runContextIds) {
|
|
1560
|
+
if (!this.pendingResolutions.has(nodeId))
|
|
1561
|
+
return;
|
|
1562
|
+
const tracked = this.pendingResolutionRunContexts.get(nodeId) ?? new Set();
|
|
1563
|
+
for (const runContextId of runContextIds) {
|
|
1564
|
+
if (!tracked.has(runContextId)) {
|
|
1565
|
+
this.runContextManager.startHandleResolution(runContextId, nodeId);
|
|
1566
|
+
tracked.add(runContextId);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
this.pendingResolutionRunContexts.set(nodeId, tracked);
|
|
1570
|
+
}
|
|
1345
1571
|
/**
|
|
1346
1572
|
* Schedule async recomputation of handles for a node
|
|
1347
1573
|
*/
|
|
@@ -1354,14 +1580,25 @@ class HandleResolver {
|
|
|
1354
1580
|
return;
|
|
1355
1581
|
// Track resolver start for all active run-contexts
|
|
1356
1582
|
const activeRunContextIds = this.graph.getNodeRunContextIds(nodeId);
|
|
1583
|
+
const trackedRunContextIds = new Set(activeRunContextIds);
|
|
1357
1584
|
if (activeRunContextIds.size > 0) {
|
|
1358
1585
|
for (const runContextId of activeRunContextIds) {
|
|
1359
1586
|
this.runContextManager.startHandleResolution(runContextId, nodeId);
|
|
1360
1587
|
}
|
|
1361
1588
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1589
|
+
// Create and track the resolution promise
|
|
1590
|
+
const resolutionPromise = new Promise((resolve) => {
|
|
1591
|
+
setTimeout(async () => {
|
|
1592
|
+
// Get all tracked run contexts (including any added during pending state)
|
|
1593
|
+
const allTracked = this.pendingResolutionRunContexts.get(nodeId) ?? trackedRunContextIds;
|
|
1594
|
+
await this.recomputeHandlesForNode(nodeId, allTracked.size > 0 ? allTracked : undefined);
|
|
1595
|
+
this.pendingResolutions.delete(nodeId);
|
|
1596
|
+
this.pendingResolutionRunContexts.delete(nodeId);
|
|
1597
|
+
resolve();
|
|
1598
|
+
}, 0);
|
|
1599
|
+
});
|
|
1600
|
+
this.pendingResolutions.set(nodeId, resolutionPromise);
|
|
1601
|
+
this.pendingResolutionRunContexts.set(nodeId, trackedRunContextIds);
|
|
1365
1602
|
}
|
|
1366
1603
|
// Update resolved handles for a single node and refresh edge converters/types that touch it
|
|
1367
1604
|
updateNodeHandles(nodeId, handles) {
|
|
@@ -1906,10 +2143,11 @@ class EdgePropagator {
|
|
|
1906
2143
|
* NodeExecutor component - handles node execution scheduling and lifecycle
|
|
1907
2144
|
*/
|
|
1908
2145
|
class NodeExecutor {
|
|
1909
|
-
constructor(graph, eventEmitter, runContextManager, edgePropagator, runtime, environment) {
|
|
2146
|
+
constructor(graph, eventEmitter, runContextManager, handleResolver, edgePropagator, runtime, environment) {
|
|
1910
2147
|
this.graph = graph;
|
|
1911
2148
|
this.eventEmitter = eventEmitter;
|
|
1912
2149
|
this.runContextManager = runContextManager;
|
|
2150
|
+
this.handleResolver = handleResolver;
|
|
1913
2151
|
this.edgePropagator = edgePropagator;
|
|
1914
2152
|
this.runtime = runtime;
|
|
1915
2153
|
this.environment = {};
|
|
@@ -1978,33 +2216,23 @@ class NodeExecutor {
|
|
|
1978
2216
|
progress: Math.max(0, Math.min(1, Number(p) || 0)),
|
|
1979
2217
|
});
|
|
1980
2218
|
});
|
|
1981
|
-
// Create log function that respects node's logLevel
|
|
2219
|
+
// Create log function that respects node's logLevel using LevelLogger
|
|
2220
|
+
const nodeLogLevel = node.logLevel ?? "info";
|
|
2221
|
+
const logger = new LevelLogger(nodeLogLevel, `[node:${runId || nodeId}:${node.typeId}]`);
|
|
1982
2222
|
const log = (level, message, context) => {
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
console.info(fullMessage);
|
|
1997
|
-
break;
|
|
1998
|
-
case "info":
|
|
1999
|
-
console.info(fullMessage);
|
|
2000
|
-
break;
|
|
2001
|
-
case "warn":
|
|
2002
|
-
console.warn(fullMessage);
|
|
2003
|
-
break;
|
|
2004
|
-
case "error":
|
|
2005
|
-
console.error(fullMessage);
|
|
2006
|
-
break;
|
|
2007
|
-
}
|
|
2223
|
+
switch (level) {
|
|
2224
|
+
case "debug":
|
|
2225
|
+
logger.debug(message, context);
|
|
2226
|
+
break;
|
|
2227
|
+
case "info":
|
|
2228
|
+
logger.info(message, context);
|
|
2229
|
+
break;
|
|
2230
|
+
case "warn":
|
|
2231
|
+
logger.warn(message, context);
|
|
2232
|
+
break;
|
|
2233
|
+
case "error":
|
|
2234
|
+
logger.error(message, context);
|
|
2235
|
+
break;
|
|
2008
2236
|
}
|
|
2009
2237
|
};
|
|
2010
2238
|
return {
|
|
@@ -2069,10 +2297,31 @@ class NodeExecutor {
|
|
|
2069
2297
|
// Early validation for auto-mode paused state
|
|
2070
2298
|
if (this.runtime.isPaused())
|
|
2071
2299
|
return;
|
|
2072
|
-
// Attach run-context IDs if provided
|
|
2300
|
+
// Attach run-context IDs if provided - do this BEFORE checking for pending resolution
|
|
2301
|
+
// so that handle resolution can track these run contexts
|
|
2073
2302
|
if (runContextIds) {
|
|
2074
2303
|
this.graph.addNodeRunContextIds(nodeId, runContextIds);
|
|
2075
2304
|
}
|
|
2305
|
+
// Check if handles are being resolved - wait for resolution before executing
|
|
2306
|
+
// Do this AFTER setting up run contexts so handle resolution can track them
|
|
2307
|
+
if (this.handleResolver && this.handleResolver.isResolvingHandles(nodeId)) {
|
|
2308
|
+
// Track run contexts for the pending resolution
|
|
2309
|
+
if (runContextIds && runContextIds.size > 0) {
|
|
2310
|
+
this.handleResolver.trackRunContextsForPendingResolution(nodeId, runContextIds);
|
|
2311
|
+
}
|
|
2312
|
+
const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
|
|
2313
|
+
if (pendingResolution) {
|
|
2314
|
+
// Wait for resolution to complete, then re-execute
|
|
2315
|
+
pendingResolution.then(() => {
|
|
2316
|
+
// Re-check node still exists and conditions
|
|
2317
|
+
const nodeAfter = this.graph.getNode(nodeId);
|
|
2318
|
+
if (nodeAfter) {
|
|
2319
|
+
this.execute(nodeId, runContextIds);
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2076
2325
|
// Handle debouncing
|
|
2077
2326
|
const now = Date.now();
|
|
2078
2327
|
if (this.shouldDebounce(nodeId, node, now)) {
|
|
@@ -2512,11 +2761,11 @@ class GraphRuntime {
|
|
|
2512
2761
|
// Initialize components
|
|
2513
2762
|
this.graph = new Graph();
|
|
2514
2763
|
this.eventEmitter = new EventEmitter();
|
|
2515
|
-
this.runContextManager = new RunContextManager(this.graph);
|
|
2764
|
+
this.runContextManager = new RunContextManager(this.graph, "debug");
|
|
2516
2765
|
this.handleResolver = new HandleResolver(this.graph, this.eventEmitter, this.runContextManager, this);
|
|
2517
2766
|
this.edgePropagator = new EdgePropagator(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this);
|
|
2518
|
-
// Create NodeExecutor with EdgePropagator
|
|
2519
|
-
this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this, this);
|
|
2767
|
+
// Create NodeExecutor with EdgePropagator and HandleResolver
|
|
2768
|
+
this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this, this);
|
|
2520
2769
|
}
|
|
2521
2770
|
static create(def, registry, opts) {
|
|
2522
2771
|
const gr = new GraphRuntime();
|
|
@@ -2829,19 +3078,30 @@ class GraphRuntime {
|
|
|
2829
3078
|
const releasePause = this.requestPause();
|
|
2830
3079
|
try {
|
|
2831
3080
|
const ins = payload?.inputs || {};
|
|
3081
|
+
const nodesWithChangedInputs = new Set();
|
|
2832
3082
|
for (const [nodeId, map] of Object.entries(ins)) {
|
|
2833
3083
|
if (!this.graph.hasNode(nodeId))
|
|
2834
3084
|
continue;
|
|
3085
|
+
let nodeChanged = false;
|
|
2835
3086
|
for (const [h, v] of Object.entries(map || {})) {
|
|
3087
|
+
const node = this.graph.getNode(nodeId);
|
|
3088
|
+
const prev = node?.inputs[h];
|
|
2836
3089
|
const clonedValue = structuredClone(v);
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
nodeId,
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
3090
|
+
const same = valuesEqual(prev, clonedValue);
|
|
3091
|
+
if (!same) {
|
|
3092
|
+
this.graph.updateNodeInput(nodeId, h, clonedValue);
|
|
3093
|
+
this.eventEmitter.emit("value", {
|
|
3094
|
+
nodeId,
|
|
3095
|
+
handle: h,
|
|
3096
|
+
value: clonedValue,
|
|
3097
|
+
io: "input",
|
|
3098
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
3099
|
+
});
|
|
3100
|
+
nodeChanged = true;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
if (nodeChanged) {
|
|
3104
|
+
nodesWithChangedInputs.add(nodeId);
|
|
2845
3105
|
}
|
|
2846
3106
|
}
|
|
2847
3107
|
const outs = payload?.outputs || {};
|
|
@@ -2860,6 +3120,10 @@ class GraphRuntime {
|
|
|
2860
3120
|
});
|
|
2861
3121
|
}
|
|
2862
3122
|
}
|
|
3123
|
+
// Trigger handle resolution for nodes with changed inputs
|
|
3124
|
+
for (const nodeId of nodesWithChangedInputs) {
|
|
3125
|
+
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
3126
|
+
}
|
|
2863
3127
|
if (opts?.invalidate) {
|
|
2864
3128
|
for (const nodeId of this.graph.getNodeIds()) {
|
|
2865
3129
|
this.invalidateDownstream(nodeId);
|
|
@@ -3042,6 +3306,8 @@ class GraphRuntime {
|
|
|
3042
3306
|
}
|
|
3043
3307
|
if (changed) {
|
|
3044
3308
|
this.edgePropagator.clearArrayBuckets(nodeId);
|
|
3309
|
+
// Trigger handle resolution when inputs are removed
|
|
3310
|
+
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
3045
3311
|
if (this.runMode === "auto" &&
|
|
3046
3312
|
this.graph.allInboundHaveValue(nodeId)) {
|
|
3047
3313
|
this.execute(nodeId);
|
|
@@ -3162,6 +3428,17 @@ class GraphBuilder {
|
|
|
3162
3428
|
const arr = Array.isArray(from) ? from : [from];
|
|
3163
3429
|
return arr.every((s) => s === to || !!this.registry.canCoerce(s, to));
|
|
3164
3430
|
};
|
|
3431
|
+
// Helper to validate enum value
|
|
3432
|
+
const validateEnumValue = (typeId, value, nodeId, handle) => {
|
|
3433
|
+
if (!typeId.startsWith("enum:"))
|
|
3434
|
+
return true; // Not an enum type
|
|
3435
|
+
const enumDef = this.registry.enums.get(typeId);
|
|
3436
|
+
if (!enumDef)
|
|
3437
|
+
return true; // Enum not registered, skip validation
|
|
3438
|
+
if (typeof value !== "number")
|
|
3439
|
+
return false; // Enum values must be numbers
|
|
3440
|
+
return enumDef.valueToLabel.has(value);
|
|
3441
|
+
};
|
|
3165
3442
|
const pushIssue = (level, code, message, data) => {
|
|
3166
3443
|
issues.push({ level, code, message, data });
|
|
3167
3444
|
};
|
|
@@ -3185,6 +3462,51 @@ class GraphBuilder {
|
|
|
3185
3462
|
if (!this.registry.categories.has(nodeType.categoryId)) {
|
|
3186
3463
|
pushIssue("error", "CATEGORY_MISSING", `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`);
|
|
3187
3464
|
}
|
|
3465
|
+
// Validate enum values in node params
|
|
3466
|
+
if (n.params) {
|
|
3467
|
+
const effectiveHandles = getEffectiveHandles(n);
|
|
3468
|
+
for (const [paramKey, paramValue] of Object.entries(n.params)) {
|
|
3469
|
+
// Skip policy and other non-input params
|
|
3470
|
+
if (paramKey === "policy")
|
|
3471
|
+
continue;
|
|
3472
|
+
// Check if this param corresponds to an input handle
|
|
3473
|
+
const inputTypeId = getInputTypeId(effectiveHandles.inputs, paramKey);
|
|
3474
|
+
if (inputTypeId && inputTypeId.startsWith("enum:")) {
|
|
3475
|
+
if (!validateEnumValue(inputTypeId, paramValue, n.nodeId)) {
|
|
3476
|
+
const enumDef = this.registry.enums.get(inputTypeId);
|
|
3477
|
+
const validValues = enumDef
|
|
3478
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3479
|
+
: "unknown";
|
|
3480
|
+
pushIssue("error", "ENUM_VALUE_INVALID", `Node ${n.nodeId} param ${paramKey} has invalid enum value ${paramValue}. Valid values: ${validValues}`, {
|
|
3481
|
+
nodeId: n.nodeId,
|
|
3482
|
+
input: paramKey,
|
|
3483
|
+
typeId: inputTypeId,
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
// Validate enum values in input defaults
|
|
3490
|
+
const resolved = n.resolvedHandles;
|
|
3491
|
+
if (resolved?.inputDefaults) {
|
|
3492
|
+
const effectiveHandles = getEffectiveHandles(n);
|
|
3493
|
+
for (const [handle, defaultValue] of Object.entries(resolved.inputDefaults)) {
|
|
3494
|
+
const inputTypeId = getInputTypeId(effectiveHandles.inputs, handle);
|
|
3495
|
+
if (inputTypeId && inputTypeId.startsWith("enum:")) {
|
|
3496
|
+
if (!validateEnumValue(inputTypeId, defaultValue, n.nodeId)) {
|
|
3497
|
+
const enumDef = this.registry.enums.get(inputTypeId);
|
|
3498
|
+
const validValues = enumDef
|
|
3499
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3500
|
+
: "unknown";
|
|
3501
|
+
pushIssue("warning", "ENUM_DEFAULT_INVALID", `Node ${n.nodeId} input default ${handle} has invalid enum value ${defaultValue}. Valid values: ${validValues}`, {
|
|
3502
|
+
nodeId: n.nodeId,
|
|
3503
|
+
input: handle,
|
|
3504
|
+
typeId: inputTypeId,
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3188
3510
|
}
|
|
3189
3511
|
// edges validation: nodes exist, handles exist, type exists
|
|
3190
3512
|
const inboundCounts = new Map();
|
|
@@ -5574,6 +5896,7 @@ exports.CompositeCategory = CompositeCategory;
|
|
|
5574
5896
|
exports.ComputeCategory = ComputeCategory;
|
|
5575
5897
|
exports.GraphBuilder = GraphBuilder;
|
|
5576
5898
|
exports.GraphRuntime = GraphRuntime;
|
|
5899
|
+
exports.LevelLogger = LevelLogger;
|
|
5577
5900
|
exports.LocalEngine = LocalEngine;
|
|
5578
5901
|
exports.Registry = Registry;
|
|
5579
5902
|
exports.buildValueConverter = buildValueConverter;
|