flowquery 1.0.15 → 1.0.16

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.
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * FlowQuery System Prompt Generator
3
- *
3
+ *
4
4
  * Generates a system prompt that instructs the LLM to create FlowQuery statements
5
5
  * based on natural language queries, with awareness of available loader plugins.
6
- *
6
+ *
7
7
  * Uses FlowQuery's built-in functions() introspection to dynamically discover
8
8
  * available async data loaders and their metadata.
9
9
  */
10
+ import { FunctionMetadata, OutputSchema, ParameterSchema } from "flowquery/extensibility";
10
11
 
11
- import { FunctionMetadata, ParameterSchema, OutputSchema } from 'flowquery/extensibility';
12
- import { getAllPluginMetadata, getAvailableLoaders } from '../plugins';
12
+ import { getAllPluginMetadata, getAvailableLoaders } from "../plugins";
13
13
 
14
14
  /**
15
15
  * FlowQuery language reference documentation.
@@ -28,25 +28,29 @@ FlowQuery is a declarative query language for data processing pipelines. It uses
28
28
  WITH expression1 AS var1, expression2 AS var2
29
29
  \`\`\`
30
30
 
31
- 2. **LOAD JSON FROM** - Load data from a URL or async data provider
31
+ 2. **LOAD JSON FROM** - Load data from a URL
32
32
  \`\`\`
33
33
  LOAD JSON FROM 'https://api.example.com/data' AS item
34
- LOAD JSON FROM myFunction(arg1, arg2) AS item
34
+ \`\`\`
35
+
36
+ 3. **CALL ... YIELD** - Call an async data provider function and yield its fields
37
+ \`\`\`
38
+ CALL myFunction(arg1, arg2) YIELD field1, field2, field3
35
39
  \`\`\`
36
40
 
37
- **IMPORTANT**: Async data providers (functions used after LOAD JSON FROM) cannot be nested inside other function calls. If you need to pass data from one async provider to another, first load the data into a variable using collect(), then pass that variable:
41
+ **IMPORTANT**: Async data providers (functions used with CALL) cannot be nested inside other function calls. If you need to pass data from one async provider to another, first load the data into a variable using collect(), then pass that variable:
38
42
  \`\`\`
39
43
  // WRONG - async providers cannot be nested:
40
- // LOAD JSON FROM table(mockProducts(5), 'Products') AS card
44
+ // CALL table(mockProducts(5), 'Products') YIELD html
41
45
 
42
46
  // CORRECT - collect data first, then pass to next provider:
43
- LOAD JSON FROM mockProducts(5) AS p
44
- WITH collect(p) AS products
45
- LOAD JSON FROM table(products, 'Products') AS card
46
- RETURN card
47
+ CALL mockProducts(5) YIELD id, name, price
48
+ WITH collect({ id: id, name: name, price: price }) AS products
49
+ CALL table(products, 'Products') YIELD html
50
+ RETURN html
47
51
  \`\`\`
48
52
 
49
- 3. **LOAD JSON FROM ... HEADERS ... POST** - Make HTTP requests with headers and body
53
+ 4. **LOAD JSON FROM ... HEADERS ... POST** - Make HTTP requests with headers and body
50
54
  \`\`\`
51
55
  LOAD JSON FROM 'https://api.example.com/data'
52
56
  HEADERS {
@@ -59,42 +63,26 @@ FlowQuery is a declarative query language for data processing pipelines. It uses
59
63
  } AS response
60
64
  \`\`\`
61
65
 
62
- 4. **UNWIND** - Expand arrays into individual rows
66
+ 5. **UNWIND** - Expand arrays into individual rows
63
67
  \`\`\`
64
68
  UNWIND [1, 2, 3] AS number
65
69
  UNWIND myArray AS item
66
70
  UNWIND range(0, 10) AS index
67
71
  \`\`\`
68
72
 
69
- 5. **WHERE** - Filter results
73
+ 6. **WHERE** - Filter results
70
74
  \`\`\`
71
75
  WHERE item.active = true
72
76
  WHERE user.age > 18 AND user.name CONTAINS 'John'
73
77
  \`\`\`
74
78
 
75
- 6. **RETURN** - Specify output columns
79
+ 7. **RETURN** - Specify output columns
76
80
  \`\`\`
77
81
  RETURN item.name, item.value
78
82
  RETURN item.name AS Name, item.price AS Price
79
83
  RETURN * -- Return all fields
80
84
  \`\`\`
81
85
 
82
- 7. **ORDER BY** - Sort results
83
- \`\`\`
84
- ORDER BY item.name ASC
85
- ORDER BY item.price DESC, item.name ASC
86
- \`\`\`
87
-
88
- 8. **LIMIT** - Limit number of results
89
- \`\`\`
90
- LIMIT 10
91
- \`\`\`
92
-
93
- 9. **SKIP** - Skip a number of results
94
- \`\`\`
95
- SKIP 5
96
- \`\`\`
97
-
98
86
  ### Built-in Functions
99
87
 
100
88
  - **String Functions**: \`size()\`, \`substring()\`, \`trim()\`, \`toLower()\`, \`toUpper()\`, \`split()\`, \`join()\`, \`replace()\`, \`startsWith()\`, \`endsWith()\`, \`contains()\`
@@ -154,9 +142,12 @@ export class FlowQuerySystemPrompt {
154
142
  * Format a parameter schema into a readable string.
155
143
  */
