flowquery 1.0.6 → 1.0.8
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/README.md +30 -138
- package/dist/compute/runner.d.ts +1 -22
- package/dist/compute/runner.d.ts.map +1 -1
- package/dist/compute/runner.js.map +1 -1
- package/dist/extensibility.d.ts +28 -2
- package/dist/extensibility.d.ts.map +1 -1
- package/dist/extensibility.js +39 -15
- package/dist/extensibility.js.map +1 -1
- package/dist/flowquery.min.js +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +0 -80
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.d.ts +3 -3
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +0 -80
- package/dist/index.node.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +3 -80
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +8 -127
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/function_metadata.d.ts +28 -35
- package/dist/parsing/functions/function_metadata.d.ts.map +1 -1
- package/dist/parsing/functions/function_metadata.js +88 -61
- package/dist/parsing/functions/function_metadata.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +1 -1
- package/misc/apps/RAG/src/plugins/index.ts +29 -33
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +28 -32
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +29 -33
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +55 -59
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +61 -71
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +8 -8
- package/package.json +1 -1
- package/src/compute/runner.ts +1 -26
- package/src/extensibility.ts +38 -2
- package/src/index.browser.ts +2 -88
- package/src/index.node.ts +3 -92
- package/src/parsing/functions/function_factory.ts +13 -154
- package/src/parsing/functions/function_metadata.ts +96 -94
- package/tests/extensibility.test.ts +601 -0
- package/dist/parsing/functions/extensibility/index.d.ts +0 -37
- package/dist/parsing/functions/extensibility/index.d.ts.map +0 -1
- package/dist/parsing/functions/extensibility/index.js +0 -50
- package/dist/parsing/functions/extensibility/index.js.map +0 -1
- package/misc/apps/RAG/src/plugins/PluginRegistry.ts +0 -136
- package/misc/apps/RAG/src/plugins/types.ts +0 -52
- package/src/parsing/functions/extensibility/index.ts +0 -54
- package/tests/parsing/function_plugins.test.ts +0 -369
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Function,
|
|
3
|
+
AggregateFunction,
|
|
4
|
+
AsyncFunction,
|
|
5
|
+
PredicateFunction,
|
|
6
|
+
ReducerElement,
|
|
7
|
+
FunctionDef,
|
|
8
|
+
FunctionMetadata,
|
|
9
|
+
FunctionDefOptions,
|
|
10
|
+
ParameterSchema,
|
|
11
|
+
OutputSchema,
|
|
12
|
+
FunctionCategory
|
|
13
|
+
} from "../src/extensibility";
|
|
14
|
+
import {
|
|
15
|
+
getRegisteredAsyncProvider,
|
|
16
|
+
getFunctionMetadata
|
|
17
|
+
} from "../src/parsing/functions/function_metadata";
|
|
18
|
+
|
|
19
|
+
describe("Extensibility API Exports", () => {
|
|
20
|
+
test("Function class is exported and can be extended", () => {
|
|
21
|
+
class CustomFunction extends Function {
|
|
22
|
+
constructor() {
|
|
23
|
+
super("customFunc");
|
|
24
|
+
this._expectedParameterCount = 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public value(): string {
|
|
28
|
+
return "custom value";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const func = new CustomFunction();
|
|
33
|
+
expect(func.name).toBe("customFunc");
|
|
34
|
+
expect(func.toString()).toBe("Function (customFunc)");
|
|
35
|
+
expect(func.value()).toBe("custom value");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("Function validates parameter count when set", () => {
|
|
39
|
+
class TwoParamFunction extends Function {
|
|
40
|
+
constructor() {
|
|
41
|
+
super("twoParam");
|
|
42
|
+
this._expectedParameterCount = 2;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const func = new TwoParamFunction();
|
|
47
|
+
|
|
48
|
+
// Should throw when wrong number of parameters
|
|
49
|
+
expect(() => {
|
|
50
|
+
func.parameters = [];
|
|
51
|
+
}).toThrow("Function twoParam expected 2 parameters, but got 0");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("Function without expected parameter count accepts any number", () => {
|
|
55
|
+
class FlexibleFunction extends Function {
|
|
56
|
+
constructor() {
|
|
57
|
+
super("flexible");
|
|
58
|
+
// _expectedParameterCount is null by default
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const func = new FlexibleFunction();
|
|
63
|
+
// Should not throw
|
|
64
|
+
func.parameters = [];
|
|
65
|
+
expect(func.getChildren().length).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("AggregateFunction class is exported and can be extended", () => {
|
|
69
|
+
class SumElement extends ReducerElement {
|
|
70
|
+
private _value: number = 0;
|
|
71
|
+
public get value(): number {
|
|
72
|
+
return this._value;
|
|
73
|
+
}
|
|
74
|
+
public set value(v: number) {
|
|
75
|
+
this._value = v;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class CustomSum extends AggregateFunction {
|
|
80
|
+
private _total: number = 0;
|
|
81
|
+
|
|
82
|
+
constructor() {
|
|
83
|
+
super("customSum");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public reduce(element: ReducerElement): void {
|
|
87
|
+
this._total += element.value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public element(): ReducerElement {
|
|
91
|
+
const el = new SumElement();
|
|
92
|
+
el.value = this._total;
|
|
93
|
+
return el;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public value(): number {
|
|
97
|
+
return this._total;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const agg = new CustomSum();
|
|
102
|
+
expect(agg.name).toBe("customSum");
|
|
103
|
+
|
|
104
|
+
const elem = new SumElement();
|
|
105
|
+
elem.value = 5;
|
|
106
|
+
agg.reduce(elem);
|
|
107
|
+
expect(agg.value()).toBe(5);
|
|
108
|
+
|
|
109
|
+
const elem2 = new SumElement();
|
|
110
|
+
elem2.value = 3;
|
|
111
|
+
agg.reduce(elem2);
|
|
112
|
+
expect(agg.value()).toBe(8);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("PredicateFunction class is exported and can be extended", () => {
|
|
116
|
+
class CustomPredicate extends PredicateFunction {
|
|
117
|
+
constructor() {
|
|
118
|
+
super("customPredicate");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public value(): boolean {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const pred = new CustomPredicate();
|
|
127
|
+
expect(pred.name).toBe("customPredicate");
|
|
128
|
+
expect(pred.toString()).toBe("PredicateFunction (customPredicate)");
|
|
129
|
+
expect(pred.value()).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("AsyncFunction class is exported and can be instantiated", () => {
|
|
133
|
+
const asyncFunc = new AsyncFunction("testAsync");
|
|
134
|
+
expect(asyncFunc.name).toBe("testAsync");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("ReducerElement class is exported and can be extended", () => {
|
|
138
|
+
class NumberElement extends ReducerElement {
|
|
139
|
+
private _num: number = 0;
|
|
140
|
+
|
|
141
|
+
public get value(): number {
|
|
142
|
+
return this._num;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public set value(v: number) {
|
|
146
|
+
this._num = v;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const elem = new NumberElement();
|
|
151
|
+
elem.value = 42;
|
|
152
|
+
expect(elem.value).toBe(42);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("FunctionDef Decorator", () => {
|
|
157
|
+
test("FunctionDef decorator can be applied to a scalar function", () => {
|
|
158
|
+
@FunctionDef({
|
|
159
|
+
description: "Test function for extensibility",
|
|
160
|
+
category: "scalar",
|
|
161
|
+
parameters: [
|
|
162
|
+
{ name: "input", description: "Input value", type: "string" }
|
|
163
|
+
],
|
|
164
|
+
output: { description: "Output value", type: "string" },
|
|
165
|
+
examples: ["RETURN testExtFunc('hello')"]
|
|
166
|
+
})
|
|
167
|
+
class TestExtFunc extends Function {
|
|
168
|
+
constructor() {
|
|
169
|
+
super("testExtFunc");
|
|
170
|
+
this._expectedParameterCount = 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public value(): string {
|
|
174
|
+
return "test result";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Verify the decorated class still works correctly
|
|
179
|
+
const instance = new TestExtFunc();
|
|
180
|
+
expect(instance.name).toBe("testExtFunc");
|
|
181
|
+
expect(instance.value()).toBe("test result");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("FunctionDef decorator can be applied to an aggregate function", () => {
|
|
185
|
+
@FunctionDef({
|
|
186
|
+
description: "Test aggregate function",
|
|
187
|
+
category: "aggregate",
|
|
188
|
+
parameters: [
|
|
189
|
+
{ name: "value", description: "Numeric value", type: "number" }
|
|
190
|
+
],
|
|
191
|
+
output: { description: "Aggregated result", type: "number" }
|
|
192
|
+
})
|
|
193
|
+
class TestAggExt extends AggregateFunction {
|
|
194
|
+
private _sum: number = 0;
|
|
195
|
+
|
|
196
|
+
constructor() {
|
|
197
|
+
super("testAggExt");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public value(): number {
|
|
201
|
+
return this._sum;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const instance = new TestAggExt();
|
|
206
|
+
expect(instance.name).toBe("testAggExt");
|
|
207
|
+
expect(instance.value()).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("FunctionDef decorator can be applied to an async provider", async () => {
|
|
211
|
+
@FunctionDef({
|
|
212
|
+
description: "Test async provider for extensibility",
|
|
213
|
+
category: "async",
|
|
214
|
+
parameters: [
|
|
215
|
+
{ name: "count", description: "Number of items", type: "number", required: false, default: 1 }
|
|
216
|
+
],
|
|
217
|
+
output: { description: "Data object", type: "object" }
|
|
218
|
+
})
|
|
219
|
+
class SimpleLoader {
|
|
220
|
+
async *fetch(count: number = 1): AsyncGenerator<any> {
|
|
221
|
+
for (let i = 0; i < count; i++) {
|
|
222
|
+
yield { id: i, data: `item${i}` };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Verify the decorated class still works correctly
|
|
228
|
+
const loader = new SimpleLoader();
|
|
229
|
+
const results: any[] = [];
|
|
230
|
+
for await (const item of loader.fetch(2)) {
|
|
231
|
+
results.push(item);
|
|
232
|
+
}
|
|
233
|
+
expect(results.length).toBe(2);
|
|
234
|
+
expect(results[0]).toEqual({ id: 0, data: "item0" });
|
|
235
|
+
expect(results[1]).toEqual({ id: 1, data: "item1" });
|
|
236
|
+
|
|
237
|
+
// Verify the async provider was registered
|
|
238
|
+
const provider = getRegisteredAsyncProvider("simple");
|
|
239
|
+
expect(provider).toBeDefined();
|
|
240
|
+
expect(typeof provider).toBe("function");
|
|
241
|
+
|
|
242
|
+
// Verify the metadata was registered
|
|
243
|
+
const metadata = getFunctionMetadata("simple");
|
|
244
|
+
expect(metadata).toBeDefined();
|
|
245
|
+
expect(metadata?.name).toBe("simple");
|
|
246
|
+
expect(metadata?.category).toBe("async");
|
|
247
|
+
expect(metadata?.description).toBe("Test async provider for extensibility");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("FunctionDef decorator can be applied to a predicate function", () => {
|
|
251
|
+
@FunctionDef({
|
|
252
|
+
description: "Test predicate function",
|
|
253
|
+
category: "predicate",
|
|
254
|
+
parameters: [
|
|
255
|
+
{ name: "list", description: "List to check", type: "array" }
|
|
256
|
+
],
|
|
257
|
+
output: { description: "Boolean result", type: "boolean" }
|
|
258
|
+
})
|
|
259
|
+
class TestPredExt extends PredicateFunction {
|
|
260
|
+
constructor() {
|
|
261
|
+
super("testPredExt");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public value(): boolean {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const instance = new TestPredExt();
|
|
270
|
+
expect(instance.name).toBe("testPredExt");
|
|
271
|
+
expect(instance.value()).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("Type Exports", () => {
|
|
276
|
+
test("FunctionMetadata type can be used", () => {
|
|
277
|
+
const meta: FunctionMetadata = {
|
|
278
|
+
name: "typeTest",
|
|
279
|
+
description: "Testing type exports",
|
|
280
|
+
category: "scalar",
|
|
281
|
+
parameters: [],
|
|
282
|
+
output: { description: "Output", type: "string" }
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
expect(meta.name).toBe("typeTest");
|
|
286
|
+
expect(meta.description).toBe("Testing type exports");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("ParameterSchema type can be used", () => {
|
|
290
|
+
const param: ParameterSchema = {
|
|
291
|
+
name: "testParam",
|
|
292
|
+
description: "A test parameter",
|
|
293
|
+
type: "string",
|
|
294
|
+
required: true,
|
|
295
|
+
default: "default value",
|
|
296
|
+
example: "example value"
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
expect(param.name).toBe("testParam");
|
|
300
|
+
expect(param.required).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("ParameterSchema with nested types", () => {
|
|
304
|
+
const arrayParam: ParameterSchema = {
|
|
305
|
+
name: "items",
|
|
306
|
+
description: "Array of items",
|
|
307
|
+
type: "array",
|
|
308
|
+
items: {
|
|
309
|
+
description: "Item in array",
|
|
310
|
+
type: "string"
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const objectParam: ParameterSchema = {
|
|
315
|
+
name: "config",
|
|
316
|
+
description: "Configuration object",
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
enabled: { description: "Is enabled", type: "boolean" },
|
|
320
|
+
value: { description: "Value", type: "number" }
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
expect(arrayParam.items?.type).toBe("string");
|
|
325
|
+
expect(objectParam.properties?.enabled.type).toBe("boolean");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("OutputSchema type can be used", () => {
|
|
329
|
+
const output: OutputSchema = {
|
|
330
|
+
description: "Result output",
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
success: { description: "Success flag", type: "boolean" },
|
|
334
|
+
data: { description: "Result data", type: "array" }
|
|
335
|
+
},
|
|
336
|
+
example: { success: true, data: [] }
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
expect(output.type).toBe("object");
|
|
340
|
+
expect(output.properties?.success.type).toBe("boolean");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("FunctionCategory type accepts standard and custom categories", () => {
|
|
344
|
+
const scalar: FunctionCategory = "scalar";
|
|
345
|
+
const aggregate: FunctionCategory = "aggregate";
|
|
346
|
+
const predicate: FunctionCategory = "predicate";
|
|
347
|
+
const async: FunctionCategory = "async";
|
|
348
|
+
const custom: FunctionCategory = "myCustomCategory";
|
|
349
|
+
|
|
350
|
+
expect(scalar).toBe("scalar");
|
|
351
|
+
expect(aggregate).toBe("aggregate");
|
|
352
|
+
expect(predicate).toBe("predicate");
|
|
353
|
+
expect(async).toBe("async");
|
|
354
|
+
expect(custom).toBe("myCustomCategory");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("FunctionDefOptions type can be used", () => {
|
|
358
|
+
const options: FunctionDefOptions = {
|
|
359
|
+
description: "Function options test",
|
|
360
|
+
category: "scalar",
|
|
361
|
+
parameters: [],
|
|
362
|
+
output: { description: "Output", type: "string" },
|
|
363
|
+
notes: "Some additional notes"
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
expect(options.description).toBe("Function options test");
|
|
367
|
+
expect(options.notes).toBe("Some additional notes");
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe("Plugin Functions Integration with FlowQuery", () => {
|
|
372
|
+
// Import Runner for executing FlowQuery statements
|
|
373
|
+
const Runner = require("../src/compute/runner").default;
|
|
374
|
+
|
|
375
|
+
test("Custom scalar function can be used in a FlowQuery statement", async () => {
|
|
376
|
+
// Define and register a custom function via @FunctionDef decorator
|
|
377
|
+
@FunctionDef({
|
|
378
|
+
description: "Doubles a number",
|
|
379
|
+
category: "scalar",
|
|
380
|
+
parameters: [{ name: "value", description: "Number to double", type: "number" }],
|
|
381
|
+
output: { description: "Doubled value", type: "number" }
|
|
382
|
+
})
|
|
383
|
+
class Double extends Function {
|
|
384
|
+
constructor() {
|
|
385
|
+
super("double");
|
|
386
|
+
this._expectedParameterCount = 1;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public value(): number {
|
|
390
|
+
return this.getChildren()[0].value() * 2;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Execute a FlowQuery statement that uses the custom function
|
|
395
|
+
const runner = new Runner("WITH 5 AS num RETURN double(num) AS result");
|
|
396
|
+
await runner.run();
|
|
397
|
+
|
|
398
|
+
expect(runner.results.length).toBe(1);
|
|
399
|
+
expect(runner.results[0]).toEqual({ result: 10 });
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("Custom string function can be used in a FlowQuery statement", async () => {
|
|
403
|
+
@FunctionDef({
|
|
404
|
+
description: "Reverses a string",
|
|
405
|
+
category: "scalar",
|
|
406
|
+
parameters: [{ name: "text", description: "String to reverse", type: "string" }],
|
|
407
|
+
output: { description: "Reversed string", type: "string" }
|
|
408
|
+
})
|
|
409
|
+
class StrReverse extends Function {
|
|
410
|
+
constructor() {
|
|
411
|
+
super("strreverse");
|
|
412
|
+
this._expectedParameterCount = 1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public value(): string {
|
|
416
|
+
const input = String(this.getChildren()[0].value());
|
|
417
|
+
return input.split('').reverse().join('');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const runner = new Runner("WITH 'hello' AS s RETURN strreverse(s) AS reversed");
|
|
422
|
+
await runner.run();
|
|
423
|
+
|
|
424
|
+
expect(runner.results.length).toBe(1);
|
|
425
|
+
expect(runner.results[0]).toEqual({ reversed: 'olleh' });
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("Custom aggregate function can be used in a FlowQuery statement", async () => {
|
|
429
|
+
// Create a custom reducer element for the aggregate
|
|
430
|
+
class ProductElement extends ReducerElement {
|
|
431
|
+
private _value: number = 1;
|
|
432
|
+
public get value(): number {
|
|
433
|
+
return this._value;
|
|
434
|
+
}
|
|
435
|
+
public set value(v: number) {
|
|
436
|
+
this._value *= v;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@FunctionDef({
|
|
441
|
+
description: "Calculates the product of values",
|
|
442
|
+
category: "aggregate",
|
|
443
|
+
parameters: [{ name: "value", description: "Number to multiply", type: "number" }],
|
|
444
|
+
output: { description: "Product of all values", type: "number" }
|
|
445
|
+
})
|
|
446
|
+
class Product extends AggregateFunction {
|
|
447
|
+
constructor() {
|
|
448
|
+
super("product");
|
|
449
|
+
this._expectedParameterCount = 1;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
public reduce(element: ReducerElement): void {
|
|
453
|
+
element.value = this.firstChild().value();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public element(): ReducerElement {
|
|
457
|
+
return new ProductElement();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const runner = new Runner("UNWIND [2, 3, 4] AS num RETURN product(num) AS result");
|
|
462
|
+
await runner.run();
|
|
463
|
+
|
|
464
|
+
expect(runner.results.length).toBe(1);
|
|
465
|
+
expect(runner.results[0]).toEqual({ result: 24 });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("Custom function works with expressions and other functions", async () => {
|
|
469
|
+
@FunctionDef({
|
|
470
|
+
description: "Adds 100 to a number",
|
|
471
|
+
category: "scalar",
|
|
472
|
+
parameters: [{ name: "value", description: "Number", type: "number" }],
|
|
473
|
+
output: { description: "Number plus 100", type: "number" }
|
|
474
|
+
})
|
|
475
|
+
class AddHundred extends Function {
|
|
476
|
+
constructor() {
|
|
477
|
+
super("addhundred");
|
|
478
|
+
this._expectedParameterCount = 1;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public value(): number {
|
|
482
|
+
return this.getChildren()[0].value() + 100;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Use the custom function with expressions
|
|
487
|
+
const runner = new Runner("WITH 5 * 3 AS num RETURN addhundred(num) + 1 AS result");
|
|
488
|
+
await runner.run();
|
|
489
|
+
|
|
490
|
+
expect(runner.results.length).toBe(1);
|
|
491
|
+
expect(runner.results[0]).toEqual({ result: 116 }); // (5*3) + 100 + 1 = 116
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("Multiple custom functions can be used together", async () => {
|
|
495
|
+
@FunctionDef({
|
|
496
|
+
description: "Triples a number",
|
|
497
|
+
category: "scalar",
|
|
498
|
+
parameters: [{ name: "value", description: "Number to triple", type: "number" }],
|
|
499
|
+
output: { description: "Tripled value", type: "number" }
|
|
500
|
+
})
|
|
501
|
+
class Triple extends Function {
|
|
502
|
+
constructor() {
|
|
503
|
+
super("triple");
|
|
504
|
+
this._expectedParameterCount = 1;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
public value(): number {
|
|
508
|
+
return this.getChildren()[0].value() * 3;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@FunctionDef({
|
|
513
|
+
description: "Squares a number",
|
|
514
|
+
category: "scalar",
|
|
515
|
+
parameters: [{ name: "value", description: "Number to square", type: "number" }],
|
|
516
|
+
output: { description: "Squared value", type: "number" }
|
|
517
|
+
})
|
|
518
|
+
class Square extends Function {
|
|
519
|
+
constructor() {
|
|
520
|
+
super("square");
|
|
521
|
+
this._expectedParameterCount = 1;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
public value(): number {
|
|
525
|
+
const v = this.getChildren()[0].value();
|
|
526
|
+
return v * v;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Use both custom functions in a query
|
|
531
|
+
const runner = new Runner("WITH 2 AS num RETURN triple(num) AS tripled, square(num) AS squared");
|
|
532
|
+
await runner.run();
|
|
533
|
+
|
|
534
|
+
expect(runner.results.length).toBe(1);
|
|
535
|
+
expect(runner.results[0]).toEqual({ tripled: 6, squared: 4 });
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("Custom async provider can be used in LOAD JSON FROM statement", async () => {
|
|
539
|
+
@FunctionDef({
|
|
540
|
+
description: "Provides example data for testing",
|
|
541
|
+
category: "async",
|
|
542
|
+
parameters: [],
|
|
543
|
+
output: { description: "Example data object", type: "object" }
|
|
544
|
+
})
|
|
545
|
+
class GetExampleDataLoader {
|
|
546
|
+
async *fetch(): AsyncGenerator<any> {
|
|
547
|
+
yield { id: 1, name: "Alice" };
|
|
548
|
+
yield { id: 2, name: "Bob" };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Verify registration worked
|
|
553
|
+
expect(getRegisteredAsyncProvider("getExampleData")).toBeDefined();
|
|
554
|
+
|
|
555
|
+
// Use the async provider in a FlowQuery statement
|
|
556
|
+
const runner = new Runner("LOAD JSON FROM getExampleData() AS data RETURN data.id AS id, data.name AS name");
|
|
557
|
+
await runner.run();
|
|
558
|
+
|
|
559
|
+
expect(runner.results.length).toBe(2);
|
|
560
|
+
expect(runner.results[0]).toEqual({ id: 1, name: "Alice" });
|
|
561
|
+
expect(runner.results[1]).toEqual({ id: 2, name: "Bob" });
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("Custom function can be retrieved via functions() in a FlowQuery statement", async () => {
|
|
565
|
+
@FunctionDef({
|
|
566
|
+
description: "A unique test function for introspection",
|
|
567
|
+
category: "scalar",
|
|
568
|
+
parameters: [{ name: "x", description: "Input value", type: "number" }],
|
|
569
|
+
output: { description: "Output value", type: "number" }
|
|
570
|
+
})
|
|
571
|
+
class IntrospectTestFunc extends Function {
|
|
572
|
+
constructor() {
|
|
573
|
+
super("introspectTestFunc");
|
|
574
|
+
this._expectedParameterCount = 1;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
public value(): number {
|
|
578
|
+
return this.getChildren()[0].value() + 42;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// First verify the function is registered
|
|
583
|
+
const metadata = getFunctionMetadata("introspectTestFunc");
|
|
584
|
+
expect(metadata).toBeDefined();
|
|
585
|
+
expect(metadata?.name).toBe("introspecttestfunc");
|
|
586
|
+
|
|
587
|
+
// Use functions() with UNWIND to find the registered function
|
|
588
|
+
const runner = new Runner(`
|
|
589
|
+
WITH functions() AS funcs
|
|
590
|
+
UNWIND funcs AS f
|
|
591
|
+
WITH f WHERE f.name = 'introspecttestfunc'
|
|
592
|
+
RETURN f.name AS name, f.description AS description, f.category AS category
|
|
593
|
+
`);
|
|
594
|
+
await runner.run();
|
|
595
|
+
|
|
596
|
+
expect(runner.results.length).toBe(1);
|
|
597
|
+
expect(runner.results[0].name).toBe("introspecttestfunc");
|
|
598
|
+
expect(runner.results[0].description).toBe("A unique test function for introspection");
|
|
599
|
+
expect(runner.results[0].category).toBe("scalar");
|
|
600
|
+
});
|
|
601
|
+
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlowQuery Extensibility API
|
|
3
|
-
*
|
|
4
|
-
* This module provides all the exports needed to create custom FlowQuery functions.
|
|
5
|
-
*
|
|
6
|
-
* @packageDocumentation
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```typescript
|
|
10
|
-
* import { Function, FunctionDef } from '../parsing/functions/extensibility';
|
|
11
|
-
*
|
|
12
|
-
* @FunctionDef({
|
|
13
|
-
* description: "Converts a string to uppercase",
|
|
14
|
-
* category: "string",
|
|
15
|
-
* parameters: [{ name: "text", description: "String to convert", type: "string" }],
|
|
16
|
-
* output: { description: "Uppercase string", type: "string" }
|
|
17
|
-
* })
|
|
18
|
-
* class UpperCase extends Function {
|
|
19
|
-
* constructor() {
|
|
20
|
-
* super("uppercase");
|
|
21
|
-
* this._expectedParameterCount = 1;
|
|
22
|
-
* }
|
|
23
|
-
* public value(): string {
|
|
24
|
-
* return String(this.getChildren()[0].value()).toUpperCase();
|
|
25
|
-
* }
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export { default as Function } from "../function";
|
|
30
|
-
export { default as AggregateFunction } from "../aggregate_function";
|
|
31
|
-
export { default as PredicateFunction } from "../predicate_function";
|
|
32
|
-
export { default as ReducerElement } from "../reducer_element";
|
|
33
|
-
export { FunctionDef, FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema, FunctionCategory } from "../function_metadata";
|
|
34
|
-
export { default as FunctionFactory } from "../function_factory";
|
|
35
|
-
export type { FunctionCreator, AsyncDataProvider } from "../function_factory";
|
|
36
|
-
export type { RegisterFunctionOptions, RegisterAsyncProviderOptions } from "../function_metadata";
|
|
37
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/parsing/functions/extensibility/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAG/D,OAAO,EACH,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,gBAAgB,EACnB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,YAAY,EACR,uBAAuB,EACvB,4BAA4B,EAC/B,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* FlowQuery Extensibility API
|
|
4
|
-
*
|
|
5
|
-
* This module provides all the exports needed to create custom FlowQuery functions.
|
|
6
|
-
*
|
|
7
|
-
* @packageDocumentation
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* import { Function, FunctionDef } from '../parsing/functions/extensibility';
|
|
12
|
-
*
|
|
13
|
-
* @FunctionDef({
|
|
14
|
-
* description: "Converts a string to uppercase",
|
|
15
|
-
* category: "string",
|
|
16
|
-
* parameters: [{ name: "text", description: "String to convert", type: "string" }],
|
|
17
|
-
* output: { description: "Uppercase string", type: "string" }
|
|
18
|
-
* })
|
|
19
|
-
* class UpperCase extends Function {
|
|
20
|
-
* constructor() {
|
|
21
|
-
* super("uppercase");
|
|
22
|
-
* this._expectedParameterCount = 1;
|
|
23
|
-
* }
|
|
24
|
-
* public value(): string {
|
|
25
|
-
* return String(this.getChildren()[0].value()).toUpperCase();
|
|
26
|
-
* }
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
-
};
|
|
33
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
-
exports.FunctionFactory = exports.FunctionDef = exports.ReducerElement = exports.PredicateFunction = exports.AggregateFunction = exports.Function = void 0;
|
|
35
|
-
// Base function classes for creating custom functions
|
|
36
|
-
var function_1 = require("../function");
|
|
37
|
-
Object.defineProperty(exports, "Function", { enumerable: true, get: function () { return __importDefault(function_1).default; } });
|
|
38
|
-
var aggregate_function_1 = require("../aggregate_function");
|
|
39
|
-
Object.defineProperty(exports, "AggregateFunction", { enumerable: true, get: function () { return __importDefault(aggregate_function_1).default; } });
|
|
40
|
-
var predicate_function_1 = require("../predicate_function");
|
|
41
|
-
Object.defineProperty(exports, "PredicateFunction", { enumerable: true, get: function () { return __importDefault(predicate_function_1).default; } });
|
|
42
|
-
var reducer_element_1 = require("../reducer_element");
|
|
43
|
-
Object.defineProperty(exports, "ReducerElement", { enumerable: true, get: function () { return __importDefault(reducer_element_1).default; } });
|
|
44
|
-
// Decorator and metadata types for function registration
|
|
45
|
-
var function_metadata_1 = require("../function_metadata");
|
|
46
|
-
Object.defineProperty(exports, "FunctionDef", { enumerable: true, get: function () { return function_metadata_1.FunctionDef; } });
|
|
47
|
-
// Factory for advanced usage
|
|
48
|
-
var function_factory_1 = require("../function_factory");
|
|
49
|
-
Object.defineProperty(exports, "FunctionFactory", { enumerable: true, get: function () { return __importDefault(function_factory_1).default; } });
|
|
50
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/parsing/functions/extensibility/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;;;;;;AAEH,sDAAsD;AACtD,wCAAkD;AAAzC,qHAAA,OAAO,OAAY;AAC5B,4DAAqE;AAA5D,wIAAA,OAAO,OAAqB;AACrC,4DAAqE;AAA5D,wIAAA,OAAO,OAAqB;AACrC,sDAA+D;AAAtD,kIAAA,OAAO,OAAkB;AAElC,yDAAyD;AACzD,0DAO8B;AAN1B,gHAAA,WAAW,OAAA;AAQf,6BAA6B;AAC7B,wDAAiE;AAAxD,oIAAA,OAAO,OAAmB"}
|