@lowgular/code-graph 0.1.1 → 0.1.2

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.
Files changed (44) hide show
  1. package/bin/cli.js +2458 -5
  2. package/lib.js +2430 -0
  3. package/package.json +3 -2
  4. package/code-graph-v2/code.graph.js +0 -37
  5. package/code-graph-v2/config-from-query.js +0 -131
  6. package/code-graph-v2/extractors/extractor.js +0 -27
  7. package/code-graph-v2/extractors/index.js +0 -1
  8. package/code-graph-v2/graph-builder/code-graph.builder.js +0 -49
  9. package/code-graph-v2/graph-builder/index.js +0 -1
  10. package/code-graph-v2/graph-builder/node.processor.js +0 -22
  11. package/code-graph-v2/graph-builder/relationship.processor.js +0 -55
  12. package/code-graph-v2/graph-builder/type.processor.js +0 -21
  13. package/code-graph-v2/index.js +0 -4
  14. package/code-graph-v2/tools/build-code-graph.tool.js +0 -19
  15. package/code-graph-v2/utils.js +0 -34
  16. package/codegular/index.js +0 -5
  17. package/codegular/node.js +0 -71
  18. package/codegular/program.js +0 -100
  19. package/codegular/string.js +0 -121
  20. package/codegular/type-checker.js +0 -133
  21. package/codegular/type.js +0 -356
  22. package/codegular/utils.js +0 -335
  23. package/cypher/index.js +0 -1
  24. package/cypher/lib/executor/condition-evaluator.js +0 -135
  25. package/cypher/lib/executor/executor.js +0 -60
  26. package/cypher/lib/executor/graph.js +0 -0
  27. package/cypher/lib/executor/match-engine.js +0 -130
  28. package/cypher/lib/executor/pattern-matcher.js +0 -86
  29. package/cypher/lib/executor/relationship-navigator.js +0 -41
  30. package/cypher/lib/executor/result-formatter.js +0 -149
  31. package/cypher/lib/executor/traverse-engine.js +0 -141
  32. package/cypher/lib/executor/utils.js +0 -14
  33. package/cypher/lib/graph.stub.js +0 -38
  34. package/cypher/lib/index.js +0 -32
  35. package/cypher/lib/lexer.js +0 -376
  36. package/cypher/lib/parser.js +0 -586
  37. package/cypher/lib/validator/query-validator.js +0 -75
  38. package/cypher/lib/validator/supported-features.config.js +0 -83
  39. package/cypher/lib/validator/unsupported-features.config.js +0 -124
  40. package/cypher-cli.js +0 -41
  41. package/infra/code-graph.js +0 -147
  42. package/main.js +0 -0
  43. package/resources-cli.js +0 -75
  44. package/run-cli.js +0 -43