156
144
  private static formatParameter(param: ParameterSchema): string {
157
- const required = param.required ? ' (required)' : ' (optional)';
158
- const defaultVal = param.default !== undefined ? `, default: ${JSON.stringify(param.default)}` : '';
159
- const enumVals = param.enum ? `, values: [${param.enum.map(v => JSON.stringify(v)).join(', ')}]` : '';
145
+ const required = param.required ? " (required)" : " (optional)";
146
+ const defaultVal =
147
+ param.default !== undefined ? `, default: ${JSON.stringify(param.default)}` : "";
148
+ const enumVals = param.enum
149
+ ? `, values: [${param.enum.map((v) => JSON.stringify(v)).join(", ")}]`
150
+ : "";
160
151
  return ` - \`${param.name}\`: ${param.type}${required}${defaultVal}${enumVals} - ${param.description}`;
161
152
  }
162
153
 
@@ -165,18 +156,18 @@ export class FlowQuerySystemPrompt {
165
156
  */
166
157
  private static formatOutput(output: OutputSchema): string {
167
158
  let result = ` Returns: ${output.type} - ${output.description}`;
168
-
159
+
169
160
  if (output.properties) {
170
- result += '\n Output properties:';
161
+ result += "\n Output properties:";
171
162
  for (const [key, prop] of Object.entries(output.properties)) {
172
163
  result += `\n - \`${key}\`: ${prop.type} - ${prop.description}`;
173
164
  }
174
165
  }
175
-
166
+
176
167
  if (output.example) {
177
168
  result += `\n Example output: ${JSON.stringify(output.example, null, 2)}`;
178
169
  }
179
-
170
+
180
171
  return result;
181
172
  }
182
173
 
@@ -185,38 +176,38 @@ export class FlowQuerySystemPrompt {
185
176
  */
186
177
  private static formatPluginDocumentation(plugin: FunctionMetadata): string {
187
178
  const lines: string[] = [];
188
-
179
+
189
180
  lines.push(`### \`${plugin.name}\``);
190
181
  lines.push(`**Description**: ${plugin.description}`);
191
-
182
+
192
183
  if (plugin.category) {
193
184
  lines.push(`**Category**: ${plugin.category}`);
194
185
  }
195
-
186
+
196
187
  if (plugin.parameters.length > 0) {
197
- lines.push('\n**Parameters**:');
188
+ lines.push("\n**Parameters**:");
198
189
  for (const param of plugin.parameters) {
199
190
  lines.push(this.formatParameter(param));
200
191
  }
201
192
  } else {
202
- lines.push('\n**Parameters**: None');
193
+ lines.push("\n**Parameters**: None");
203
194
  }
204
-
205
- lines.push('\n**Output**:');
195
+
196
+ lines.push("\n**Output**:");
206
197
  lines.push(this.formatOutput(plugin.output));
207
-
198
+
208
199
  if (plugin.examples && plugin.examples.length > 0) {
209
- lines.push('\n**Usage Examples**:');
200
+ lines.push("\n**Usage Examples**:");
210
201
  for (const example of plugin.examples) {
211
202
  lines.push(`\`\`\`\n${example}\n\`\`\``);
212
203
  }
213
204
  }
214
-
205
+
215
206
  if (plugin.notes) {
216
207
  lines.push(`\n**Notes**: ${plugin.notes}`);
217
208
  }
218
-
219
- return lines.join('\n');
209
+
210
+ return lines.join("\n");
220
211
  }
221
212
 
222
213
  /**
@@ -224,33 +215,37 @@ export class FlowQuerySystemPrompt {
224
215
  */
225
216
  private static generatePluginDocumentation(plugins: FunctionMetadata[]): string {
226
217
  if (plugins.length === 0) {
227
- return 'No data loader plugins are currently available.';
218
+ return "No data loader plugins are currently available.";
228
219
  }
229
-
220
+
230
221
  const sections: string[] = [];
231
-
222
+
232
223
  // Group plugins by category
233
224
  const byCategory = new Map<string, FunctionMetadata[]>();
234
225
  for (const plugin of plugins) {
235
- const category = plugin.category || 'general';
226
+ const category = plugin.category || "general";
236
227
  if (!byCategory.has(category)) {
237
228
  byCategory.set(category, []);
238
229
  }
239
230
  byCategory.get(category)!.push(plugin);
240
231
  }
241
-
242
- sections.push('## Available Data Loader Plugins\n');
243
- sections.push('The following async data loader functions are available for use with `LOAD JSON FROM`:\n');
244
-
232
+
233
+ sections.push("## Available Data Loader Plugins\n");
234
+ sections.push(
235
+ "The following async data loader functions are available for use with `CALL ... YIELD`:\n"
236
+ );
237
+
245
238
  for (const [category, categoryPlugins] of byCategory) {
246
- sections.push(`\n### Category: ${category.charAt(0).toUpperCase() + category.slice(1)}\n`);
239
+ sections.push(
240
+ `\n### Category: ${category.charAt(0).toUpperCase() + category.slice(1)}\n`
241
+ );
247
242
  for (const plugin of categoryPlugins) {
248
243
  sections.push(this.formatPluginDocumentation(plugin));
249
- sections.push('---');
244
+ sections.push("---");
250
245
  }
251
246
  }
252
-
253
- return sections.join('\n');
247
+
248
+ return sections.join("\n");
254
249
  }
255
250
 
256
251
  /**
@@ -296,15 +291,15 @@ ${FLOWQUERY_LANGUAGE_REFERENCE}
296
291
 
297
292
  ${pluginDocs}
298
293
 
299
- ${additionalContext ? `## Additional Context\n\n${additionalContext}` : ''}
294
+ ${additionalContext ? `## Additional Context\n\n${additionalContext}` : ""}
300
295
 
301
296
  ## Example Response Format
302
297
 
303
298
  **When a query is needed**:
304
299
  \`\`\`flowquery
305
- LOAD JSON FROM pluginName(args) AS item
306
- WHERE item.field = 'value'
307
- RETURN item.name AS Name, item.value AS Value
300
+ CALL pluginName(args) YIELD field1, field2, field3
301
+ WHERE field1 = 'value'
302
+ RETURN field1 AS Name, field2 AS Value
308
303
  \`\`\`
309
304
 
310
305
  **When no query is needed** (e.g., general questions about FlowQuery):
@@ -317,7 +312,7 @@ Now help the user with their request.`;
317
312
  /**
318
313
  * Generate the complete FlowQuery system prompt.
319
314
  * Uses FlowQuery's introspection via functions() as the single source of truth.
320
- *
315
+ *
321
316
  * @param additionalContext - Optional additional context to include in the prompt
322
317
  * @returns The complete system prompt string
323
318
  */
@@ -325,14 +320,14 @@ Now help the user with their request.`;
325
320
  // Uses FlowQuery's introspection to get available async providers
326
321
  const plugins = getAllPluginMetadata();
327
322
  const pluginDocs = this.generatePluginDocumentation(plugins);
328
-
323
+
329
324
  return this.buildSystemPrompt(pluginDocs, additionalContext);
330
325
  }
331
326
 
332
327
  /**
333
328
  * Generate a system prompt for the interpretation phase.
334
329
  * Used after FlowQuery execution to interpret results.
335
- *
330
+ *
336
331
  * @returns The interpretation system prompt string
337
332
  */
338
333
  public static generateInterpretationPrompt(): string {
@@ -363,8 +358,8 @@ You are now receiving the execution results. Your job is to:
363
358
  */
364
359
  public static getMinimalPrompt(): string {
365
360
  const plugins = getAllPluginMetadata();
366
- const pluginList = plugins.map(p => `- \`${p.name}\`: ${p.description}`).join('\n');
367
-
361
+ const pluginList = plugins.map((p) => `- \`${p.name}\`: ${p.description}`).join("\n");
362
+
368
363
  return `You are a FlowQuery assistant. Generate FlowQuery statements based on user requests.
369
364
 
370
365
  Available data loader plugins:
@@ -379,7 +374,7 @@ Always wrap FlowQuery code in \`\`\`flowquery code blocks.`;
379
374
  /**
380
375
  * Generate the FlowQuery system prompt asynchronously using functions() introspection.
381
376
  * This is the preferred method that uses FlowQuery's built-in introspection.
382
- *
377
+ *
383
378
  * @param additionalContext - Optional additional context to include in the prompt
384
379
  * @returns Promise resolving to the complete system prompt string
385
380
  */
@@ -387,15 +382,19 @@ Always wrap FlowQuery code in \`\`\`flowquery code blocks.`;
387
382
  // Use FlowQuery's functions() introspection to discover available loaders
388
383
  const plugins = await getAvailableLoaders();
389
384
  const pluginDocs = this.generatePluginDocumentation(plugins);
390
-
385
+
391
386
  return this.buildSystemPrompt(pluginDocs, additionalContext);
392
387
  }
