@bian-womp/spark-graph 0.3.82 → 0.3.83

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
@@ -1438,9 +1438,52 @@ function stringifySceneAndOps(obj) {
1438
1438
  /**
1439
1439
  * A reusable logger class that supports configurable log levels and prefixes.
1440
1440
  * Can be instantiated with a default log level and optionally override per call.
1441
+ *
1442
+ * Dynamic log-level rules:
1443
+ * - Rules are configured once via `LevelLogger.setDynamicLevelRules(raw)`.
1444
+ * - Rules can also be configured per logger instance via `setInstanceDynamicLevelRules(raw)`.
1445
+ * - Rules are compiled into a static runtime-friendly structure for fast matching.
1446
+ * - On every log call, logger resolves dynamic level by rule match:
1447
+ * `overrideLevel` > instance rule > global rule > logger default.
1448
+ *
1449
+ * Rule format (raw):
1450
+ * [
1451
+ * {
1452
+ * "name": "optional label",
1453
+ * "enabled": true,
1454
+ * "setLevel": "debug",
1455
+ * "when": {
1456
+ * "prefix": { "startsWith": ["[node:scene.importAsset"] },
1457
+ * "message": { "startsWith": ["computeAssetSyncStatus"] }
1458
+ * }
1459
+ * }
1460
+ * ]
1461
+ *
1462
+ * Matching semantics:
1463
+ * - Rules are evaluated in order; first match wins.
1464
+ * - `prefix` and `message` conditions are AND-ed when both exist.
1465
+ * - Within each operator list (`startsWith`, `contains`, etc.), values are OR-ed.
1466
+ * - If multiple operators are defined in one block, all operators must pass.
1467
+ * - `not` negates an additional matcher block.
1468
+ * - String matching is case-insensitive; regex is used as provided.
1469
+ *
1470
+ * Priority strategy (fixed):
1471
+ * - `overrideLevel` > first matching instance rule > first matching global rule > logger default.
1441
1472
  */
1442
1473
  class LevelLogger {
1474
+ /**
1475
+ * Updates static dynamic level rules from raw settings input.
1476
+ * Accepts either:
1477
+ * - a JSON string containing an array of rules
1478
+ * - an in-memory array of rules
1479
+ *
1480
+ * Invalid rules are ignored; parse errors clear rules and warn.
1481
+ */
1482
+ static setDynamicLevelRules(raw) {
1483
+ LevelLogger.compiledDynamicLevelRules = LevelLogger.compileDynamicLevelRulesFromRaw(raw);
1484
+ }
1443
1485
  constructor(defaultLevel = "info", prefix = "") {
1486
+ this.compiledInstanceDynamicLevelRules = [];
1444
1487
  this.defaultLevel = defaultLevel;
1445
1488
  this.prefix = prefix;
1446
1489
  }
@@ -1471,29 +1514,179 @@ class LevelLogger {
1471
1514
  getLevelValue() {
1472
1515
  return LevelLogger.levelValues[this.defaultLevel];
1473
1516
  }
1517
+ /**
1518
+ * Sets per-instance dynamic rules. These are evaluated along with global rules
1519
+ * using the active priority preset.
1520
+ */
1521
+ setInstanceDynamicLevelRules(raw) {
1522
+ this.compiledInstanceDynamicLevelRules = LevelLogger.compileDynamicLevelRulesFromRaw(raw);
1523
+ }
1524
+ static isRecord(value) {
1525
+ return value !== null && typeof value === "object";
1526
+ }
1527
+ static isLogLevel(value) {
1528
+ return typeof value === "string" && value in LevelLogger.levelValues;
1529
+ }
1530
+ static normalizeStringArray(input) {
1531
+ if (typeof input === "string")
1532
+ return [input];
1533
+ if (!Array.isArray(input))
1534
+ return [];
1535
+ return input.filter((v) => typeof v === "string");
1536
+ }
1537
+ static compileRegexArray(input) {
1538
+ const patterns = LevelLogger.normalizeStringArray(input);
1539
+ const compiled = [];
1540
+ for (const pattern of patterns) {
1541
+ try {
1542
+ compiled.push(new RegExp(pattern));
1543
+ }
1544
+ catch (err) {
1545
+ console.warn(`[LevelLogger] Invalid regex in dynamic level rule: ${pattern}`, err);
1546
+ }
1547
+ }
1548
+ return compiled;
1549
+ }
1550
+ static compileMatchRule(input) {
1551
+ if (!LevelLogger.isRecord(input))
1552
+ return undefined;
1553
+ const equals = LevelLogger.normalizeStringArray(input.equals).map((v) => v.toLowerCase());
1554
+ const startsWith = LevelLogger.normalizeStringArray(input.startsWith).map((v) => v.toLowerCase());
1555
+ const contains = LevelLogger.normalizeStringArray(input.contains).map((v) => v.toLowerCase());
1556
+ const regex = LevelLogger.compileRegexArray(input.regex);
1557
+ let not;
1558
+ if (LevelLogger.isRecord(input.not)) {
1559
+ not = {
1560
+ equals: LevelLogger.normalizeStringArray(input.not.equals).map((v) => v.toLowerCase()),
1561
+ startsWith: LevelLogger.normalizeStringArray(input.not.startsWith).map((v) => v.toLowerCase()),
1562
+ contains: LevelLogger.normalizeStringArray(input.not.contains).map((v) => v.toLowerCase()),
1563
+ regex: LevelLogger.compileRegexArray(input.not.regex),
1564
+ };
1565
+ }
1566
+ if (!equals.length && !startsWith.length && !contains.length && !regex.length && !not)
1567
+ return undefined;
1568
+ return { equals, startsWith, contains, regex, not };
1569
+ }
1570
+ static compileDynamicLevelRules(raw) {
1571
+ if (!Array.isArray(raw)) {
1572
+ console.warn("[LevelLogger] Dynamic level rules must be an array. Ignoring value.");
1573
+ return [];
1574
+ }
1575
+ const compiled = [];
1576
+ for (const item of raw) {
1577
+ if (!LevelLogger.isRecord(item))
1578
+ continue;
1579
+ if (item.enabled === false)
1580
+ continue;
1581
+ if (!LevelLogger.isLogLevel(item.setLevel))
1582
+ continue;
1583
+ let when;
1584
+ if (LevelLogger.isRecord(item.when)) {
1585
+ const prefix = LevelLogger.compileMatchRule(item.when.prefix);
1586
+ const message = LevelLogger.compileMatchRule(item.when.message);
1587
+ if (prefix || message)
1588
+ when = { prefix, message };
1589
+ }
1590
+ compiled.push({
1591
+ name: typeof item.name === "string" ? item.name : undefined,
1592
+ setLevel: item.setLevel,
1593
+ when,
1594
+ });
1595
+ }
1596
+ return compiled;
1597
+ }
1598
+ static compileDynamicLevelRulesFromRaw(raw) {
1599
+ if (raw === undefined || raw === null || raw === "")
1600
+ return [];
1601
+ let parsed = raw;
1602
+ if (typeof raw === "string") {
1603
+ try {
1604
+ parsed = JSON.parse(raw);
1605
+ }
1606
+ catch (err) {
1607
+ console.warn("[LevelLogger] Failed to parse dynamic level rules JSON:", err);
1608
+ return [];
1609
+ }
1610
+ }
1611
+ return LevelLogger.compileDynamicLevelRules(parsed);
1612
+ }
1613
+ static matchesCompiledMatchRule(rule, value, valueLower) {
1614
+ if (!rule)
1615
+ return true;
1616
+ const lower = valueLower ?? value.toLowerCase();
1617
+ if (rule.equals.length > 0 && !rule.equals.some((candidate) => lower === candidate))
1618
+ return false;
1619
+ if (rule.startsWith.length > 0 && !rule.startsWith.some((candidate) => lower.startsWith(candidate)))
1620
+ return false;
1621
+ if (rule.contains.length > 0 && !rule.contains.some((candidate) => lower.includes(candidate)))
1622
+ return false;
1623
+ if (rule.regex.length > 0 && !rule.regex.some((re) => re.test(value)))
1624
+ return false;
1625
+ if (rule.not) {
1626
+ const notMatches = (rule.not.equals.length > 0 && rule.not.equals.some((candidate) => lower === candidate)) ||
1627
+ (rule.not.startsWith.length > 0 && rule.not.startsWith.some((candidate) => lower.startsWith(candidate))) ||
1628
+ (rule.not.contains.length > 0 && rule.not.contains.some((candidate) => lower.includes(candidate))) ||
1629
+ (rule.not.regex.length > 0 && rule.not.regex.some((re) => re.test(value)));
1630
+ if (notMatches)
1631
+ return false;
1632
+ }
1633
+ return true;
1634
+ }
1635
+ static resolveDynamicLevelFromRules(prefix, message, rules) {
1636
+ const prefixLower = prefix.toLowerCase();
1637
+ const messageLower = message.toLowerCase();
1638
+ for (const rule of rules) {
1639
+ const prefixMatch = LevelLogger.matchesCompiledMatchRule(rule.when?.prefix, prefix, prefixLower);
1640
+ if (!prefixMatch)
1641
+ continue;
1642
+ const messageMatch = LevelLogger.matchesCompiledMatchRule(rule.when?.message, message, messageLower);
1643
+ if (!messageMatch)
1644
+ continue;
1645
+ return rule.setLevel;
1646
+ }
1647
+ return undefined;
1648
+ }
1649
+ static resolveDynamicLevel(prefix, message) {
1650
+ return LevelLogger.resolveDynamicLevelFromRules(prefix, message, LevelLogger.compiledDynamicLevelRules);
1651
+ }
1652
+ resolveEffectiveLevel(message, overrideLevel) {
1653
+ if (overrideLevel)
1654
+ return overrideLevel;
1655
+ const instanceDynamic = LevelLogger.resolveDynamicLevelFromRules(this.prefix, message, this.compiledInstanceDynamicLevelRules);
1656
+ const globalDynamic = LevelLogger.resolveDynamicLevel(this.prefix, message);
1657
+ return instanceDynamic ?? globalDynamic ?? this.defaultLevel;
1658
+ }
1474
1659
  /**
1475
1660
  * Logs a debug message
1476
1661
  */
1477
1662
  debug(message, context, overrideLevel) {
1478
- this.log("debug", message, context, overrideLevel);
1663
+ this._log("debug", message, context, overrideLevel);
1479
1664
  }
1480
1665
  /**
1481
1666
  * Logs an info message
1482
1667
  */
1483
1668
  info(message, context, overrideLevel) {
1484
- this.log("info", message, context, overrideLevel);
1669
+ this._log("info", message, context, overrideLevel);
1485
1670
  }
1486
1671
  /**
1487
1672
  * Logs a warning message
1488
1673
  */
1489
1674
  warn(message, context, overrideLevel) {
1490
- this.log("warn", message, context, overrideLevel);
1675
+ this._log("warn", message, context, overrideLevel);
1491
1676
  }
1492
1677
  /**
1493
1678
  * Logs an error message
1494
1679
  */
1495
1680
  error(message, context, overrideLevel) {
1496
- this.log("error", message, context, overrideLevel);
1681
+ this._log("error", message, context, overrideLevel);
1682
+ }
1683
+ /**
1684
+ * Logs a message at the specified level
1685
+ */
1686
+ log(level, message, context, overrideLevel) {
1687
+ if (level === "silent")
1688
+ return;
1689
+ this._log(level, message, context, overrideLevel);
1497
1690
  }
1498
1691
  /**
1499
1692
  * Tries to parse a string as JSON and format it nicely if it's fully JSON
@@ -1560,8 +1753,8 @@ class LevelLogger {
1560
1753
  /**
1561
1754
  * Core logging method that respects the log level and applies prefix
1562
1755
  */
1563
- log(requestedLevel, message, context, overrideLevel) {
1564
- const effectiveLevel = overrideLevel ?? this.defaultLevel;
1756
+ _log(requestedLevel, message, context, overrideLevel) {
1757
+ const effectiveLevel = this.resolveEffectiveLevel(message, overrideLevel);
1565
1758
  // Silent level suppresses all logs
1566
1759
  if (effectiveLevel === "silent") {
1567
1760
  return;
@@ -1654,6 +1847,7 @@ class LevelLogger {
1654
1847
  }
1655
1848
  }
1656
1849
  }
1850
+ LevelLogger.compiledDynamicLevelRules = [];
1657
1851
  /**
1658
1852
  * Maps log levels to numeric values for comparison
1659
1853
  */
@@ -3001,20 +3195,7 @@ class NodeExecutor {
3001
3195
  const nodeLogLevel = node?.logLevel ?? "info";
3002
3196
  const logger = new LevelLogger(nodeLogLevel, `[node:${formatNodeRef(this.graph, nodeId)}:${runId}]`);
3003
3197
  const log = (level, message, context) => {
3004
- switch (level) {
3005
- case "debug":
3006
- logger.debug(message, context);
3007
- break;
3008
- case "info":
3009
- logger.info(message, context);
3010
- break;
3011
- case "warn":
3012
- logger.warn(message, context);
3013
- break;
3014
- case "error":
3015
- logger.error(message, context);
3016
- break;
3017
- }
3198
+ logger.log(level, message, context);
3018
3199
  };
3019
3200
  return {
3020
3201
  nodeId,