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