393
388
  }
394
389
 
395
390
  // Export functions for backward compatibility
396
- export const generateFlowQuerySystemPrompt = FlowQuerySystemPrompt.generate.bind(FlowQuerySystemPrompt);
397
- export const generateInterpretationPrompt = FlowQuerySystemPrompt.generateInterpretationPrompt.bind(FlowQuerySystemPrompt);
398
- export const getMinimalFlowQueryPrompt = FlowQuerySystemPrompt.getMinimalPrompt.bind(FlowQuerySystemPrompt);
399
- export const generateFlowQuerySystemPromptAsync = FlowQuerySystemPrompt.generateAsync.bind(FlowQuerySystemPrompt);
391
+ export const generateFlowQuerySystemPrompt =
392
+ FlowQuerySystemPrompt.generate.bind(FlowQuerySystemPrompt);
393
+ export const generateInterpretationPrompt =
394
+ FlowQuerySystemPrompt.generateInterpretationPrompt.bind(FlowQuerySystemPrompt);
395
+ export const getMinimalFlowQueryPrompt =
396
+ FlowQuerySystemPrompt.getMinimalPrompt.bind(FlowQuerySystemPrompt);
397
+ export const generateFlowQuerySystemPromptAsync =
398
+ FlowQuerySystemPrompt.generateAsync.bind(FlowQuerySystemPrompt);
400
399
 
