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
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin registry for loading and managing FlowQuery async data loader plugins.
|
|
3
|
-
*
|
|
4
|
-
* This registry uses FlowQuery's built-in function introspection via the functions()
|
|
5
|
-
* function as the single source of truth for available loaders and their metadata.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import FlowQuery from 'flowquery';
|
|
9
|
-
import { AsyncLoaderPlugin, PluginMetadata, PluginModule } from './types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Registry for managing FlowQuery plugins.
|
|
13
|
-
*
|
|
14
|
-
* Uses FlowQuery's `functions()` introspection as the source of truth,
|
|
15
|
-
* eliminating duplicate tracking of registered plugins.
|
|
16
|
-
*/
|
|
17
|
-
class PluginRegistry {
|
|
18
|
-
/**
|
|
19
|
-
* Register a single plugin with FlowQuery.
|
|
20
|
-
*
|
|
21
|
-
* @param plugin - The plugin to register
|
|
22
|
-
*/
|
|
23
|
-
public register(plugin: AsyncLoaderPlugin): void {
|
|
24
|
-
// Register with FlowQuery - it becomes the source of truth
|
|
25
|
-
if (plugin.metadata) {
|
|
26
|
-
FlowQuery.registerAsyncProvider(plugin.name, {
|
|
27
|
-
provider: plugin.provider,
|
|
28
|
-
metadata: {
|
|
29
|
-
name: plugin.name,
|
|
30
|
-
...plugin.metadata
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
} else {
|
|
34
|
-
FlowQuery.registerAsyncProvider(plugin.name, plugin.provider);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
console.log(`Registered plugin: ${plugin.name}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Register multiple plugins at once.
|
|
42
|
-
*
|
|
43
|
-
* @param plugins - Array of plugins to register
|
|
44
|
-
*/
|
|
45
|
-
public registerAll(plugins: AsyncLoaderPlugin[]): void {
|
|
46
|
-
for (const plugin of plugins) {
|
|
47
|
-
this.register(plugin);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Load plugins from a plugin module.
|
|
53
|
-
*
|
|
54
|
-
* @param module - The plugin module to load
|
|
55
|
-
*/
|
|
56
|
-
public loadModule(module: PluginModule): void {
|
|
57
|
-
this.registerAll(module.plugins);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Unregister a plugin by name.
|
|
62
|
-
*
|
|
63
|
-
* @param name - The plugin name to unregister
|
|
64
|
-
*/
|
|
65
|
-
public unregister(name: string): void {
|
|
66
|
-
FlowQuery.unregisterAsyncProvider(name);
|
|
67
|
-
console.log(`Unregistered plugin: ${name}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get all loaded async provider names by querying FlowQuery directly.
|
|
72
|
-
*
|
|
73
|
-
* @returns Array of registered async provider names
|
|
74
|
-
*/
|
|
75
|
-
public getLoadedPlugins(): string[] {
|
|
76
|
-
// Use FlowQuery's FunctionFactory to get async provider names
|
|
77
|
-
const allFunctions = FlowQuery.listFunctions({ asyncOnly: true });
|
|
78
|
-
return allFunctions.map(f => f.name);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check if a plugin is registered by querying FlowQuery.
|
|
83
|
-
*
|
|
84
|
-
* @param name - The plugin name to check
|
|
85
|
-
* @returns True if the plugin is registered
|
|
86
|
-
*/
|
|
87
|
-
public isRegistered(name: string): boolean {
|
|
88
|
-
const plugins = this.getLoadedPlugins();
|
|
89
|
-
return plugins.some(p => p.toLowerCase() === name.toLowerCase());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get all plugin metadata for registered async providers.
|
|
94
|
-
* Uses FlowQuery's functions() introspection as the single source of truth.
|
|
95
|
-
*
|
|
96
|
-
* @returns Array of plugin metadata objects
|
|
97
|
-
*/
|
|
98
|
-
public getAllPluginMetadata(): PluginMetadata[] {
|
|
99
|
-
// Query FlowQuery directly for all async providers with their metadata
|
|
100
|
-
const asyncFunctions = FlowQuery.listFunctions({ asyncOnly: true });
|
|
101
|
-
|
|
102
|
-
return asyncFunctions.map(f => ({
|
|
103
|
-
name: f.name,
|
|
104
|
-
description: f.description,
|
|
105
|
-
category: f.category,
|
|
106
|
-
parameters: f.parameters || [],
|
|
107
|
-
output: f.output || { description: 'Data items', type: 'object' },
|
|
108
|
-
examples: f.examples,
|
|
109
|
-
notes: f.notes
|
|
110
|
-
}));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get all plugin metadata asynchronously using a FlowQuery statement.
|
|
115
|
-
* This demonstrates the functions() approach for dynamic discovery.
|
|
116
|
-
*
|
|
117
|
-
* @returns Promise resolving to array of plugin metadata
|
|
118
|
-
*/
|
|
119
|
-
public async getAvailableLoadersAsync(): Promise<PluginMetadata[]> {
|
|
120
|
-
const runner = new FlowQuery(`
|
|
121
|
-
WITH functions() AS funcs
|
|
122
|
-
UNWIND funcs AS f
|
|
123
|
-
WHERE f.isAsyncProvider = true
|
|
124
|
-
RETURN f
|
|
125
|
-
`);
|
|
126
|
-
await runner.run();
|
|
127
|
-
return runner.results.map((r: any) => r.expr0 as PluginMetadata);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Global plugin registry instance.
|
|
133
|
-
*/
|
|
134
|
-
export const pluginRegistry = new PluginRegistry();
|
|
135
|
-
|
|
136
|
-
export default pluginRegistry;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin type definitions for FlowQuery async data loaders.
|
|
3
|
-
*
|
|
4
|
-
* Re-exports core types from FlowQuery library for consistency.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
ParameterSchema,
|
|
9
|
-
OutputSchema,
|
|
10
|
-
FunctionMetadata,
|
|
11
|
-
AsyncDataProvider
|
|
12
|
-
} from 'flowquery';
|
|
13
|
-
|
|
14
|
-
// Re-export core types from FlowQuery
|
|
15
|
-
export { ParameterSchema, OutputSchema, FunctionMetadata, AsyncDataProvider };
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Alias for FunctionMetadata - used for plugin definitions.
|
|
19
|
-
* This type is identical to FunctionMetadata from FlowQuery.
|
|
20
|
-
*/
|
|
21
|
-
export type PluginMetadata = FunctionMetadata;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A plugin definition for an async data loader.
|
|
25
|
-
*/
|
|
26
|
-
export interface AsyncLoaderPlugin {
|
|
27
|
-
/**
|
|
28
|
-
* The name of the function as it will be used in FlowQuery.
|
|
29
|
-
* Will be lowercased when registered.
|
|
30
|
-
*/
|
|
31
|
-
name: string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The async data provider function.
|
|
35
|
-
*/
|
|
36
|
-
provider: AsyncDataProvider;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Optional metadata describing the function for LLM consumption.
|
|
40
|
-
*/
|
|
41
|
-
metadata?: Omit<PluginMetadata, 'name'>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Interface that plugin modules should export.
|
|
46
|
-
*/
|
|
47
|
-
export interface PluginModule {
|
|
48
|
-
/**
|
|
49
|
-
* Array of plugins defined in this module.
|
|
50
|
-
*/
|
|
51
|
-
plugins: AsyncLoaderPlugin[];
|
|
52
|
-
}
|
|
@@ -1,54 +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
|
-
|
|
30
|
-
// Base function classes for creating custom functions
|
|
31
|
-
export { default as Function } from "../function";
|
|
32
|
-
export { default as AggregateFunction } from "../aggregate_function";
|
|
33
|
-
export { default as PredicateFunction } from "../predicate_function";
|
|
34
|
-
export { default as ReducerElement } from "../reducer_element";
|
|
35
|
-
|
|
36
|
-
// Decorator and metadata types for function registration
|
|
37
|
-
export {
|
|
38
|
-
FunctionDef,
|
|
39
|
-
FunctionMetadata,
|
|
40
|
-
FunctionDefOptions,
|
|
41
|
-
ParameterSchema,
|
|
42
|
-
OutputSchema,
|
|
43
|
-
FunctionCategory
|
|
44
|
-
} from "../function_metadata";
|
|
45
|
-
|
|
46
|
-
// Factory for advanced usage
|
|
47
|
-
export { default as FunctionFactory } from "../function_factory";
|
|
48
|
-
export type { FunctionCreator, AsyncDataProvider } from "../function_factory";
|
|
49
|
-
|
|
50
|
-
// Registration option types
|
|
51
|
-
export type {
|
|
52
|
-
RegisterFunctionOptions,
|
|
53
|
-
RegisterAsyncProviderOptions
|
|
54
|
-
} from "../function_metadata";
|
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import FunctionFactory from "../../src/parsing/functions/function_factory";
|
|
2
|
-
import Function from "../../src/parsing/functions/function";
|
|
3
|
-
import { FunctionMetadata } from "../../src/parsing/functions/function_metadata";
|
|
4
|
-
import Runner from "../../src/compute/runner";
|
|
5
|
-
|
|
6
|
-
// Custom test function
|
|
7
|
-
class UpperCase extends Function {
|
|
8
|
-
constructor() {
|
|
9
|
-
super("uppercase");
|
|
10
|
-
this._expectedParameterCount = 1;
|
|
11
|
-
}
|
|
12
|
-
public value(): any {
|
|
13
|
-
return String(this.getChildren()[0].value()).toUpperCase();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("FunctionFactory Plugin Registration", () => {
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
// Clean up registered functions after each test
|
|
20
|
-
FunctionFactory.unregister("uppercase");
|
|
21
|
-
FunctionFactory.unregister("testfunc");
|
|
22
|
-
FunctionFactory.unregisterAsyncProvider("testprovider");
|
|
23
|
-
FunctionFactory.unregisterAsyncProvider("asyncdata");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe("register()", () => {
|
|
27
|
-
it("should register a simple function factory", () => {
|
|
28
|
-
FunctionFactory.register("uppercase", () => new UpperCase());
|
|
29
|
-
const func = FunctionFactory.create("uppercase");
|
|
30
|
-
expect(func).toBeInstanceOf(UpperCase);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("should register a function with metadata", () => {
|
|
34
|
-
FunctionFactory.register("uppercase", {
|
|
35
|
-
factory: () => new UpperCase(),
|
|
36
|
-
metadata: {
|
|
37
|
-
name: "uppercase",
|
|
38
|
-
description: "Converts a string to uppercase",
|
|
39
|
-
category: "string",
|
|
40
|
-
parameters: [
|
|
41
|
-
{ name: "text", description: "String to convert", type: "string" }
|
|
42
|
-
],
|
|
43
|
-
output: { description: "Uppercase string", type: "string" },
|
|
44
|
-
examples: ["WITH 'hello' AS s RETURN uppercase(s)"]
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const func = FunctionFactory.create("uppercase");
|
|
49
|
-
expect(func).toBeInstanceOf(UpperCase);
|
|
50
|
-
|
|
51
|
-
const metadata = FunctionFactory.getMetadata("uppercase");
|
|
52
|
-
expect(metadata).toBeDefined();
|
|
53
|
-
expect(metadata?.description).toBe("Converts a string to uppercase");
|
|
54
|
-
expect(metadata?.category).toBe("string");
|
|
55
|
-
expect(metadata?.parameters).toHaveLength(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should be case-insensitive", () => {
|
|
59
|
-
FunctionFactory.register("UpperCase", () => new UpperCase());
|
|
60
|
-
const func = FunctionFactory.create("UPPERCASE");
|
|
61
|
-
expect(func).toBeInstanceOf(UpperCase);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("unregister()", () => {
|
|
66
|
-
it("should unregister a function and its metadata", () => {
|
|
67
|
-
FunctionFactory.register("testfunc", {
|
|
68
|
-
factory: () => new Function("testfunc"),
|
|
69
|
-
metadata: {
|
|
70
|
-
name: "testfunc",
|
|
71
|
-
description: "Test function",
|
|
72
|
-
parameters: [],
|
|
73
|
-
output: { description: "Test output", type: "string" }
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(FunctionFactory.getMetadata("testfunc")).toBeDefined();
|
|
78
|
-
|
|
79
|
-
FunctionFactory.unregister("testfunc");
|
|
80
|
-
|
|
81
|
-
expect(FunctionFactory.getMetadata("testfunc")).toBeUndefined();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("registerAsyncProvider()", () => {
|
|
86
|
-
it("should register a simple async provider", () => {
|
|
87
|
-
const provider = async function* () {
|
|
88
|
-
yield { id: 1 };
|
|
89
|
-
yield { id: 2 };
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
FunctionFactory.registerAsyncProvider("testprovider", provider);
|
|
93
|
-
|
|
94
|
-
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(true);
|
|
95
|
-
expect(FunctionFactory.getAsyncProvider("testprovider")).toBe(provider);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("should register an async provider with metadata", () => {
|
|
99
|
-
FunctionFactory.registerAsyncProvider("asyncdata", {
|
|
100
|
-
provider: async function* (url: string) {
|
|
101
|
-
yield { data: url };
|
|
102
|
-
},
|
|
103
|
-
metadata: {
|
|
104
|
-
name: "asyncdata",
|
|
105
|
-
description: "Fetches data from a URL",
|
|
106
|
-
category: "data",
|
|
107
|
-
parameters: [
|
|
108
|
-
{ name: "url", description: "The URL to fetch", type: "string" }
|
|
109
|
-
],
|
|
110
|
-
output: {
|
|
111
|
-
description: "Data object",
|
|
112
|
-
type: "object",
|
|
113
|
-
properties: {
|
|
114
|
-
data: { description: "The fetched data", type: "string" }
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
examples: ["LOAD JSON FROM asyncdata('http://example.com') AS item"]
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
expect(FunctionFactory.isAsyncProvider("asyncdata")).toBe(true);
|
|
122
|
-
|
|
123
|
-
const metadata = FunctionFactory.getMetadata("asyncdata");
|
|
124
|
-
expect(metadata).toBeDefined();
|
|
125
|
-
expect(metadata?.description).toBe("Fetches data from a URL");
|
|
126
|
-
expect(metadata?.isAsyncProvider).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("unregisterAsyncProvider()", () => {
|
|
131
|
-
it("should unregister an async provider and its metadata", () => {
|
|
132
|
-
FunctionFactory.registerAsyncProvider("testprovider", {
|
|
133
|
-
provider: async function* () { yield 1; },
|
|
134
|
-
metadata: {
|
|
135
|
-
name: "testprovider",
|
|
136
|
-
description: "Test provider",
|
|
137
|
-
parameters: [],
|
|
138
|
-
output: { description: "Number", type: "number" }
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(true);
|
|
143
|
-
expect(FunctionFactory.getMetadata("testprovider")).toBeDefined();
|
|
144
|
-
|
|
145
|
-
FunctionFactory.unregisterAsyncProvider("testprovider");
|
|
146
|
-
|
|
147
|
-
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(false);
|
|
148
|
-
expect(FunctionFactory.getMetadata("testprovider")).toBeUndefined();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe("FunctionFactory Metadata Queries", () => {
|
|
154
|
-
beforeAll(() => {
|
|
155
|
-
// Register test functions for metadata queries
|
|
156
|
-
FunctionFactory.register("customfunc", {
|
|
157
|
-
factory: () => new Function("customfunc"),
|
|
158
|
-
metadata: {
|
|
159
|
-
name: "customfunc",
|
|
160
|
-
description: "A custom test function",
|
|
161
|
-
category: "test",
|
|
162
|
-
parameters: [{ name: "arg", description: "An argument", type: "string" }],
|
|
163
|
-
output: { description: "Result", type: "string" }
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
FunctionFactory.registerAsyncProvider("customprovider", {
|
|
168
|
-
provider: async function* () { yield 1; },
|
|
169
|
-
metadata: {
|
|
170
|
-
name: "customprovider",
|
|
171
|
-
description: "A custom async provider",
|
|
172
|
-
category: "test",
|
|
173
|
-
parameters: [],
|
|
174
|
-
output: { description: "Number", type: "number" }
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
afterAll(() => {
|
|
180
|
-
FunctionFactory.unregister("customfunc");
|
|
181
|
-
FunctionFactory.unregisterAsyncProvider("customprovider");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe("getMetadata()", () => {
|
|
185
|
-
it("should return metadata for built-in functions", () => {
|
|
186
|
-
const metadata = FunctionFactory.getMetadata("sum");
|
|
187
|
-
expect(metadata).toBeDefined();
|
|
188
|
-
expect(metadata?.name).toBe("sum");
|
|
189
|
-
expect(metadata?.category).toBe("aggregate");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("should return metadata for plugin functions", () => {
|
|
193
|
-
const metadata = FunctionFactory.getMetadata("customfunc");
|
|
194
|
-
expect(metadata).toBeDefined();
|
|
195
|
-
expect(metadata?.description).toBe("A custom test function");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("should return undefined for unknown functions", () => {
|
|
199
|
-
const metadata = FunctionFactory.getMetadata("nonexistent");
|
|
200
|
-
expect(metadata).toBeUndefined();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("should be case-insensitive", () => {
|
|
204
|
-
const metadata = FunctionFactory.getMetadata("SUM");
|
|
205
|
-
expect(metadata).toBeDefined();
|
|
206
|
-
expect(metadata?.name).toBe("sum");
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe("listFunctions()", () => {
|
|
211
|
-
it("should list all functions including built-ins", () => {
|
|
212
|
-
const functions = FunctionFactory.listFunctions();
|
|
213
|
-
expect(functions.length).toBeGreaterThan(10);
|
|
214
|
-
expect(functions.some(f => f.name === "sum")).toBe(true);
|
|
215
|
-
expect(functions.some(f => f.name === "customfunc")).toBe(true);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("should filter by category", () => {
|
|
219
|
-
const aggregationFuncs = FunctionFactory.listFunctions({ category: "aggregate" });
|
|
220
|
-
expect(aggregationFuncs.every(f => f.category === "aggregate")).toBe(true);
|
|
221
|
-
expect(aggregationFuncs.some(f => f.name === "sum")).toBe(true);
|
|
222
|
-
expect(aggregationFuncs.some(f => f.name === "avg")).toBe(true);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("should filter async providers only", () => {
|
|
226
|
-
const asyncFuncs = FunctionFactory.listFunctions({ asyncOnly: true });
|
|
227
|
-
expect(asyncFuncs.every(f => f.isAsyncProvider === true)).toBe(true);
|
|
228
|
-
expect(asyncFuncs.some(f => f.name === "customprovider")).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("should filter sync functions only", () => {
|
|
232
|
-
const syncFuncs = FunctionFactory.listFunctions({ syncOnly: true });
|
|
233
|
-
expect(syncFuncs.every(f => !f.isAsyncProvider)).toBe(true);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("should exclude built-ins when specified", () => {
|
|
237
|
-
const pluginsOnly = FunctionFactory.listFunctions({ includeBuiltins: false });
|
|
238
|
-
expect(pluginsOnly.some(f => f.name === "sum")).toBe(false);
|
|
239
|
-
expect(pluginsOnly.some(f => f.name === "customfunc")).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe("listFunctionNames()", () => {
|
|
244
|
-
it("should return all function names", () => {
|
|
245
|
-
const names = FunctionFactory.listFunctionNames();
|
|
246
|
-
expect(names).toContain("sum");
|
|
247
|
-
expect(names).toContain("avg");
|
|
248
|
-
expect(names).toContain("customfunc");
|
|
249
|
-
expect(names).toContain("customprovider");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it("should not have duplicates", () => {
|
|
253
|
-
const names = FunctionFactory.listFunctionNames();
|
|
254
|
-
const uniqueNames = [...new Set(names)];
|
|
255
|
-
expect(names.length).toBe(uniqueNames.length);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe("toJSON()", () => {
|
|
260
|
-
it("should return functions and categories", () => {
|
|
261
|
-
const result = FunctionFactory.toJSON();
|
|
262
|
-
expect(result.functions).toBeDefined();
|
|
263
|
-
expect(Array.isArray(result.functions)).toBe(true);
|
|
264
|
-
expect(result.categories).toBeDefined();
|
|
265
|
-
expect(Array.isArray(result.categories)).toBe(true);
|
|
266
|
-
expect(result.categories).toContain("aggregate");
|
|
267
|
-
expect(result.categories).toContain("scalar");
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
describe("functions() built-in function", () => {
|
|
273
|
-
it("should return all functions when called without arguments", async () => {
|
|
274
|
-
const runner = new Runner("WITH functions() AS funcs RETURN size(funcs)");
|
|
275
|
-
await runner.run();
|
|
276
|
-
expect(runner.results[0].expr0).toBeGreaterThan(10);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it("should return function metadata with expected properties", async () => {
|
|
280
|
-
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'sum' AND f.category = 'aggregate' RETURN f.name, f.description, f.category");
|
|
281
|
-
await runner.run();
|
|
282
|
-
expect(runner.results).toHaveLength(1);
|
|
283
|
-
expect(runner.results[0].expr0).toBe("sum");
|
|
284
|
-
expect(runner.results[0].expr1).toBe("Calculates the sum of numeric values across grouped rows");
|
|
285
|
-
expect(runner.results[0].expr2).toBe("aggregate");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("should filter by category when argument provided", async () => {
|
|
289
|
-
const runner = new Runner("WITH functions('aggregate') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
290
|
-
await runner.run();
|
|
291
|
-
const names = runner.results.map((r: any) => r.expr0);
|
|
292
|
-
expect(names).toContain("sum");
|
|
293
|
-
expect(names).toContain("avg");
|
|
294
|
-
expect(names).toContain("collect");
|
|
295
|
-
expect(names).not.toContain("split"); // scalar category
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it("should include the functions() function itself", async () => {
|
|
299
|
-
const runner = new Runner("WITH functions('scalar') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
300
|
-
await runner.run();
|
|
301
|
-
expect(runner.results.some((r: any) => r.expr0 === "functions")).toBe(true);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it("should include parameter information", async () => {
|
|
305
|
-
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'split' RETURN f.parameters");
|
|
306
|
-
await runner.run();
|
|
307
|
-
const params = runner.results[0].expr0;
|
|
308
|
-
expect(Array.isArray(params)).toBe(true);
|
|
309
|
-
expect(params.length).toBe(2);
|
|
310
|
-
expect(params[0].name).toBe("text");
|
|
311
|
-
expect(params[1].name).toBe("delimiter");
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it("should include output schema", async () => {
|
|
315
|
-
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'sum' RETURN f.output");
|
|
316
|
-
await runner.run();
|
|
317
|
-
const output = runner.results[0].expr0;
|
|
318
|
-
expect(output.type).toBe("number");
|
|
319
|
-
expect(output.description).toBeDefined();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("should include examples", async () => {
|
|
323
|
-
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'range' RETURN f.examples");
|
|
324
|
-
await runner.run();
|
|
325
|
-
const examples = runner.results[0].expr0;
|
|
326
|
-
expect(Array.isArray(examples)).toBe(true);
|
|
327
|
-
expect(examples.length).toBeGreaterThan(0);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
describe("Plugin function execution", () => {
|
|
332
|
-
beforeAll(() => {
|
|
333
|
-
FunctionFactory.register("double", {
|
|
334
|
-
factory: () => {
|
|
335
|
-
const func = new Function("double");
|
|
336
|
-
(func as any)._expectedParameterCount = 1;
|
|
337
|
-
(func as any).value = function() {
|
|
338
|
-
return this.getChildren()[0].value() * 2;
|
|
339
|
-
};
|
|
340
|
-
return func;
|
|
341
|
-
},
|
|
342
|
-
metadata: {
|
|
343
|
-
name: "double",
|
|
344
|
-
description: "Doubles a number",
|
|
345
|
-
category: "math",
|
|
346
|
-
parameters: [{ name: "n", description: "Number to double", type: "number" }],
|
|
347
|
-
output: { description: "Doubled value", type: "number" }
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
afterAll(() => {
|
|
353
|
-
FunctionFactory.unregister("double");
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("should execute a registered plugin function", async () => {
|
|
357
|
-
const runner = new Runner("WITH 5 AS n RETURN double(n)");
|
|
358
|
-
await runner.run();
|
|
359
|
-
expect(runner.results[0].expr0).toBe(10);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("should show plugin function in functions() output", async () => {
|
|
363
|
-
const runner = new Runner("WITH functions('math') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
364
|
-
await runner.run();
|
|
365
|
-
const names = runner.results.map((r: any) => r.expr0);
|
|
366
|
-
expect(names).toContain("double");
|
|
367
|
-
// round is in 'scalar' category, not 'math'
|
|
368
|
-
});
|
|
369
|
-
});
|