@bian-womp/spark-graph 0.3.51 → 0.3.53

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
@@ -998,39 +998,494 @@ const LOG_LEVEL_VALUES = {
998
998
  silent: 4,
999
999
  };
1000
1000
 
1001
- /**
1002
- * Shared utility functions for runtime components
1003
- */
1004
- /**
1005
- * Type guard to check if a value is a Promise
1006
- */
1007
- function isPromise(value) {
1008
- return !!value && typeof value.then === "function";
1001
+ function parseJsonPath(path) {
1002
+ if (typeof path === "string") {
1003
+ return path.split(".").flatMap((segment) => {
1004
+ const arrayMatch = segment.match(/^(.+)\[(\d+)\]$/);
1005
+ if (arrayMatch) {
1006
+ const index = parseInt(arrayMatch[2], 10);
1007
+ return arrayMatch[1] ? [arrayMatch[1], index] : [index];
1008
+ }
1009
+ return [segment];
1010
+ });
1011
+ }
1012
+ return path;
1009
1013
  }
1010
- /**
1011
- * Unwrap a value that might be a Promise
1012
- */
1013
- async function unwrapMaybePromise(value) {
1014
- return isPromise(value) ? await value : value;
1014
+ function getValueAtPath(obj, pathSegments) {
1015
+ if (pathSegments.length === 0) {
1016
+ return { value: obj, parent: null, key: "" };
1017
+ }
1018
+ let current = obj;
1019
+ for (let i = 0; i < pathSegments.length - 1; i++) {
1020
+ const segment = pathSegments[i];
1021
+ if (current === null ||
1022
+ current === undefined ||
1023
+ typeof current !== "object") {
1024
+ return null;
1025
+ }
1026
+ if (typeof segment === "string") {
1027
+ if (Array.isArray(current)) {
1028
+ const index = parseInt(segment, 10);
1029
+ if (isNaN(index))
1030
+ return null;
1031
+ current = current[index];
1032
+ }
1033
+ else {
1034
+ current = current[segment];
1035
+ }
1036
+ }
1037
+ else if (typeof segment === "number") {
1038
+ if (Array.isArray(current)) {
1039
+ if (segment >= 0 && segment < current.length) {
1040
+ current = current[segment];
1041
+ }
1042
+ else {
1043
+ return null;
1044
+ }
1045
+ }
1046
+ else {
1047
+ return null;
1048
+ }
1049
+ }
1050
+ else if (segment instanceof RegExp) {
1051
+ if (Array.isArray(current)) {
1052
+ return null;
1053
+ }
1054
+ const obj = current;
1055
+ const matchingKey = Object.keys(obj).find((key) => segment.test(key));
1056
+ if (!matchingKey)
1057
+ return null;
1058
+ current = obj[matchingKey];
1059
+ }
1060
+ else {
1061
+ return null;
1062
+ }
1063
+ }
1064
+ const lastSegment = pathSegments[pathSegments.length - 1];
1065
+ if (typeof lastSegment === "string") {
1066
+ if (Array.isArray(current)) {
1067
+ const index = parseInt(lastSegment, 10);
1068
+ if (isNaN(index))
1069
+ return null;
1070
+ return { value: current[index], parent: current, key: index };
1071
+ }
1072
+ else if (current !== null &&
1073
+ current !== undefined &&
1074
+ typeof current === "object") {
1075
+ return {
1076
+ value: current[lastSegment],
1077
+ parent: current,
1078
+ key: lastSegment,
1079
+ };
1080
+ }
1081
+ }
1082
+ else if (typeof lastSegment === "number") {
1083
+ if (Array.isArray(current)) {
1084
+ if (lastSegment >= 0 && lastSegment < current.length) {
1085
+ return {
1086
+ value: current[lastSegment],
1087
+ parent: current,
1088
+ key: lastSegment,
1089
+ };
1090
+ }
1091
+ }
1092
+ return null;
1093
+ }
1094
+ else if (lastSegment instanceof RegExp) {
1095
+ if (Array.isArray(current)) {
1096
+ return null;
1097
+ }
1098
+ const obj = current;
1099
+ const matchingKey = Object.keys(obj).find((key) => lastSegment.test(key));
1100
+ if (!matchingKey)
1101
+ return null;
1102
+ return { value: obj[matchingKey], parent: current, key: matchingKey };
1103
+ }
1104
+ return null;
1105
+ }
1106
+ function setValueAtPath(obj, pathSegments, newValue) {
1107
+ const result = getValueAtPath(obj, pathSegments);
1108
+ if (!result || result.parent === null)
1109
+ return false;
1110
+ if (Array.isArray(result.parent)) {
1111
+ result.parent[result.key] = newValue;
1112
+ }
1113
+ else {
1114
+ result.parent[result.key] = newValue;
1115
+ }
1116
+ return true;
1015
1117
  }
1016
1118
  /**
1017
- * Shallow/deep-ish equality check to avoid unnecessary runs on identical values
1119
+ * Sets a value at a path, creating intermediate objects as needed.
1120
+ * Mutates the root object in place.
1121
+ * @param root - The root object to modify (must be an object, will be initialized if needed)
1122
+ * @param pathSegments - The path segments to traverse
1123
+ * @param value - The value to set, or null to delete the path
1124
+ * @throws Error if path cannot be created (e.g., array indices not supported, invalid parent types)
1018
1125
  */
1019
- function valuesEqual(a, b) {
1020
- if (a === b)
1021
- return true;
1022
- if (typeof a !== typeof b)
1023
- return false;
1024
- if (a && b && typeof a === "object") {
1025
- try {
1026
- return JSON.stringify(a) === JSON.stringify(b);
1126
+ function setValueAtPathWithCreation(root, pathSegments, value) {
1127
+ if (value === null) {
1128
+ const result = getValueAtPath(root, pathSegments);
1129
+ if (result && result.parent !== null && !Array.isArray(result.parent)) {
1130
+ delete result.parent[result.key];
1027
1131
  }
1028
- catch {
1132
+ return;
1133
+ }
1134
+ if (!root || typeof root !== "object" || Array.isArray(root)) {
1135
+ throw new Error("Root must be an object");
1136
+ }
1137
+ let current = root;
1138
+ for (let i = 0; i < pathSegments.length - 1; i++) {
1139
+ const segment = pathSegments[i];
1140
+ if (typeof segment === "string") {
1141
+ if (!current ||
1142
+ typeof current !== "object" ||
1143
+ Array.isArray(current) ||
1144
+ !(segment in current) ||
1145
+ typeof current[segment] !== "object" ||
1146
+ current[segment] === null ||
1147
+ Array.isArray(current[segment])) {
1148
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
1149
+ throw new Error(`Cannot create path: parent at segment ${i} is not an object`);
1150
+ }
1151
+ current[segment] = {};
1152
+ }
1153
+ current = current[segment];
1154
+ }
1155
+ else {
1156
+ throw new Error("Array indices not supported in extData paths");
1157
+ }
1158
+ }
1159
+ const lastSegment = pathSegments[pathSegments.length - 1];
1160
+ if (typeof lastSegment === "string") {
1161
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
1162
+ throw new Error(`Cannot set value: parent at final segment is not an object`);
1163
+ }
1164
+ current[lastSegment] = value;
1165
+ }
1166
+ else {
1167
+ throw new Error("Array indices not supported in extData paths");
1168
+ }
1169
+ }
1170
+ function findMatchingPaths(obj, pathSegments, currentPath = []) {
1171
+ if (pathSegments.length === 0) {
1172
+ return [{ path: currentPath, value: obj }];
1173
+ }
1174
+ const [currentSegment, ...remainingSegments] = pathSegments;
1175
+ const results = [];
1176
+ if (currentSegment === undefined) {
1177
+ return results;
1178
+ }
1179
+ if (typeof currentSegment === "string") {
1180
+ if (Array.isArray(obj)) {
1181
+ const index = parseInt(currentSegment, 10);
1182
+ if (!isNaN(index) && index >= 0 && index < obj.length) {
1183
+ results.push(...findMatchingPaths(obj[index], remainingSegments, [
1184
+ ...currentPath,
1185
+ index,
1186
+ ]));
1187
+ }
1188
+ }
1189
+ else if (obj !== null && obj !== undefined && typeof obj === "object") {
1190
+ const objRecord = obj;
1191
+ if (currentSegment in objRecord) {
1192
+ results.push(...findMatchingPaths(objRecord[currentSegment], remainingSegments, [
1193
+ ...currentPath,
1194
+ currentSegment,
1195
+ ]));
1196
+ }
1197
+ }
1198
+ }
1199
+ else if (typeof currentSegment === "number") {
1200
+ if (Array.isArray(obj)) {
1201
+ if (currentSegment >= 0 && currentSegment < obj.length) {
1202
+ results.push(...findMatchingPaths(obj[currentSegment], remainingSegments, [
1203
+ ...currentPath,
1204
+ currentSegment,
1205
+ ]));
1206
+ }
1207
+ }
1208
+ }
1209
+ else if (currentSegment instanceof RegExp) {
1210
+ if (Array.isArray(obj)) {
1211
+ for (let i = 0; i < obj.length; i++) {
1212
+ results.push(...findMatchingPaths(obj[i], remainingSegments, [...currentPath, i]));
1213
+ }
1214
+ }
1215
+ else if (obj !== null && obj !== undefined && typeof obj === "object") {
1216
+ const objRecord = obj;
1217
+ for (const key of Object.keys(objRecord)) {
1218
+ if (currentSegment.test(key)) {
1219
+ results.push(...findMatchingPaths(objRecord[key], remainingSegments, [
1220
+ ...currentPath,
1221
+ key,
1222
+ ]));
1223
+ }
1224
+ }
1225
+ }
1226
+ }
1227
+ return results;
1228
+ }
1229
+ function stringifyJson(obj, oneLiner) {
1230
+ // No formatting requested: behave exactly like native JSON.stringify.
1231
+ if (!oneLiner)
1232
+ return JSON.stringify(obj);
1233
+ const indentSize = Number.isFinite(oneLiner.indent)
1234
+ ? Math.max(0, Math.floor(oneLiner.indent))
1235
+ : 2;
1236
+ const maxDepth = typeof oneLiner.maxDepth === "number" && Number.isFinite(oneLiner.maxDepth)
1237
+ ? oneLiner.maxDepth
1238
+ : undefined;
1239
+ const patterns = (oneLiner.paths || []).filter(Boolean);
1240
+ // Preserve JSON.stringify semantics for things like toJSON(), dropping functions/undefined, etc.
1241
+ // Note: this still throws on circular structures (same as JSON.stringify).
1242
+ const base = JSON.stringify(obj);
1243
+ if (base === undefined)
1244
+ return base;
1245
+ const value = JSON.parse(base);
1246
+ const pathMatchers = patterns.map((p) => compilePathMatcher(String(p)));
1247
+ const formatKey = (k) => JSON.stringify(k);
1248
+ const shouldInline = (path, key, v, depth) => {
1249
+ if (maxDepth !== undefined && depth > maxDepth)
1250
+ return true;
1251
+ if (oneLiner.criteria?.({ path, key, value: v, depth }))
1252
+ return true;
1253
+ if (!pathMatchers.length)
1029
1254
  return false;
1255
+ const tokensWithRoot = tokenizePath(path);
1256
+ const tokensNoRoot = tokensWithRoot[0] === "$" ? tokensWithRoot.slice(1) : tokensWithRoot;
1257
+ return pathMatchers.some((m) => m(tokensWithRoot, tokensNoRoot));
1258
+ };
1259
+ const stringifyInline = (v, depth, path) => {
1260
+ if (v === null)
1261
+ return "null";
1262
+ const t = typeof v;
1263
+ if (t === "string")
1264
+ return JSON.stringify(v);
1265
+ if (t === "number" || t === "boolean")
1266
+ return String(v);
1267
+ if (Array.isArray(v)) {
1268
+ if (!v.length)
1269
+ return "[]";
1270
+ const parts = v.map((vv, i) => stringifyInline(vv));
1271
+ return `[${parts.join(", ")}]`;
1272
+ }
1273
+ if (t === "object") {
1274
+ const keys = Object.keys(v);
1275
+ if (!keys.length)
1276
+ return "{}";
1277
+ const parts = keys.map((k) => `${formatKey(k)}: ${stringifyInline(v[k])}`);
1278
+ return `{ ${parts.join(", ")} }`;
1279
+ }
1280
+ // Shouldn't happen after JSON.parse(JSON.stringify(...)), but keep output valid JSON.
1281
+ return "null";
1282
+ };
1283
+ const stringifyPretty = (v, depth, path, key) => {
1284
+ if (shouldInline(path, key, v, depth))
1285
+ return stringifyInline(v);
1286
+ if (v === null)
1287
+ return "null";
1288
+ const t = typeof v;
1289
+ if (t === "string")
1290
+ return JSON.stringify(v);
1291
+ if (t === "number" || t === "boolean")
1292
+ return String(v);
1293
+ const indentCur = " ".repeat(indentSize * depth);
1294
+ const indentInner = " ".repeat(indentSize * (depth + 1));
1295
+ if (Array.isArray(v)) {
1296
+ if (!v.length)
1297
+ return "[]";
1298
+ // Compact array style: `[{...}, {...}]` while still allowing multi-line objects within.
1299
+ const parts = v.map((vv, i) => stringifyPretty(vv, depth, `${path}[${i}]`, String(i)));
1300
+ return `[${parts.join(", ")}]`;
1301
+ }
1302
+ if (t === "object") {
1303
+ const keys = Object.keys(v);
1304
+ if (!keys.length)
1305
+ return "{}";
1306
+ const lines = keys.map((k, idx) => {
1307
+ const childPath = `${path}.${k}`;
1308
+ const rendered = stringifyPretty(v[k], depth + 1, childPath, k);
1309
+ const comma = idx === keys.length - 1 ? "" : ",";
1310
+ return `${indentInner}${formatKey(k)}: ${rendered}${comma}`;
1311
+ });
1312
+ return `{\n${lines.join("\n")}\n${indentCur}}`;
1313
+ }
1314
+ return "null";
1315
+ };
1316
+ return stringifyPretty(value, 0, "$", "$");
1317
+ }
1318
+ function tokenizePath(path) {
1319
+ // Path format we generate: `$`, `$.a.b[0].c`
1320
+ // Tokens: ["$", "a", "b", "[0]", "c"]
1321
+ const tokens = [];
1322
+ let i = 0;
1323
+ if (path.startsWith("$")) {
1324
+ tokens.push("$");
1325
+ i = 1;
1326
+ }
1327
+ while (i < path.length) {
1328
+ const ch = path[i];
1329
+ if (ch === ".") {
1330
+ i++;
1331
+ const start = i;
1332
+ while (i < path.length && path[i] !== "." && path[i] !== "[")
1333
+ i++;
1334
+ if (i > start)
1335
+ tokens.push(path.slice(start, i));
1336
+ continue;
1030
1337
  }
1338
+ if (ch === "[") {
1339
+ const end = path.indexOf("]", i + 1);
1340
+ if (end < 0) {
1341
+ tokens.push(path.slice(i));
1342
+ break;
1343
+ }
1344
+ tokens.push(path.slice(i, end + 1));
1345
+ i = end + 1;
1346
+ continue;
1347
+ }
1348
+ // Unexpected char; skip.
1349
+ i++;
1031
1350
  }
1032
- return false;
1351
+ return tokens.filter((t) => t.length);
1352
+ }
1353
+ function tokenizePattern(pattern) {
1354
+ // Pattern format: `$`, `$.a.*.b`, `**.graph.**`, `arr[2].z`
1355
+ // Tokens: ["$", "a", "*", "b"] etc. Brackets become their own token: ["arr","[2]","z"]
1356
+ const tokens = [];
1357
+ let i = 0;
1358
+ if (pattern.startsWith("$")) {
1359
+ tokens.push("$");
1360
+ i = 1;
1361
+ }
1362
+ while (i < pattern.length) {
1363
+ const ch = pattern[i];
1364
+ if (ch === ".") {
1365
+ i++;
1366
+ continue;
1367
+ }
1368
+ if (ch === "[") {
1369
+ const end = pattern.indexOf("]", i + 1);
1370
+ if (end < 0) {
1371
+ tokens.push(pattern.slice(i));
1372
+ break;
1373
+ }
1374
+ tokens.push(pattern.slice(i, end + 1));
1375
+ i = end + 1;
1376
+ continue;
1377
+ }
1378
+ const start = i;
1379
+ while (i < pattern.length && pattern[i] !== "." && pattern[i] !== "[")
1380
+ i++;
1381
+ if (i > start)
1382
+ tokens.push(pattern.slice(start, i));
1383
+ }
1384
+ return tokens.filter((t) => t.length);
1033
1385
  }
1386
+ function compilePathMatcher(pattern) {
1387
+ // Wildcard semantics (case-insensitive):
1388
+ // - `*` matches exactly 1 path segment (key or `[index]`)
1389
+ // - `**` matches 0 or more segments
1390
+ // - `#` matches exactly 1 numeric segment (e.g. `"0"` or `[0]`)
1391
+ // - `##` matches 0 or more numeric segments
1392
+ // - `[*]` matches exactly 1 index segment
1393
+ const pat = tokenizePattern(pattern);
1394
+ const expectsRoot = pat[0] === "$";
1395
+ const eq = (a, b) => a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
1396
+ const isIndex = (t) => t.startsWith("[") && t.endsWith("]");
1397
+ const isNumericKey = (t) => /^[0-9]+$/.test(t);
1398
+ const isNumericSegment = (t) => isNumericKey(t) || (isIndex(t) && /^[0-9]+$/.test(t.slice(1, -1)));
1399
+ const match = (patTokens, pathTokens) => {
1400
+ const memo = new Map();
1401
+ const go = (pi, ti) => {
1402
+ const key = `${pi},${ti}`;
1403
+ const cached = memo.get(key);
1404
+ if (cached !== undefined)
1405
+ return cached;
1406
+ let res = false;
1407
+ if (pi === patTokens.length) {
1408
+ res = ti === pathTokens.length;
1409
+ }
1410
+ else {
1411
+ const p = patTokens[pi];
1412
+ if (p === "**") {
1413
+ // Match any number of segments, including zero.
1414
+ for (let k = ti; k <= pathTokens.length; k++) {
1415
+ if (go(pi + 1, k)) {
1416
+ res = true;
1417
+ break;
1418
+ }
1419
+ }
1420
+ }
1421
+ else if (p === "##") {
1422
+ // Match any number of numeric segments, including zero.
1423
+ for (let k = ti; k <= pathTokens.length; k++) {
1424
+ let ok = true;
1425
+ for (let j = ti; j < k; j++) {
1426
+ if (!isNumericSegment(pathTokens[j])) {
1427
+ ok = false;
1428
+ break;
1429
+ }
1430
+ }
1431
+ if (ok && go(pi + 1, k)) {
1432
+ res = true;
1433
+ break;
1434
+ }
1435
+ }
1436
+ }
1437
+ else if (p === "*") {
1438
+ res = ti < pathTokens.length && go(pi + 1, ti + 1);
1439
+ }
1440
+ else if (p === "#") {
1441
+ res =
1442
+ ti < pathTokens.length &&
1443
+ isNumericSegment(pathTokens[ti]) &&
1444
+ go(pi + 1, ti + 1);
1445
+ }
1446
+ else if (p === "[*]") {
1447
+ res =
1448
+ ti < pathTokens.length &&
1449
+ isIndex(pathTokens[ti]) &&
1450
+ go(pi + 1, ti + 1);
1451
+ }
1452
+ else {
1453
+ res =
1454
+ ti < pathTokens.length &&
1455
+ eq(p.toLowerCase(), pathTokens[ti].toLowerCase()) &&
1456
+ go(pi + 1, ti + 1);
1457
+ }
1458
+ }
1459
+ memo.set(key, res);
1460
+ return res;
1461
+ };
1462
+ return go(0, 0);
1463
+ };
1464
+ return (tokensWithRoot, tokensNoRoot) => match(pat, expectsRoot ? tokensWithRoot : tokensNoRoot);
1465
+ }
1466
+ function stringifySceneAndOps(obj) {
1467
+ return stringifyJson(obj, {
1468
+ indent: 2,
1469
+ paths: [
1470
+ "**.camera.animation.*",
1471
+ "**.animations.*",
1472
+ "**.nodes.#.*",
1473
+ "**.graphs.#.*",
1474
+ "**.instances.#.*",
1475
+ "**.shaders.#.*",
1476
+ "**.materials.#.*",
1477
+ "**.ext.#",
1478
+ "**.ext.*.#.*",
1479
+ "**.perUserExt.*.#",
1480
+ "**.perUserExt.*.*.#",
1481
+ ],
1482
+ criteria: ({ value, key }) => (typeof value === "object" &&
1483
+ value &&
1484
+ Object.keys(value).sort().join(",") === "x,y,z") ||
1485
+ key === "value",
1486
+ });
1487
+ }
1488
+
1034
1489
  /**
1035
1490
  * A reusable logger class that supports configurable log levels and prefixes.
1036
1491
  * Can be instantiated with a default log level and optionally override per call.
@@ -1108,7 +1563,7 @@ class LevelLogger {
1108
1563
  const contextStr = rest && Object.keys(rest).length > 0
1109
1564
  ? `${formatJson ? "\n" : " "}${Object.entries(rest)
1110
1565
  .map(([k, v]) => formatJson
1111
- ? `${k}=${JSON.stringify(v, undefined, 2)}`
1566
+ ? `${k}=${stringifySceneAndOps(v)}`
1112
1567
  : `${k}=${JSON.stringify(v)}`)
1113
1568
  .join(formatJson ? "\n" : " ")}`
1114
1569
  : "";
@@ -1413,29 +1868,63 @@ class RunContextManager {
1413
1868
  for (const id of toCancel) {
1414
1869
  this.graph.clearNodeRunContextIds(id);
1415
1870
  }
1416
- }
1417
- /**
1418
- * Resolve all pending run-context promises (for cleanup)
1419
- */
1420
- resolveAll() {
1421
- const count = this.runContexts.size;
1422
- this.logger.info("resolve-all-run-contexts", {
1423
- count,
1424
- runContextIds: Array.from(this.runContexts.keys()),
1425
- });
1426
- for (const ctx of this.runContexts.values()) {
1427
- if (ctx.resolve)
1428
- ctx.resolve();
1871
+ }
1872
+ /**
1873
+ * Resolve all pending run-context promises (for cleanup)
1874
+ */
1875
+ resolveAll() {
1876
+ const count = this.runContexts.size;
1877
+ this.logger.info("resolve-all-run-contexts", {
1878
+ count,
1879
+ runContextIds: Array.from(this.runContexts.keys()),
1880
+ });
1881
+ for (const ctx of this.runContexts.values()) {
1882
+ if (ctx.resolve)
1883
+ ctx.resolve();
1884
+ }
1885
+ }
1886
+ /**
1887
+ * Clear all run-contexts
1888
+ */
1889
+ clear() {
1890
+ const count = this.runContexts.size;
1891
+ this.logger.info("clear-all-run-contexts", { count });
1892
+ this.runContexts.clear();
1893
+ }
1894
+ }
1895
+
1896
+ /**
1897
+ * Shared utility functions for runtime components
1898
+ */
1899
+ /**
1900
+ * Type guard to check if a value is a Promise
1901
+ */
1902
+ function isPromise(value) {
1903
+ return !!value && typeof value.then === "function";
1904
+ }
1905
+ /**
1906
+ * Unwrap a value that might be a Promise
1907
+ */
1908
+ async function unwrapMaybePromise(value) {
1909
+ return isPromise(value) ? await value : value;
1910
+ }
1911
+ /**
1912
+ * Shallow/deep-ish equality check to avoid unnecessary runs on identical values
1913
+ */
1914
+ function valuesEqual(a, b) {
1915
+ if (a === b)
1916
+ return true;
1917
+ if (typeof a !== typeof b)
1918
+ return false;
1919
+ if (a && b && typeof a === "object") {
1920
+ try {
1921
+ return JSON.stringify(a) === JSON.stringify(b);
1922
+ }
1923
+ catch {
1924
+ return false;
1429
1925
  }
1430
1926
  }
1431
- /**
1432
- * Clear all run-contexts
1433
- */
1434
- clear() {
1435
- const count = this.runContexts.size;
1436
- this.logger.info("clear-all-run-contexts", { count });
1437
- this.runContexts.clear();
1438
- }
1927
+ return false;
1439
1928
  }
1440
1929
 
1441
1930
  function tryHandleResolving(def, registry, environment) {
@@ -5553,235 +6042,6 @@ function createValidationGraphRegistry(id) {
5553
6042
  return registry;
5554
6043
  }
5555
6044
 
5556
- function parseJsonPath(path) {
5557
- if (typeof path === "string") {
5558
- return path.split(".").flatMap((segment) => {
5559
- const arrayMatch = segment.match(/^(.+)\[(\d+)\]$/);
5560
- if (arrayMatch) {
5561
- const index = parseInt(arrayMatch[2], 10);
5562
- return arrayMatch[1] ? [arrayMatch[1], index] : [index];
5563
- }
5564
- return [segment];
5565
- });
5566
- }
5567
- return path;
5568
- }
5569
- function getValueAtPath(obj, pathSegments) {
5570
- if (pathSegments.length === 0) {
5571
- return { value: obj, parent: null, key: "" };
5572
- }
5573
- let current = obj;
5574
- for (let i = 0; i < pathSegments.length - 1; i++) {
5575
- const segment = pathSegments[i];
5576
- if (current === null ||
5577
- current === undefined ||
5578
- typeof current !== "object") {
5579
- return null;
5580
- }
5581
- if (typeof segment === "string") {
5582
- if (Array.isArray(current)) {
5583
- const index = parseInt(segment, 10);
5584
- if (isNaN(index))
5585
- return null;
5586
- current = current[index];
5587
- }
5588
- else {
5589
- current = current[segment];
5590
- }
5591
- }
5592
- else if (typeof segment === "number") {
5593
- if (Array.isArray(current)) {
5594
- if (segment >= 0 && segment < current.length) {
5595
- current = current[segment];
5596
- }
5597
- else {
5598
- return null;
5599
- }
5600
- }
5601
- else {
5602
- return null;
5603
- }
5604
- }
5605
- else if (segment instanceof RegExp) {
5606
- if (Array.isArray(current)) {
5607
- return null;
5608
- }
5609
- const obj = current;
5610
- const matchingKey = Object.keys(obj).find((key) => segment.test(key));
5611
- if (!matchingKey)
5612
- return null;
5613
- current = obj[matchingKey];
5614
- }
5615
- else {
5616
- return null;
5617
- }
5618
- }
5619
- const lastSegment = pathSegments[pathSegments.length - 1];
5620
- if (typeof lastSegment === "string") {
5621
- if (Array.isArray(current)) {
5622
- const index = parseInt(lastSegment, 10);
5623
- if (isNaN(index))
5624
- return null;
5625
- return { value: current[index], parent: current, key: index };
5626
- }
5627
- else if (current !== null &&
5628
- current !== undefined &&
5629
- typeof current === "object") {
5630
- return {
5631
- value: current[lastSegment],
5632
- parent: current,
5633
- key: lastSegment,
5634
- };
5635
- }
5636
- }
5637
- else if (typeof lastSegment === "number") {
5638
- if (Array.isArray(current)) {
5639
- if (lastSegment >= 0 && lastSegment < current.length) {
5640
- return {
5641
- value: current[lastSegment],
5642
- parent: current,
5643
- key: lastSegment,
5644
- };
5645
- }
5646
- }
5647
- return null;
5648
- }
5649
- else if (lastSegment instanceof RegExp) {
5650
- if (Array.isArray(current)) {
5651
- return null;
5652
- }
5653
- const obj = current;
5654
- const matchingKey = Object.keys(obj).find((key) => lastSegment.test(key));
5655
- if (!matchingKey)
5656
- return null;
5657
- return { value: obj[matchingKey], parent: current, key: matchingKey };
5658
- }
5659
- return null;
5660
- }
5661
- function setValueAtPath(obj, pathSegments, newValue) {
5662
- const result = getValueAtPath(obj, pathSegments);
5663
- if (!result || result.parent === null)
5664
- return false;
5665
- if (Array.isArray(result.parent)) {
5666
- result.parent[result.key] = newValue;
5667
- }
5668
- else {
5669
- result.parent[result.key] = newValue;
5670
- }
5671
- return true;
5672
- }
5673
- /**
5674
- * Sets a value at a path, creating intermediate objects as needed.
5675
- * Mutates the root object in place.
5676
- * @param root - The root object to modify (must be an object, will be initialized if needed)
5677
- * @param pathSegments - The path segments to traverse
5678
- * @param value - The value to set, or null to delete the path
5679
- * @throws Error if path cannot be created (e.g., array indices not supported, invalid parent types)
5680
- */
5681
- function setValueAtPathWithCreation(root, pathSegments, value) {
5682
- if (value === null) {
5683
- const result = getValueAtPath(root, pathSegments);
5684
- if (result && result.parent !== null && !Array.isArray(result.parent)) {
5685
- delete result.parent[result.key];
5686
- }
5687
- return;
5688
- }
5689
- if (!root || typeof root !== "object" || Array.isArray(root)) {
5690
- throw new Error("Root must be an object");
5691
- }
5692
- let current = root;
5693
- for (let i = 0; i < pathSegments.length - 1; i++) {
5694
- const segment = pathSegments[i];
5695
- if (typeof segment === "string") {
5696
- if (!current ||
5697
- typeof current !== "object" ||
5698
- Array.isArray(current) ||
5699
- !(segment in current) ||
5700
- typeof current[segment] !== "object" ||
5701
- current[segment] === null ||
5702
- Array.isArray(current[segment])) {
5703
- if (!current || typeof current !== "object" || Array.isArray(current)) {
5704
- throw new Error(`Cannot create path: parent at segment ${i} is not an object`);
5705
- }
5706
- current[segment] = {};
5707
- }
5708
- current = current[segment];
5709
- }
5710
- else {
5711
- throw new Error("Array indices not supported in extData paths");
5712
- }
5713
- }
5714
- const lastSegment = pathSegments[pathSegments.length - 1];
5715
- if (typeof lastSegment === "string") {
5716
- if (!current || typeof current !== "object" || Array.isArray(current)) {
5717
- throw new Error(`Cannot set value: parent at final segment is not an object`);
5718
- }
5719
- current[lastSegment] = value;
5720
- }
5721
- else {
5722
- throw new Error("Array indices not supported in extData paths");
5723
- }
5724
- }
5725
- function findMatchingPaths(obj, pathSegments, currentPath = []) {
5726
- if (pathSegments.length === 0) {
5727
- return [{ path: currentPath, value: obj }];
5728
- }
5729
- const [currentSegment, ...remainingSegments] = pathSegments;
5730
- const results = [];
5731
- if (currentSegment === undefined) {
5732
- return results;
5733
- }
5734
- if (typeof currentSegment === "string") {
5735
- if (Array.isArray(obj)) {
5736
- const index = parseInt(currentSegment, 10);
5737
- if (!isNaN(index) && index >= 0 && index < obj.length) {
5738
- results.push(...findMatchingPaths(obj[index], remainingSegments, [
5739
- ...currentPath,
5740
- index,
5741
- ]));
5742
- }
5743
- }
5744
- else if (obj !== null && obj !== undefined && typeof obj === "object") {
5745
- const objRecord = obj;
5746
- if (currentSegment in objRecord) {
5747
- results.push(...findMatchingPaths(objRecord[currentSegment], remainingSegments, [
5748
- ...currentPath,
5749
- currentSegment,
5750
- ]));
5751
- }
5752
- }
5753
- }
5754
- else if (typeof currentSegment === "number") {
5755
- if (Array.isArray(obj)) {
5756
- if (currentSegment >= 0 && currentSegment < obj.length) {
5757
- results.push(...findMatchingPaths(obj[currentSegment], remainingSegments, [
5758
- ...currentPath,
5759
- currentSegment,
5760
- ]));
5761
- }
5762
- }
5763
- }
5764
- else if (currentSegment instanceof RegExp) {
5765
- if (Array.isArray(obj)) {
5766
- for (let i = 0; i < obj.length; i++) {
5767
- results.push(...findMatchingPaths(obj[i], remainingSegments, [...currentPath, i]));
5768
- }
5769
- }
5770
- else if (obj !== null && obj !== undefined && typeof obj === "object") {
5771
- const objRecord = obj;
5772
- for (const key of Object.keys(objRecord)) {
5773
- if (currentSegment.test(key)) {
5774
- results.push(...findMatchingPaths(objRecord[key], remainingSegments, [
5775
- ...currentPath,
5776
- key,
5777
- ]));
5778
- }
5779
- }
5780
- }
5781
- }
5782
- return results;
5783
- }
5784
-
5785
6045
  function mergeGraphDefinitions(target, source) {
5786
6046
  const existingNodeIds = new Set(target.nodes.map((n) => n.nodeId));
5787
6047
  const existingEdgeIds = new Set(target.edges.map((e) => e.id));
@@ -6420,6 +6680,8 @@ exports.registerDelayNode = registerDelayNode;
6420
6680
  exports.registerProgressNodes = registerProgressNodes;
6421
6681
  exports.setValueAtPath = setValueAtPath;
6422
6682
  exports.setValueAtPathWithCreation = setValueAtPathWithCreation;
6683
+ exports.stringifyJson = stringifyJson;
6684
+ exports.stringifySceneAndOps = stringifySceneAndOps;
6423
6685
  exports.typed = typed;
6424
6686
  exports.unwrapTypeId = unwrapTypeId;
6425
6687
  exports.unwrapValue = unwrapValue;