401
400
  export default FlowQuerySystemPrompt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "A declarative query language for data processing pipelines.",
5
5
  "main": "dist/index.node.js",
6
6
  "types": "dist/index.node.d.ts",
@@ -5,6 +5,9 @@ class ExpressionMap {
5
5
  public get(alias: string): Expression | undefined {
6
6
  return this._map.get(alias);
7
7
  }
8
+ public has(alias: string): boolean {
9
+ return this._map.has(alias);
10
+ }
8
11
  public set map(expressions: Expression[]) {
9
12
  this._map.clear();
10
13
  for (const expr of expressions) {
@@ -50,7 +50,9 @@ class Call extends Projection {
50
50
  const record: Map<string, any> = new Map();
51
51
  if (typeof item == "object" && !Array.isArray(item)) {
52
52
  for (const [key, value] of Object.entries(item)) {
53
- record.set(key, value);
53
+ if (this._map.has(key) || !this.hasYield) {
54
+ record.set(key, value);
55
+ }
54
56
  }
55
57
  } else {
56
58
  record.set(DEFAULT_VARIABLE_NAME, item);
@@ -15,9 +15,9 @@ class CallTestFunction extends AsyncFunction {
15
15
  this._expectedParameterCount = 0;
16
16
  }
17
17
  public async *generate(): AsyncGenerator<any> {
18
- yield { result: 1 };
19
- yield { result: 2 };
20
- yield { result: 3 };
18
+ yield { result: 1, dummy: "a" };
19
+ yield { result: 2, dummy: "b" };
20
+ yield { result: 3, dummy: "c" };
21
21
  }
22
22
  }
23
23
 
@@ -616,9 +616,9 @@ test("Test call operation as last operation", async () => {
616
616
  await runner.run();
617
617
  const results = runner.results;
618
618
  expect(results.length).toBe(3);
619
- expect(results[0]).toEqual({ result: 1 });
620
- expect(results[1]).toEqual({ result: 2 });
621
- expect(results[2]).toEqual({ result: 3 });
619
+ expect(results[0]).toEqual({ result: 1, dummy: "a" });
620
+ expect(results[1]).toEqual({ result: 2, dummy: "b" });
621
+ expect(results[2]).toEqual({ result: 3, dummy: "c" });
622
622
  });
623
623
 
624
624
  test("Test call operation as last operation with yield", async () => {
@@ -1,8 +1,5 @@
1
1
  import AsyncFunction from "../../src/parsing/functions/async_function";
2
- import {
3
- FunctionDef,
4
- getRegisteredFunctionFactory,
5
- } from "../../src/parsing/functions/function_metadata";
2
+ import { FunctionDef } from "../../src/parsing/functions/function_metadata";
6
3
  import Parser from "../../src/parsing/parser";
7
4
 
8
5
  // Test class for CALL operation parsing test - defined at module level for Prettier compatibility
@@ -12,7 +9,7 @@ import Parser from "../../src/parsing/parser";
12
9
  parameters: [],
13
10
  output: { description: "Yields test values", type: "any" },
14
11
  })
15
- class CallParserTestFunction extends AsyncFunction {
12
+ class Test extends AsyncFunction {
16
13
  constructor() {
17
14
  super();
18
15
  this._expectedParameterCount = 0;
@@ -473,7 +470,7 @@ test("Test return -2", () => {
473
470
 
474
471
  test("Test call operation", () => {
475
472
  const parser = new Parser();
476
- const ast = parser.parse("CALL callparsertestfunction() YIELD result RETURN result");
473
+ const ast = parser.parse("CALL test() YIELD result RETURN result");
477
474
  expect(ast.print()).toBe(
478
475
  "ASTNode\n" +
479
476
  "- Call\n" +
@@ -1,62 +1,62 @@
1
- import Tokenizer from '../../src/tokenization/tokenizer';
1
+ import Tokenizer from "../../src/tokenization/tokenizer";
2
2
 
3
- test('Tokenizer.tokenize() should return an array of tokens', () => {
4
- const tokenizer = new Tokenizer('MATCH (n:Person) RETURN n');
3
+ test("Tokenizer.tokenize() should return an array of tokens", () => {
4
+ const tokenizer = new Tokenizer("MATCH (n:Person) RETURN n");
5
5
  const tokens = tokenizer.tokenize();
6
6
  expect(tokens).toBeDefined();
7
7
  expect(tokens.length).toBeGreaterThan(0);
8
8
  });
9
9
 
10
- test('Tokenizer.tokenize() should handle escaped quotes', () => {
10
+ test("Tokenizer.tokenize() should handle escaped quotes", () => {
11
11
  const tokenizer = new Tokenizer('return "hello \\"world"');
12
12
  const tokens = tokenizer.tokenize();
13
13
  expect(tokens).toBeDefined();
14
14
  expect(tokens.length).toBeGreaterThan(0);
15
15
  });
16
16
 
17
- test('Test predicate function', () => {
18
- const tokenizer = new Tokenizer('RETURN sum(n in [1, 2, 3] | n where n > 1)');
17
+ test("Test predicate function", () => {
18
+ const tokenizer = new Tokenizer("RETURN sum(n in [1, 2, 3] | n where n > 1)");
19
19
  const tokens = tokenizer.tokenize();
20
20
  expect(tokens).toBeDefined();
21
21
  expect(tokens.length).toBeGreaterThan(0);
22
22
  });
23
23
 
24
- test('Test f-string', () => {
24
+ test("Test f-string", () => {
25
25
  const tokenizer = new Tokenizer('RETURN f"hello {world}"');
26
26
  const tokens = tokenizer.tokenize();
27
27
  expect(tokens).toBeDefined();
28
28
  expect(tokens.length).toBeGreaterThan(0);
29
29
  });
30
30
 
31
- test('Test', () => {
32
- const tokenizer = new Tokenizer('WITH 1 AS n RETURN n');
31
+ test("Test", () => {
32
+ const tokenizer = new Tokenizer("WITH 1 AS n RETURN n");
33
33
  const tokens = tokenizer.tokenize();
34
34
  expect(tokens).toBeDefined();
35
35
  expect(tokens.length).toBeGreaterThan(0);
36
36
  });
37
37
 
38
- test('Test associative array with backtick string', () => {
39
- const tokenizer = new Tokenizer('RETURN {`key`: `value`}');
38
+ test("Test associative array with backtick string", () => {
39
+ const tokenizer = new Tokenizer("RETURN {`key`: `value`}");
40
40
  const tokens = tokenizer.tokenize();
41
41
  expect(tokens).toBeDefined();
42
42
  expect(tokens.length).toBeGreaterThan(0);
43
43
  });
44
44
 
45
- test('Test limit', () => {
46
- const tokenizer = new Tokenizer('unwind range(1, 10) as n limit 5 return n');
45
+ test("Test limit", () => {
46
+ const tokenizer = new Tokenizer("unwind range(1, 10) as n limit 5 return n");
47
47
  const tokens = tokenizer.tokenize();
48
48
  expect(tokens).toBeDefined();
49
49
  expect(tokens.length).toBeGreaterThan(0);
50
50
  });
51
51
 
52
- test('Test return -2', () => {
53
- const tokenizer = new Tokenizer('return [:-2], -2');
52
+ test("Test return -2", () => {
53
+ const tokenizer = new Tokenizer("return [:-2], -2");
54
54
  const tokens = tokenizer.tokenize();
55
55
  expect(tokens).toBeDefined();
56
56
  expect(tokens.length).toBeGreaterThan(0);
57
57
  });
58
58
 
59
- test('Test range with function', () => {
59
+ test("Test range with function", () => {
60
60
  const tokenizer = new Tokenizer(`
61
61
  with range(1,10) as data
62
62
  return range(0, size(data)-1) as indices
@@ -64,4 +64,4 @@ test('Test range with function', () => {
64
64
  const tokens = tokenizer.tokenize();
65
65
  expect(tokens).toBeDefined();
66
66
  expect(tokens.length).toBeGreaterThan(0);
67
- });
67
+ });