simple-graph-query 2.0.1 → 2.1.0

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.
@@ -48473,11 +48473,17 @@ function extractNumber(val) {
48473
48473
  function isString(value) {
48474
48474
  return typeof value === "string";
48475
48475
  }
48476
+ // Helper to create a string key from a tuple for fast lookup
48477
+ function tupleToKey(tuple) {
48478
+ return JSON.stringify(tuple);
48479
+ }
48476
48480
  function areTuplesEqual(a, b) {
48477
48481
  return a.length === b.length && a.every((val, i) => val === b[i]);
48478
48482
  }
48479
48483
  function isTupleArraySubset(a, b) {
48480
- return a.every((tupleA) => b.some((tupleB) => areTuplesEqual(tupleA, tupleB)));
48484
+ // Optimize using Set for O(n) lookup instead of O()
48485
+ const bSet = new Set(b.map(tupleToKey));
48486
+ return a.every((tupleA) => bSet.has(tupleToKey(tupleA)));
48481
48487
  }
48482
48488
  function areTupleArraysEqual(a, b) {
48483
48489
  if (a.length !== b.length) {
@@ -48486,9 +48492,13 @@ function areTupleArraysEqual(a, b) {
48486
48492
  return isTupleArraySubset(a, b) && isTupleArraySubset(b, a);
48487
48493
  }
48488
48494
  function deduplicateTuples(tuples) {
48495
+ // Optimize using Set for O(n) deduplication instead of O(n²)
48496
+ const seen = new Set();
48489
48497
  const result = [];
48490
48498
  for (const tuple of tuples) {
48491
- if (!result.some((existing) => areTuplesEqual(existing, tuple))) {
48499
+ const key = tupleToKey(tuple);
48500
+ if (!seen.has(key)) {
48501
+ seen.add(key);
48492
48502
  result.push(tuple);
48493
48503
  }
48494
48504
  }
@@ -48497,15 +48507,23 @@ function deduplicateTuples(tuples) {
48497
48507
  function getCombinations(arrays) {
48498
48508
  // first, turn each string[][] into a string[] by flattening
48499
48509
  const valueSets = arrays.map((tuple) => tuple.flat());
48500
- // now, recursively compute the cartesian product
48501
- function cartesianProduct(arrays) {
48502
- if (arrays.length === 0)
48503
- return [[]];
48504
- const [first, ...rest] = arrays;
48505
- const restProduct = cartesianProduct(rest);
48506
- return first.flatMap((value) => restProduct.map((product) => [value, ...product]));
48510
+ // Early exit for empty arrays
48511
+ if (valueSets.length === 0)
48512
+ return [[]];
48513
+ if (valueSets.some(arr => arr.length === 0))
48514
+ return [];
48515
+ // Iterative approach for better performance on large cartesian products
48516
+ let result = [[]];
48517
+ for (const valueSet of valueSets) {
48518
+ const newResult = [];
48519
+ for (const existing of result) {
48520
+ for (const value of valueSet) {
48521
+ newResult.push([...existing, value]);
48522
+ }
48523
+ }
48524
+ result = newResult;
48507
48525
  }
48508
- return cartesianProduct(valueSets);
48526
+ return result;
48509
48527
  }
48510
48528
  function transitiveClosure(pairs) {
48511
48529
  if (pairs.length === 0)
@@ -48554,17 +48572,29 @@ function transitiveClosure(pairs) {
48554
48572
  function dotJoin(left, right) {
48555
48573
  const leftExpr = isSingleValue(left) ? [[left]] : left;
48556
48574
  const rightExpr = isSingleValue(right) ? [[right]] : right;
48575
+ // Optimize join using a Map for O(n+m) instead of O(n*m) lookup
48576
+ // Group right tuples by their first element for fast lookup
48577
+ const rightIndex = new Map();
48578
+ for (const rightTuple of rightExpr) {
48579
+ const key = rightTuple[0];
48580
+ if (!rightIndex.has(key)) {
48581
+ rightIndex.set(key, []);
48582
+ }
48583
+ rightIndex.get(key).push(rightTuple);
48584
+ }
48557
48585
  const result = [];
48558
- leftExpr.forEach((leftTuple) => {
48559
- rightExpr.forEach((rightTuple) => {
48560
- if (leftTuple[leftTuple.length - 1] === rightTuple[0]) {
48586
+ for (const leftTuple of leftExpr) {
48587
+ const joinKey = leftTuple[leftTuple.length - 1];
48588
+ const matchingRightTuples = rightIndex.get(joinKey);
48589
+ if (matchingRightTuples) {
48590
+ for (const rightTuple of matchingRightTuples) {
48561
48591
  result.push([
48562
48592
  ...leftTuple.slice(0, leftTuple.length - 1),
48563
48593
  ...rightTuple.slice(1),
48564
48594
  ]);
48565
48595
  }
48566
- });
48567
- });
48596
+ }
48597
+ }
48568
48598
  if (result.some(tuple => tuple.length === 0)) {
48569
48599
  throw new Error("Join would create a relation of arity 0");
48570
48600
  }
@@ -48628,7 +48658,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
48628
48658
  constructFreeVariableKey(freeVarValues) {
48629
48659
  const keys = Object.keys(freeVarValues);
48630
48660
  keys.sort(); // sort the keys to ensure consistent ordering
48631
- return keys.map((key) => `${key}=${freeVarValues[key]}`).join("|");
48661
+ // Use JSON.stringify for complex values to ensure proper serialization
48662
+ return keys.map((key) => {
48663
+ const value = freeVarValues[key];
48664
+ const valueStr = Array.isArray(value) ? JSON.stringify(value) : String(value);
48665
+ return `${key}=${valueStr}`;
48666
+ }).join("|");
48632
48667
  }
48633
48668
  // helper function to get the label for a value (used for @: operator)
48634
48669
  getLabelForValue(value) {
@@ -49418,7 +49453,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49418
49453
  return leftChildValue;
49419
49454
  }
49420
49455
  if (leftChildValue[0].length === rightChildValue[0].length) {
49421
- return leftChildValue.filter((tuple) => !rightChildValue.some((rightTuple) => areTuplesEqual(tuple, rightTuple)));
49456
+ // Optimize set difference using Set for O(n+m) instead of O(n*m)
49457
+ const rightSet = new Set(rightChildValue.map(tupleToKey));
49458
+ return leftChildValue.filter(tuple => !rightSet.has(tupleToKey(tuple)));
49422
49459
  }
49423
49460
  }
49424
49461
  else {
@@ -49491,7 +49528,9 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
49491
49528
  return [];
49492
49529
  }
49493
49530
  if (leftChildValue[0].length === rightChildValue[0].length) {
49494
- return leftChildValue.filter((tuple) => rightChildValue.some((rightTuple) => areTuplesEqual(tuple, rightTuple)));
49531
+ // Optimize set intersection using Set for O(n+m) instead of O(n*m)
49532
+ const rightSet = new Set(rightChildValue.map(tupleToKey));
49533
+ return leftChildValue.filter(tuple => rightSet.has(tupleToKey(tuple)));
49495
49534
  }
49496
49535
  }
49497
49536
  else {
@@ -50025,12 +50064,12 @@ class ForgeExprEvaluator extends AbstractParseTreeVisitor_1.AbstractParseTreeVis
50025
50064
  if (exports.SUPPORTED_BUILTINS.includes(identifier)) {
50026
50065
  return identifier;
50027
50066
  }
50028
- // Check if this looks like a simple label identifier (for label comparison)
50029
- // Heuristic: simple lowercase words that look like color/state names
50030
- const labelLikePattern = /^[a-z]{3,10}$/; // 3-10 lowercase letters only
50067
+ // Check if this looks like a label identifier (for label comparison).
50068
+ // Labels can be any alphanumeric string with underscores.
50069
+ // This allows identifiers like "Black", "red", "my_label", "Color_123", etc.
50070
+ // to be treated as string literals in label comparisons (e.g., @:y = Black).
50071
+ const labelLikePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
50031
50072
  if (labelLikePattern.test(identifier)) {
50032
- // This looks like a simple label (e.g., "black", "red", "blue", etc.)
50033
- // Return it as a string literal to enable label comparison syntax
50034
50073
  return identifier;
50035
50074
  }
50036
50075
  throw new NameNotFoundError(`bad name ${identifier} referenced!`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-graph-query",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "TypeScript evaluator for Forge expressions with browser-compatible UMD bundle",
5
5
  "main": "dist/simple-graph-query.bundle.js",
6
6
  "types": "dist/index.d.ts",