@bian-womp/spark-graph 0.3.16 → 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 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();
@@ -2019,33 +2216,23 @@ class NodeExecutor {
2019
2216
  progress: Math.max(0, Math.min(1, Number(p) || 0)),
2020
2217
  });
2021
2218
  });
2022
- // 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}]`);
2023
2222
  const log = (level, message, context) => {
2024
- const nodeLogLevel = node.logLevel ?? "info";
2025
- const nodeLogValue = LOG_LEVEL_VALUES[nodeLogLevel] ?? 1;
2026
- const requestedValue = LOG_LEVEL_VALUES[level] ?? 1;
2027
- // Only log if requested level >= node's logLevel
2028
- if (requestedValue >= nodeLogValue && nodeLogLevel !== "silent") {
2029
- const contextStr = context
2030
- ? ` ${Object.entries(context)
2031
- .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
2032
- .join(" ")}`
2033
- : "";
2034
- const fullMessage = `[node:${runId || nodeId}:${node.typeId}] ${message}${contextStr}`;
2035
- switch (level) {
2036
- case "debug":
2037
- console.info(fullMessage);
2038
- break;
2039
- case "info":
2040
- console.info(fullMessage);
2041
- break;
2042
- case "warn":
2043
- console.warn(fullMessage);
2044
- break;
2045
- case "error":
2046
- console.error(fullMessage);
2047
- break;
2048
- }
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;
2049
2236
  }
2050
2237
  };
2051
2238
  return {
@@ -2574,7 +2761,7 @@ class GraphRuntime {
2574
2761
  // Initialize components
2575
2762
  this.graph = new Graph();
2576
2763
  this.eventEmitter = new EventEmitter();
2577
- this.runContextManager = new RunContextManager(this.graph);
2764
+ this.runContextManager = new RunContextManager(this.graph, "debug");
2578
2765
  this.handleResolver = new HandleResolver(this.graph, this.eventEmitter, this.runContextManager, this);
2579
2766
  this.edgePropagator = new EdgePropagator(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this);
2580
2767
  // Create NodeExecutor with EdgePropagator and HandleResolver
@@ -5709,6 +5896,7 @@ exports.CompositeCategory = CompositeCategory;
5709
5896
  exports.ComputeCategory = ComputeCategory;
5710
5897
  exports.GraphBuilder = GraphBuilder;
5711
5898
  exports.GraphRuntime = GraphRuntime;
5899
+ exports.LevelLogger = LevelLogger;
5712
5900
  exports.LocalEngine = LocalEngine;
5713
5901
  exports.Registry = Registry;
5714
5902
  exports.buildValueConverter = buildValueConverter;