@@ -1,335 +0,0 @@
1
- import * as path from "path";
2
- import * as ts from "typescript";
3
- import { getNameByIdentifier } from "./string.js";
4
- const { forEachChild, SyntaxKind } = ts;
5
- function escapeValue(value) {
6
- return value.replace(/^['"`]+/i, "").replace(/['"`]+$/i, "");
7
- }
8
- const parseObjectLiteralString = (objectLiteralString) => {
9
- const result = {};
10
- if (!objectLiteralString || typeof objectLiteralString !== "string") {
11
- return result;
12
- }
13
- const code = `const _ = ${objectLiteralString}`;
14
- const sourceFile = ts.createSourceFile(
15
- "temp.ts",
16
- code,
17
- ts.ScriptTarget.Latest,
18
- true
19
- );
20
- const findObjectLiteral = (node) => {
21
- if (ts.isObjectLiteralExpression(node)) {
22
- return node;
23
- }
24
- return ts.forEachChild(node, findObjectLiteral);
25
- };
26
- const objectLiteral = findObjectLiteral(sourceFile);
27
- if (!objectLiteral) {
28
- return result;
29
- }
30
- for (const property of objectLiteral.properties) {
31
- if (ts.isPropertyAssignment(property)) {
32
- let key;
33
- if (ts.isIdentifier(property.name)) {
34
- key = property.name.text;
35
- } else if (ts.isStringLiteral(property.name)) {
36
- key = property.name.text;
37
- } else if (ts.isComputedPropertyName(property.name)) {
38
- key = property.name.getText(sourceFile);
39
- } else {
40
- continue;
41
- }
42
- const value = property.initializer.getText(sourceFile);
43
- result[key] = value;
44
- } else if (ts.isShorthandPropertyAssignment(property)) {
45
- const key = property.name.text;
46
- result[key] = key;
47
- }
48
- }
49
- return result;
50
- };
51
- function findFirstChildByKind(node, kind) {
52
- let element;
53
- forEachChild(node, (child) => {
54
- if (child.kind !== kind || !!element) {
55
- return;
56
- }
57
- element = child;
58
- });
59
- return element;
60
- }
61
- function peelCallTarget(expr) {
62
- const chain = [];
63
- let hasOptional = false;
64
- let e = expr;
65
- let baseAccess;
66
- while (true) {
67
- if (ts.isPropertyAccessExpression(e)) {
68
- chain.unshift(e.name.text);
69
- hasOptional = hasOptional || !!e.questionDotToken;
70
- baseAccess = e;
71
- e = e.expression;
72
- continue;
73
- }
74
- if (ts.isElementAccessExpression(e)) {
75
- const arg = e.argumentExpression;
76
- const key = ts.isStringLiteral(arg) || ts.isIdentifier(arg) ? arg.text : "<computed>";
77
- chain.unshift(key);
78
- hasOptional = hasOptional || !!e.questionDotToken;
79
- baseAccess = e;
80
- e = e.expression;
81
- continue;
82
- }
83
- break;
84
- }
85
- return {
86
- root: e,
87
- basePropAccess: baseAccess,
88
- chain,
89
- invoked: chain.length ? chain[chain.length - 1] : void 0,
90
- hasOptional
91
- };
92
- }
93
- function getAncestorsBy(node, predicate) {
94
- let currentNode = node;
95
- const nodes = [];
96
- while (currentNode) {
97
- if (predicate(currentNode)) {
98
- nodes.push(currentNode);
99
- }
100
- const parent = currentNode?.parent;
101
- if (!parent) {
102
- break;
103
- }
104
- currentNode = parent;
105
- }
106
- return nodes;
107
- }
108
- function getAncestorsByKind(node, kind) {
109
- return getAncestorsBy(node, (node2) => node2.kind === kind);
110
- }
111
- function getAncestorByKind(node, kinds) {
112
- let currentNode = node;
113
- while (currentNode) {
114
- if (kinds.includes(currentNode.kind)) {
115
- return currentNode;
116
- }
117
- currentNode = currentNode.parent;
118
- }
119
- return void 0;
120
- }
121
- function getAllExpressions(node) {
122
- const expressions = [];
123
- function visit(currentNode) {
124
- if (!currentNode)
125
- return;
126
- if (ts.isExpression(currentNode)) {
127
- expressions.push(currentNode);
128
- }
129
- ts.forEachChild(currentNode, visit);
130
- }
131
- visit(node);
132
- return expressions;
133
- }
134
- function getDescendantsBy(node, predicate) {
135
- let foundElements = [];
136
- let queue = [node];
137
- while (queue.length > 0) {
138
- const currentNode = queue.shift();
139
- const children = Array.from(
140
- currentNode?.getChildren(node.getSourceFile()) || []
141
- );
142
- foundElements = [
143
- ...foundElements,
144
- ...children.filter((node2) => predicate(node2))
145
- ];
146
- queue = [...queue, ...children];
147
- }
148
- return foundElements;
149
- }
150
- function getDescendantsByKind(node, kind) {
151
- return getDescendantsBy(node, (node2) => node2.kind === kind);
152
- }
153
- function getDescendantsByKinds(node, kinds) {
154
- return getDescendantsBy(node, (node2) => kinds.includes(node2.kind));
155
- }
156
- function getDescendantByName(node, name) {
157
- return getDescendantsBy(
158
- node,
159
- (node2) => getNameByIdentifier(node2) === name
160
- );
161
- }
162
- function findFirstChildByKinds(node, kinds) {
163
- let element;
164
- forEachChild(node, (child) => {
165
- if (!kinds.includes(child.kind) || !!element) {
166
- return;
167
- }
168
- element = child;
169
- });
170
- return element;
171
- }
172
- function findChildrenByKinds(node, kinds) {
173
- const elements = [];
174
- forEachChild(node, (child) => {
175
- if (!kinds.includes(child.kind)) {
176
- return;
177
- }
178
- elements.push(child);
179
- });
180
- return elements;
181
- }
182
- function findChildrenByKind(node, kind) {
183
- return findChildrenByKinds(node, [kind]);
184
- }
185
- function findChildrenByName(sourceFile, node, name) {
186
- const elements = [];
187
- forEachChild(node, (child) => {
188
- const [nameNode] = findChildrenByKind(child, SyntaxKind.Identifier);
189
- if (nameNode?.getText(sourceFile) !== name) {
190
- return;
191
- }
192
- elements.push(child);
193
- });
194
- return elements;
195
- }
196
- function findChildByName(sourceFile, node, name) {
197
- let element;
198
- forEachChild(node, (child) => {
199
- const [nameNode] = findChildrenByKind(child, SyntaxKind.Identifier);
200
- if (nameNode?.getText(sourceFile) !== name) {
201
- return;
202
- }
203
- element = child;
204
- });
205
- return element;
206
- }
207
- function findChildByIndex(node, index) {
208
- let element;
209
- let i = 0;
210
- forEachChild(node, (child) => {
211
- if (i !== index) {
212
- i++;
213
- return;
214
- }
215
- element = child;
216
- });
217
- return element;
218
- }
219
- function consoleAllChildren(sourceFile, node) {
220
- forEachChild(node, (child) => {
221
- console.log(child.kind, child.getText(sourceFile));
222
- });
223
- }
224
- function parseArrayLiteralExpression(sourceFile, node) {
225
- return findChildrenByKind(node, SyntaxKind.ObjectLiteralExpression).map(
226
- (obj) => {
227
- return parseObjectLiteralExpression(sourceFile, obj);
228
- }
229
- );
230
- }
231
- function parseObjectLiteralExpression(sourceFile, node) {
232
- return findChildrenByKind(node, SyntaxKind.PropertyAssignment).reduce(
233
- (state, currentNode) => {
234
- const [property, _, value] = Array.from(
235
- currentNode?.getChildren(sourceFile) || []
236
- );
237
- if (value.kind === SyntaxKind.ObjectLiteralExpression) {
238
- const obj = parseObjectLiteralExpression(sourceFile, value);
239
- return {
240
- ...state,
241
- ...Object.keys(obj).reduce((s, k) => {
242
- return {
243
- ...s,
244
- [property.getText(sourceFile) + "." + k]: escapeValue(obj[k])
245
- };
246
- }, {})
247
- };
248
- }
249
- return {
250
- ...state,
251
- [property.getText(sourceFile)]: escapeValue(value.getText(sourceFile))
252
- };
253
- },
254
- {}
255
- );
256
- }
257
- const isComment = (content) => content.match(/^\s*\/\/.*/) !== null;
258
- const extractCommentContent = (content) => content.replace(/^\s*\/\/\s*/, "");
259
- const getComments = (nodeContent) => {
260
- const blockLines = nodeContent.split("\r\n");
261
- const comments = blockLines.filter(isComment).map(extractCommentContent);
262
- return comments;
263
- };
264
- function findTypeDeclarationByName(sourceFile, typeName) {
265
- let result;
266
- function visit(node) {
267
- if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
268
- const name = node.name?.getText(sourceFile);
269
- if (name === typeName) {
270
- result = node;
271
- return;
272
- }
273
- }
274
- ts.forEachChild(node, visit);
275
- }
276
- visit(sourceFile);
277
- return result;
278
- }
279
- const getSolutionSourceFiles = (program) => {
280
- const allSourceFiles = program.getSourceFiles();
281
- const sourceFiles = allSourceFiles.filter((sourceFile) => {
282
- const fileName = sourceFile.fileName;
283
- if (fileName.includes("node_modules")) {
284
- return false;
285
- }
286
- if (fileName.endsWith(".d.ts")) {
287
- return false;
288
- }
289
- return true;
290
- });
291
- return sourceFiles;
292
- };
293
- function readSourceFiles(tsConfigPath) {
294
- const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
295
- if (configFile.error) {
296
- throw new Error("Error reading tsconfig at " + tsConfigPath);
297
- }
298
- const parsedConfig = ts.parseJsonConfigFileContent(
299
- configFile.config,
300
- ts.sys,
301
- path.dirname(tsConfigPath)
302
- );
303
- const program = ts.createProgram(
304
- parsedConfig.fileNames,
305
- parsedConfig.options
306
- );
307
- return { sourceFiles: getSolutionSourceFiles(program), program };
308
- }
309
- export {
310
- consoleAllChildren,
311
- escapeValue,
312
- findChildByIndex,
313
- findChildByName,
314
- findChildrenByKind,
315
- findChildrenByKinds,
316
- findChildrenByName,
317
- findFirstChildByKind,
318
- findFirstChildByKinds,
319
- findTypeDeclarationByName,
320
- getAllExpressions,
321
- getAncestorByKind,
322
- getAncestorsBy,
323
- getAncestorsByKind,
324
- getComments,
325
- getDescendantByName,
326
- getDescendantsBy,
327
- getDescendantsByKind,
328
- getDescendantsByKinds,
329
- getSolutionSourceFiles,
330
- parseArrayLiteralExpression,
331
- parseObjectLiteralExpression,
332
- parseObjectLiteralString,
333
- peelCallTarget,
334
- readSourceFiles
335
- };
package/cypher/index.js DELETED
@@ -1 +0,0 @@
1
- export * from "./lib/index.js";
@@ -1,135 +0,0 @@
1
- class ConditionEvaluator {
2
- // @TODO: apply where at match level
3
- /**
4
- * Apply WHERE clause filtering to matches
5
- */
6
- applyWhereClauseIfNeeded(matches, where) {
7
- return matches.filter((match) => {
8
- const conditionResults = where.conditions.map(
9
- (condition) => this.evaluateCondition(match, condition)
10
- );
11
- const logic = where.logic || "AND";
12
- if (logic === "AND") {
13
- return conditionResults.every((result) => result === true);
14
- } else {
15
- return conditionResults.some((result) => result === true);
16
- }
17
- });
18
- }
19
- // @fixme move to utils
20
- /**
21
- * Escape special regex characters in a string
22
- */
23
- escapeRegex(str) {
24
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
- }
26
- /**
27
- * Evaluate a single WHERE condition
28
- * Supports flat properties only (no nesting, like Neo4j Cypher)
29
- * Handles null values from optional matches (null values fail the condition)
30
- */
31
- evaluateCondition(match, condition) {
32
- const value = match[condition.variable];
33
- if ((condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") && !condition.property) {
34
- const isNull = value === null || value === void 0;
35
- return condition.operator === "IS NULL" ? isNull : !isNull;
36
- }
37
- if (!value || value === null) {
38
- return false;
39
- }
40
- let baseObject;
41
- if ("id" in value) {
42
- baseObject = value;
43
- } else if ("type" in value && "source" in value) {
44
- baseObject = value;
45
- } else {
46
- return false;
47
- }
48
- let actualValue;
49
- if (!condition.property) {
50
- actualValue = baseObject;
51
- } else {
52
- actualValue = baseObject[condition.property];
53
- if (actualValue === void 0) {
54
- if (condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") {
55
- actualValue = void 0;
56
- } else {
57
- return false;
58
- }
59
- }
60
- }
61
- const conditionValue = condition.value;
62
- switch (condition.operator) {
63
- case "=":
64
- return actualValue === conditionValue;
65
- case "!=":
66
- return actualValue !== conditionValue;
67
- case ">":
68
- return actualValue > conditionValue;
69
- case "<":
70
- return actualValue < conditionValue;
71
- case ">=":
72
- return actualValue >= conditionValue;
73
- case "<=":
74
- return actualValue <= conditionValue;
75
- case "IS NULL":
76
- return actualValue === null || actualValue === void 0;
77
- case "IS NOT NULL":
78
- return actualValue !== null && actualValue !== void 0;
79
- case "IN":
80
- if (Array.isArray(conditionValue)) {
81
- if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
82
- const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
83
- return extractedValues.includes(actualValue);
84
- }
85
- return conditionValue.includes(actualValue);
86
- }
87
- return false;
88
- case "NOT IN":
89
- if (Array.isArray(conditionValue)) {
90
- if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
91
- const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
92
- return !extractedValues.includes(actualValue);
93
- }
94
- return !conditionValue.includes(actualValue);
95
- }
96
- return true;
97
- case "STARTS WITH":
98
- return this.testRegex(
99
- new RegExp(`^${this.escapeRegex(conditionValue)}`),
100
- actualValue
101
- );
102
- case "ENDS WITH":
103
- return this.testRegex(
104
- new RegExp(`${this.escapeRegex(conditionValue)}$`),
105
- actualValue
106
- );
107
- case "CONTAINS":
108
- return this.testRegex(
109
- new RegExp(this.escapeRegex(conditionValue)),
110
- actualValue
111
- );
112
- case "=~":
113
- return this.testRegex(new RegExp(conditionValue), actualValue);
114
- default:
115
- throw new Error(`Unsupported operator: ${condition.operator}`);
116
- }
117
- }
118
- testRegex(regexp, actualValue) {
119
- if (actualValue === null || actualValue === void 0) {
120
- return false;
121
- }
122
- if (typeof actualValue !== "string" && typeof actualValue !== "number" && typeof actualValue !== "boolean") {
123
- return false;
124
- }
125
- const regexStr = typeof actualValue === "string" ? actualValue : String(actualValue);
126
- try {
127
- return regexp.test(regexStr);
128
- } catch {
129
- return false;
130
- }
131
- }
132
- }
133
- export {
134
- ConditionEvaluator
135
- };
@@ -1,60 +0,0 @@
1
- import { ConditionEvaluator } from "./condition-evaluator.js";
2
- import { MatchEngine } from "./match-engine.js";
3
- import { PatternMatcher } from "./pattern-matcher.js";
4
- import { RelationshipNavigator } from "./relationship-navigator.js";
5
- import { ResultFormatter } from "./result-formatter.js";
6
- import { TraverseEngine } from "./traverse-engine.js";
7
- class CypherExecutor {
8
- constructor(graph) {
9
- this.graph = graph;
10
- this.conditionEvaluator = new ConditionEvaluator();
11
- const patternMatcher = new PatternMatcher();
12
- const relationshipNavigator = new RelationshipNavigator(patternMatcher);
13
- const traverseEngine = new TraverseEngine(
14
- patternMatcher,
15
- relationshipNavigator
16
- );
17
- this.matchEngine = new MatchEngine(patternMatcher, traverseEngine);
18
- this.resultFormatter = new ResultFormatter();
19
- }
20
- /**
21
- * Execute a parsed Cypher query
22
- * @param extractData - If true (default), returns node.data. If false, returns full GraphNode objects.
23
- * For edges, always returns the full GraphEdge object (edges have no data property).
24
- * Returns results where keys are variable names and values are either node data or edge objects
25
- */
26
- execute(query) {
27
- if (query.statements.length === 0) {
28
- throw new Error("Query must have at least one statement");
29
- }
30
- if (query.statements[query.statements.length - 1].type !== "ReturnStatement") {
31
- throw new Error("MATCH Query must have a RETURN statement");
32
- }
33
- let results = [];
34
- for (const statement of query.statements) {
35
- if (statement.type === "MatchStatement") {
36
- results = this.matchEngine.processMatchStatement(
37
- this.graph,
38
- statement,
39
- results
40
- );
41
- } else if (statement.type === "WhereStatement") {
42
- results = this.conditionEvaluator.applyWhereClauseIfNeeded(
43
- results,
44
- statement
45
- );
46
- } else if (statement.type === "OrderByStatement") {
47
- results = this.resultFormatter.sortResults(results, statement);
48
- }
49
- }
50
- const returnStatement = query.statements[query.statements.length - 1];
51
- let formattedResults = this.resultFormatter.formatResults(
52
- results,
53
- returnStatement
54
- );
55
- return formattedResults;
56
- }
57
- }
58
- export {
59
- CypherExecutor
60
- };
File without changes
@@ -1,130 +0,0 @@
1
- import { Annonymizer } from "./utils.js";
2
- class MatchEngine {
3
- constructor(patternMatcher, traverseEngine) {
4
- this.patternMatcher = patternMatcher;
5
- this.traverseEngine = traverseEngine;
6
- this.annonymizer = new Annonymizer();
7
- }
8
- processMatchStatement(graph, match, existingResults = []) {
9
- if (match.patterns.length === 0)
10
- return existingResults;
11
- const starting = match.patterns[0];
12
- const allResults = [];
13
- this.patternMatcher.filterMatchingNodes(graph, starting).forEach((node) => {
14
- const matches = this.traverseEngine.traverseRelationships(
15
- graph,
16
- node,
17
- starting.variable || this.annonymizer.generate(node),
18
- match.patterns.slice(1),
19
- match.relationships
20
- );
21
- allResults.push(...matches);
22
- });
23
- if (existingResults.length > 0) {
24
- if (match.optional) {
25
- return this.mergeOptionalResults(existingResults, allResults, match);
26
- } else {
27
- return this.mergeResults(existingResults, allResults);
28
- }
29
- }
30
- return allResults;
31
- }
32
- mergeResults(existingResults, newResults) {
33
- if (existingResults.length === 0) {
34
- return newResults;
35
- }
36
- const mergedResults = [];
37
- for (const existingResult of existingResults) {
38
- for (const newResult of newResults) {
39
- if (this.canMergeResult(existingResult, newResult)) {
40
- mergedResults.push({ ...existingResult, ...newResult });
41
- }
42
- }
43
- }
44
- return mergedResults;
45
- }
46
- /**
47
- * Merge optional match results with existing results (left outer join)
48
- * - Keeps all existing results
49
- * - Merges optional results where variables overlap
50
- * - Adds nulls for optional variables when no match is found
51
- */
52
- mergeOptionalResults(existingResults, optionalResults, match) {
53
- if (existingResults.length === 0) {
54
- return optionalResults;
55
- }
56
- const optionalVarNames = /* @__PURE__ */ new Set();
57
- for (const pattern of match.patterns) {
58
- if (pattern.variable) {
59
- optionalVarNames.add(pattern.variable);
60
- }
61
- }
62
- if (match.relationships) {
63
- for (const rel of match.relationships) {
64
- if (rel.variable) {
65
- optionalVarNames.add(rel.variable);
66
- }
67
- }
68
- }
69
- if (optionalResults.length === 0) {
70
- return existingResults.map((result) => {
71
- const resultWithNulls = { ...result };
72
- for (const varName of optionalVarNames) {
73
- if (!(varName in resultWithNulls)) {
74
- resultWithNulls[varName] = null;
75
- }
76
- }
77
- return resultWithNulls;
78
- });
79
- }
80
- for (const optionalResult of optionalResults) {
81
- for (const key in optionalResult) {
82
- optionalVarNames.add(key);
83
- }
84
- }
85
- const mergedResults = [];
86
- for (const existingResult of existingResults) {
87
- let hasMatch = false;
88
- for (const optionalResult of optionalResults) {
89
- if (this.canMergeResult(existingResult, optionalResult)) {
90
- mergedResults.push({ ...existingResult, ...optionalResult });
91
- hasMatch = true;
92
- }
93
- }
94
- if (!hasMatch) {
95
- const resultWithNulls = { ...existingResult };
96
- for (const varName of optionalVarNames) {
97
- if (!(varName in resultWithNulls)) {
98
- resultWithNulls[varName] = null;
99
- }
100
- }
101
- mergedResults.push(resultWithNulls);
102
- }
103
- }
104
- return mergedResults;
105
- }
106
- /**
107
- * Check if additionalResult can be merged with existingResult
108
- * Returns true if shared variables match (same node/edge) or if there are no conflicts
109
- * Does NOT mutate any parameters
110
- */
111
- canMergeResult(additionalResult, existingResult) {
112
- for (const key in additionalResult) {
113
- if (key in existingResult) {
114
- const existing = existingResult[key];
115
- const additional = additionalResult[key];
116
- if (existing !== null && additional !== null) {
117
- if ("id" in existing && "id" in additional && existing.id !== additional.id) {
118
- return false;
119
- }
120
- } else if (existing !== additional) {
121
- return false;
122
- }
123
- }
124
- }
125
- return true;
126
- }
127
- }
128
- export {
129
- MatchEngine
130
- };