flowquery 1.0.25 → 1.0.27

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 (54) hide show
  1. package/.github/workflows/release.yml +1 -0
  2. package/.husky/pre-commit +3 -2
  3. package/dist/flowquery.min.js +1 -1
  4. package/dist/parsing/expressions/reference.d.ts +1 -0
  5. package/dist/parsing/expressions/reference.d.ts.map +1 -1
  6. package/dist/parsing/expressions/reference.js +3 -0
  7. package/dist/parsing/expressions/reference.js.map +1 -1
  8. package/dist/parsing/parser.d.ts.map +1 -1
  9. package/dist/parsing/parser.js +19 -4
  10. package/dist/parsing/parser.js.map +1 -1
  11. package/docs/flowquery.min.js +1 -1
  12. package/flowquery-py/pyproject.toml +1 -1
  13. package/flowquery-py/src/parsing/parser.py +14 -0
  14. package/flowquery-py/tests/compute/test_runner.py +67 -1
  15. package/flowquery-py/tests/parsing/test_parser.py +18 -0
  16. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  17. package/jest.config.js +6 -9
  18. package/misc/apps/RAG/data/chats.json +302 -0
  19. package/misc/apps/RAG/data/emails.json +182 -0
  20. package/misc/apps/RAG/data/events.json +226 -0
  21. package/misc/apps/RAG/data/files.json +172 -0
  22. package/misc/apps/RAG/data/users.json +158 -0
  23. package/misc/apps/RAG/jest.config.js +21 -0
  24. package/misc/apps/RAG/package.json +9 -2
  25. package/misc/apps/RAG/src/App.tsx +5 -5
  26. package/misc/apps/RAG/src/components/ChatContainer.tsx +53 -124
  27. package/misc/apps/RAG/src/components/FlowQueryAgent.ts +151 -157
  28. package/misc/apps/RAG/src/components/index.ts +1 -1
  29. package/misc/apps/RAG/src/graph/index.ts +19 -0
  30. package/misc/apps/RAG/src/graph/initializeGraph.ts +254 -0
  31. package/misc/apps/RAG/src/index.tsx +25 -13
  32. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +146 -231
  33. package/misc/apps/RAG/src/prompts/index.ts +4 -4
  34. package/misc/apps/RAG/src/tests/graph.test.ts +35 -0
  35. package/misc/apps/RAG/src/utils/FlowQueryExecutor.ts +20 -21
  36. package/misc/apps/RAG/src/utils/FlowQueryExtractor.ts +35 -30
  37. package/misc/apps/RAG/src/utils/Llm.ts +248 -0
  38. package/misc/apps/RAG/src/utils/index.ts +7 -4
  39. package/misc/apps/RAG/tsconfig.json +4 -3
  40. package/misc/apps/RAG/webpack.config.js +40 -40
  41. package/package.json +1 -1
  42. package/src/parsing/expressions/reference.ts +8 -5
  43. package/src/parsing/parser.ts +19 -4
  44. package/tests/compute/runner.test.ts +47 -1
  45. package/tests/parsing/parser.test.ts +16 -0
  46. package/misc/apps/RAG/src/plugins/README.md +0 -139
  47. package/misc/apps/RAG/src/plugins/index.ts +0 -72
  48. package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +0 -70
  49. package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +0 -65
  50. package/misc/apps/RAG/src/plugins/loaders/Form.ts +0 -594
  51. package/misc/apps/RAG/src/plugins/loaders/Llm.ts +0 -450
  52. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +0 -101
  53. package/misc/apps/RAG/src/plugins/loaders/Table.ts +0 -274
  54. package/misc/apps/RAG/src/plugins/loaders/Weather.ts +0 -138
@@ -3,9 +3,9 @@ import Identifier from "./identifier";
3
3
 
4
4
  /**
5
5
  * Represents a reference to a previously defined variable or expression.
6
- *
6
+ *
7
7
  * References point to values defined earlier in the query (e.g., in WITH or LOAD statements).
8
- *
8
+ *
9
9
  * @example
10
10
  * ```typescript
11
11
  * const ref = new Reference("myVar", previousNode);
@@ -14,10 +14,10 @@ import Identifier from "./identifier";
14
14
  */
15
15
  class Reference extends Identifier {
16
16
  private _referred: ASTNode | undefined = undefined;
17
-
17
+
18
18
  /**
19
19
  * Creates a new Reference to a variable.
20
- *
20
+ *
21
21
  * @param value - The identifier name
22
22
  * @param referred - The node this reference points to (optional)
23
23
  */
@@ -25,6 +25,9 @@ class Reference extends Identifier {
25
25
  super(value);
26
26
  this._referred = referred;
27
27
  }
28
+ public get referred(): ASTNode | undefined {
29
+ return this._referred;
30
+ }
28
31
  public set referred(node: ASTNode) {
29
32
  this._referred = node;
30
33
  }
@@ -39,4 +42,4 @@ class Reference extends Identifier {
39
42
  }
40
43
  }
41
44
 
42
- export default Reference;
45
+ export default Reference;
@@ -220,6 +220,7 @@ class Parser extends BaseParser {
220
220
  }
221
221
 
222
222
  private parseWhere(): Where | null {
223
+ this.skipWhitespaceAndComments();
223
224
  if (!this.token.isWhere()) {
224
225
  return null;
225
226
  }
@@ -437,8 +438,15 @@ class Parser extends BaseParser {
437
438
  node.identifier = identifier;
438
439
  this.variables.set(identifier, node);
439
440
  } else if (identifier !== null) {
440
- const reference = this.variables.get(identifier);
441
- if (reference === undefined || reference.constructor !== Node) {
441
+ let reference = this.variables.get(identifier);
442
+ // Resolve through Expression -> Reference -> Node (e.g., after WITH)
443
+ if (reference instanceof Expression && reference.firstChild() instanceof Reference) {
444
+ const inner = (reference.firstChild() as Reference).referred;
445
+ if (inner instanceof Node) {
446
+ reference = inner;
447
+ }
448
+ }
449
+ if (reference === undefined || !(reference instanceof Node)) {
442
450
  throw new Error(`Undefined node reference: ${identifier}`);
443
451
  }
444
452
  node = new NodeReference(node, reference);
@@ -628,8 +636,15 @@ class Parser extends BaseParser {
628
636
  relationship.identifier = variable;
629
637
  this.variables.set(variable, relationship);
630
638
  } else if (variable !== null) {
631
- const reference = this.variables.get(variable);
632
- if (reference === undefined || reference.constructor !== Relationship) {
639
+ let reference = this.variables.get(variable);
640
+ // Resolve through Expression -> Reference -> Relationship (e.g., after WITH)
641
+ if (reference instanceof Expression && reference.firstChild() instanceof Reference) {
642
+ const inner = (reference.firstChild() as Reference).referred;
643
+ if (inner instanceof Relationship) {
644
+ reference = inner;
645
+ }
646
+ }
647
+ if (reference === undefined || !(reference instanceof Relationship)) {
633
648
  throw new Error(`Undefined relationship reference: ${variable}`);
634
649
  }
635
650
  relationship = new RelationshipReference(relationship, reference);
@@ -414,7 +414,7 @@ test("Test lookup which is keyword", async () => {
414
414
  expect(results[0]).toEqual({ aa: 1 });
415
415
  });
416
416
 
417
- test("Test lookup which is keyword", async () => {
417
+ test("Test lookup which is keyword with bracket notation", async () => {
418
418
  const runner = new Runner('RETURN {return: 1}["return"] as aa');
419
419
  await runner.run();
420
420
  const results = runner.results;
@@ -1556,3 +1556,49 @@ test("Test reserved keywords as relationship types and labels", async () => {
1556
1556
  expect(results.length).toBe(1);
1557
1557
  expect(results[0]).toEqual({ name1: "Node 1", name2: "Node 2" });
1558
1558
  });
1559
+
1560
+ test("Test match with node reference passed through WITH", async () => {
1561
+ await new Runner(`
1562
+ CREATE VIRTUAL (:User) AS {
1563
+ UNWIND [
1564
+ {id: 1, name: 'Alice', mail: 'alice@test.com', jobTitle: 'CEO'},
1565
+ {id: 2, name: 'Bob', mail: 'bob@test.com', jobTitle: 'VP'},
1566
+ {id: 3, name: 'Carol', mail: 'carol@test.com', jobTitle: 'VP'},
1567
+ {id: 4, name: 'Dave', mail: 'dave@test.com', jobTitle: 'Engineer'}
1568
+ ] AS record
1569
+ RETURN record.id AS id, record.name AS name, record.mail AS mail, record.jobTitle AS jobTitle
1570
+ }
1571
+ `).run();
1572
+ await new Runner(`
1573
+ CREATE VIRTUAL (:User)-[:MANAGES]-(:User) AS {
1574
+ UNWIND [
1575
+ {left_id: 1, right_id: 2},
1576
+ {left_id: 1, right_id: 3},
1577
+ {left_id: 2, right_id: 4}
1578
+ ] AS record
1579
+ RETURN record.left_id AS left_id, record.right_id AS right_id
1580
+ }
1581
+ `).run();
1582
+ // Equivalent to:
1583
+ // MATCH (ceo:User)-[:MANAGES]->(dr1:User)
1584
+ // WHERE ceo.jobTitle = 'CEO'
1585
+ // WITH ceo, dr1
1586
+ // MATCH (ceo)-[:MANAGES]->(dr2:User)
1587
+ // WHERE dr1.mail <> dr2.mail
1588
+ // RETURN ceo, dr1, dr2
1589
+ const match = new Runner(`
1590
+ MATCH (ceo:User)-[:MANAGES]->(dr1:User)
1591
+ WHERE ceo.jobTitle = 'CEO'
1592
+ WITH ceo, dr1
1593
+ MATCH (ceo)-[:MANAGES]->(dr2:User)
1594
+ WHERE dr1.mail <> dr2.mail
1595
+ RETURN ceo.name AS ceo, dr1.name AS dr1, dr2.name AS dr2
1596
+ `);
1597
+ await match.run();
1598
+ const results = match.results;
1599
+ // CEO (Alice) manages Bob and Carol. All distinct pairs:
1600
+ // (Alice, Bob, Carol) and (Alice, Carol, Bob)
1601
+ expect(results.length).toBe(2);
1602
+ expect(results[0]).toEqual({ ceo: "Alice", dr1: "Bob", dr2: "Carol" });
1603
+ expect(results[1]).toEqual({ ceo: "Alice", dr1: "Carol", dr2: "Bob" });
1604
+ });
@@ -867,6 +867,22 @@ test("Test node with properties", () => {
867
867
  expect(node.properties.get("value")?.value()).toBe("hello");
868
868
  });
869
869
 
870
+ test("Test WHERE in CREATE VIRTUAL sub-query", () => {
871
+ const parser = new Parser();
872
+ const ast = parser.parse(`
873
+ CREATE VIRTUAL (:Email)-[:HAS_ATTACHMENT]-(:File) AS {
874
+ LOAD JSON FROM '/data/emails.json' AS email
875
+ WHERE email.hasAttachments = true
876
+ UNWIND email.attachments AS fileId
877
+ RETURN email.id AS left_id, fileId AS right_id
878
+ }
879
+ `);
880
+ const create = ast.firstChild() as CreateRelationship;
881
+ expect(create.relationship).not.toBeNull();
882
+ expect(create.relationship!.type).toBe("HAS_ATTACHMENT");
883
+ expect(create.statement).not.toBeNull();
884
+ });
885
+
870
886
  test("Test relationship with properties", () => {
871
887
  const parser = new Parser();
872
888
  const ast = parser.parse("MATCH (:Person)-[r:LIKES{since: 2022}]->(:Food) return a");
@@ -1,139 +0,0 @@
1
- # FlowQuery Plugin System
2
-
3
- This folder contains the plugin system for adding async data loader functions to FlowQuery.
4
-
5
- ## Quick Start
6
-
7
- 1. Create a new file in `loaders/` (e.g., `my-api.ts`)
8
- 2. Define your plugin following the `AsyncLoaderPlugin` interface
9
- 3. Import and add it to the `allPlugins` array in `index.ts`
10
-
11
- ## Creating a Plugin
12
-
13
- ### Basic Plugin Structure
14
-
15
- ```typescript
16
- import { AsyncLoaderPlugin } from '../types';
17
-
18
- // Your async data provider function
19
- async function* myDataProvider(arg1: string, arg2?: number): AsyncGenerator<any, void, unknown> {
20
- const response = await fetch(`https://api.example.com/${arg1}`);
21
- const data = await response.json();
22
-
23
- // Yield items one at a time (for arrays)
24
- if (Array.isArray(data)) {
25
- for (const item of data) {
26
- yield item;
27
- }
28
- } else {
29
- yield data;
30
- }
31
- }
32
-
33
- // Export the plugin definition
34
- export const myPlugin: AsyncLoaderPlugin = {
35
- name: 'myData', // Function name in FlowQuery
36
- provider: myDataProvider,
37
- metadata: {
38
- description: 'Fetches data from My API',
39
- category: 'data',
40
- parameters: [
41
- { name: 'arg1', description: 'First argument', type: 'string', required: true },
42
- { name: 'arg2', description: 'Optional second arg', type: 'number', required: false }
43
- ],
44
- output: {
45
- description: 'Data item',
46
- type: 'object',
47
- properties: {
48
- id: { description: 'Item ID', type: 'number' },
49
- value: { description: 'Item value', type: 'string' }
50
- }
51
- },
52
- examples: [
53
- "LOAD JSON FROM myData('users') AS item RETURN item.id, item.value"
54
- ]
55
- }
56
- };
57
-
58
- export default myPlugin;
59
- ```
60
-
61
- ### Register the Plugin
62
-
63
- Add your plugin to `index.ts`:
64
-
65
- ```typescript
66
- import myPlugin from './loaders/my-api';
67
-
68
- const allPlugins: AsyncLoaderPlugin[] = [
69
- // ... existing plugins
70
- myPlugin,
71
- ];
72
- ```
73
-
74
- ## Using Plugins in FlowQuery
75
-
76
- Once registered, use your plugin in FlowQuery queries:
77
-
78
- ```sql
79
- -- Fetch data from your custom source
80
- LOAD JSON FROM myData('users') AS user
81
- WHERE user.active = true
82
- RETURN user.name, user.email
83
-
84
- -- Combine with filtering and ordering
85
- LOAD JSON FROM myData('products', 50) AS product
86
- WHERE product.price > 10
87
- ORDER BY product.name ASC
88
- RETURN product.name, product.price
89
- ```
90
-
91
- ## Available Built-in Plugins
92
-
93
- | Plugin | Description | Example |
94
- |--------|-------------|---------|
95
- | `fetchJson` | Fetch JSON from any URL | `fetchJson('https://api.example.com/data')` |
96
-
97
- > **Note**: Additional plugins may be registered. Check `index.ts` for the current list of available plugins.
98
-
99
- ## Plugin Types
100
-
101
- ### AsyncLoaderPlugin
102
-
103
- ```typescript
104
- interface AsyncLoaderPlugin {
105
- name: string; // Function name (lowercased when registered)
106
- provider: AsyncDataProvider; // The async generator/function
107
- metadata?: PluginMetadata; // Optional metadata for LLM consumption
108
- }
109
- ```
110
-
111
- ### AsyncDataProvider
112
-
113
- ```typescript
114
- type AsyncDataProvider = (...args: any[]) => AsyncGenerator<any, void, unknown> | Promise<any>;
115
- ```
116
-
117
- Plugins can return either:
118
- - An `AsyncGenerator` that yields items one at a time
119
- - A `Promise` that resolves to an array or single value
120
-
121
- ### PluginMetadata
122
-
123
- ```typescript
124
- interface PluginMetadata {
125
- name: string;
126
- description: string;
127
- category?: string;
128
- parameters?: ParameterSchema[];
129
- output?: OutputSchema;
130
- examples?: string[];
131
- }
132
- ```
133
-
134
- ## Tips
135
-
136
- 1. **Use generators for large datasets** - Yield items one at a time to avoid memory issues
137
- 2. **Add comprehensive metadata** - This helps LLMs understand and use your functions
138
- 3. **Handle errors gracefully** - Throw descriptive errors for API failures
139
- 4. **Include examples** - Show users how to use your plugin in FlowQuery
@@ -1,72 +0,0 @@
1
- /**
2
- * Plugin loader - automatically discovers and loads all plugins.
3
- *
4
- * To add a new plugin:
5
- * 1. Create a new file in the `loaders/` directory
6
- * 2. Add the @FunctionDef decorator with category: 'async' to your loader class
7
- * 3. Import the class in this file (the decorator auto-registers with FlowQuery)
8
- */
9
-
10
- import FlowQuery from 'flowquery';
11
- import { FunctionMetadata } from 'flowquery/extensibility';
12
-
13
- // Import plugin classes - the @FunctionDef decorator auto-registers them with FlowQuery
14
- // This step is essential to ensure decorators are executed so that plugins are registered with FlowQuery
15
- import './loaders/FetchJson';
16
- import './loaders/CatFacts';
17
- import './loaders/MockData';
18
- import './loaders/Llm';
19
- import './loaders/Table';
20
- import './loaders/Form';
21
- import './loaders/Weather';
22
-
23
- /**
24
- * Initialize plugins.
25
- * Plugins are auto-registered via @FunctionDef decorators when imported.
26
- * This function just logs the registered plugins for debugging.
27
- */
28
- export function initializePlugins(): void {
29
- const plugins = getLoadedPluginNames();
30
- console.log(`FlowQuery plugins loaded: ${plugins.join(', ')}`);
31
- }
32
-
33
- /**
34
- * Get the list of loaded plugin names.
35
- * Uses FlowQuery's introspection to discover registered async providers.
36
- */
37
- export function getLoadedPluginNames(): string[] {
38
- return FlowQuery.listFunctions({ asyncOnly: true }).map(f => f.name);
39
- }
40
-
41
- /**
42
- * Get metadata for all loaded plugins.
43
- * Uses FlowQuery's functions() introspection as the single source of truth.
44
- */
45
- export function getAllPluginMetadata(): FunctionMetadata[] {
46
- return FlowQuery.listFunctions({ asyncOnly: true });
47
- }
48
-
49
- /**
50
- * Get all available async loader plugins by querying FlowQuery directly.
51
- * This is the preferred async method that uses functions() introspection.
52
- *
53
- * @returns Promise resolving to array of plugin metadata
54
- */
55
- export async function getAvailableLoaders(): Promise<FunctionMetadata[]> {
56
- const runner = new FlowQuery(`
57
- WITH functions() AS funcs
58
- UNWIND funcs AS f
59
- WHERE f.isAsyncProvider = true
60
- RETURN f
61
- `);
62
- await runner.run();
63
- return runner.results.map((r: any) => r.expr0 as FunctionMetadata);
64
- }
65
-
66
- // Re-export types for external use
67
- export type { FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema } from 'flowquery/extensibility';
68
- export { FunctionDef } from 'flowquery/extensibility';
69
-
70
- // Re-export standalone loader functions for use outside of FlowQuery
71
- export { llm, llmStream, extractContent } from './loaders/Llm';
72
- export type { LlmOptions, LlmResponse } from './loaders/Llm';
@@ -1,70 +0,0 @@
1
- /**
2
- * Example plugin: Fetch random cat facts from the Cat Facts API.
3
- *
4
- * Usage in FlowQuery:
5
- * CALL catFacts(5) YIELD text, length
6
- */
7
- import { AsyncFunction, FunctionDef } from "flowquery/extensibility";
8
-
9
- const CAT_FACTS_API = "https://catfact.ninja/facts";
10
-
11
- /**
12
- * CatFacts class - fetches random cat facts from the Cat Facts API.
13
- */
14
- @FunctionDef({
15
- description: "Fetches random cat facts from the Cat Facts API (catfact.ninja)",
16
- category: "async",
17
- parameters: [
18
- {
19
- name: "count",
20
- description: "Number of cat facts to fetch",
21
- type: "number",
22
- required: false,
23
- default: 1,
24
- },
25
- ],
26
- output: {
27
- description: "Cat fact object",
28
- type: "object",
29
- properties: {
30
- text: { description: "The cat fact text", type: "string" },
31
- length: { description: "Length of the fact text", type: "number" },
32
- },
33
- },
34
- examples: ["CALL catFacts() YIELD text", "CALL catFacts(5) YIELD text, length"],
35
- })
36
- export class CatFacts extends AsyncFunction {
37
- private readonly apiUrl: string;
38
-
39
- constructor(apiUrl: string = CAT_FACTS_API) {
40
- super();
41
- this.apiUrl = apiUrl;
42
- }
43
-
44
- /**
45
- * Fetches random cat facts from the Cat Facts API.
46
- *
47
- * @param count - Number of cat facts to fetch (default: 1)
48
- */
49
- async *generate(count: number = 1): AsyncGenerator<any, void, unknown> {
50
- const url = `${this.apiUrl}?limit=${count}`;
51
- const response = await fetch(url);
52
-
53
- if (!response.ok) {
54
- throw new Error(`Failed to fetch cat facts: ${response.statusText}`);
55
- }
56
-
57
- const json = await response.json();
58
- const data = json.data || [];
59
-
60
- for (const item of data) {
61
- // Map 'fact' to 'text' for backwards compatibility with existing queries
62
- yield {
63
- text: item.fact,
64
- length: item.length,
65
- };
66
- }
67
- }
68
- }
69
-
70
- export default CatFacts;
@@ -1,65 +0,0 @@
1
- /**
2
- * Example plugin: Fetch JSON data from a URL.
3
- *
4
- * Usage in FlowQuery:
5
- * CALL fetchJson('https://api.example.com/data') YIELD name, value
6
- */
7
- import { AsyncFunction, FunctionDef } from "flowquery/extensibility";
8
-
9
- /**
10
- * FetchJson class - fetches JSON data from a URL and yields items.
11
- */
12
- @FunctionDef({
13
- description:
14
- "Fetches JSON data from a URL. If the response is an array, yields each item individually.",
15
- category: "async",
16
- parameters: [
17
- {
18
- name: "url",
19
- description: "The URL to fetch JSON from",
20
- type: "string",
21
- required: true,
22
- },
23
- {
24
- name: "options",
25
- description: "Optional fetch options (headers, method, etc.)",
26
- type: "object",
27
- required: false,
28
- },
29
- ],
30
- output: {
31
- description: "JSON data items",
32
- type: "object",
33
- },
34
- examples: [
35
- "CALL fetchJson('https://api.example.com/users') YIELD name",
36
- "CALL fetchJson('https://api.example.com/data') YIELD name, active WHERE active = true",
37
- ],
38
- })
39
- export class FetchJson extends AsyncFunction {
40
- /**
41
- * Fetches JSON data from a URL and yields each item if array, or the object itself.
42
- *
43
- * @param url - The URL to fetch JSON from
44
- * @param options - Optional fetch options
45
- */
46
- async *generate(url: string, options?: RequestInit): AsyncGenerator<any, void, unknown> {
47
- const response = await fetch(url, options);
48
-
49
- if (!response.ok) {
50
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
51
- }
52
-
53
- const data = await response.json();
54
-
55
- if (Array.isArray(data)) {
56
- for (const item of data) {
57
- yield item;
58
- }
59
- } else {
60
- yield data;
61
- }
62
- }
63
- }
64
-
65
- export default FetchJson;