flowquery 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/.github/workflows/npm-publish.yml +2 -0
  2. package/.github/workflows/release.yml +24 -9
  3. package/dist/compute/runner.js +75 -0
  4. package/dist/compute/runner.js.map +1 -0
  5. package/dist/flowquery.min.js +1 -0
  6. package/dist/index.browser.js +119 -0
  7. package/dist/index.browser.js.map +1 -0
  8. package/dist/index.js +16 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/index.node.js +123 -0
  11. package/dist/index.node.js.map +1 -0
  12. package/dist/io/command_line.js +102 -0
  13. package/dist/io/command_line.js.map +1 -0
  14. package/dist/parsing/alias.js +23 -0
  15. package/dist/parsing/alias.js.map +1 -0
  16. package/dist/parsing/alias_option.js +11 -0
  17. package/dist/parsing/alias_option.js.map +1 -0
  18. package/dist/parsing/ast_node.js +145 -0
  19. package/dist/parsing/ast_node.js.map +1 -0
  20. package/dist/parsing/base_parser.js +92 -0
  21. package/dist/parsing/base_parser.js.map +1 -0
  22. package/dist/parsing/components/csv.js +13 -0
  23. package/dist/parsing/components/csv.js.map +1 -0
  24. package/dist/parsing/components/from.js +16 -0
  25. package/dist/parsing/components/from.js.map +1 -0
  26. package/dist/parsing/components/headers.js +16 -0
  27. package/dist/parsing/components/headers.js.map +1 -0
  28. package/dist/parsing/components/json.js +13 -0
  29. package/dist/parsing/components/json.js.map +1 -0
  30. package/dist/parsing/components/null.js +13 -0
  31. package/dist/parsing/components/null.js.map +1 -0
  32. package/dist/parsing/components/post.js +13 -0
  33. package/dist/parsing/components/post.js.map +1 -0
  34. package/dist/parsing/components/text.js +13 -0
  35. package/dist/parsing/components/text.js.map +1 -0
  36. package/dist/parsing/context.js +47 -0
  37. package/dist/parsing/context.js.map +1 -0
  38. package/dist/parsing/data_structures/associative_array.js +45 -0
  39. package/dist/parsing/data_structures/associative_array.js.map +1 -0
  40. package/dist/parsing/data_structures/json_array.js +35 -0
  41. package/dist/parsing/data_structures/json_array.js.map +1 -0
  42. package/dist/parsing/data_structures/key_value_pair.js +41 -0
  43. package/dist/parsing/data_structures/key_value_pair.js.map +1 -0
  44. package/dist/parsing/data_structures/lookup.js +44 -0
  45. package/dist/parsing/data_structures/lookup.js.map +1 -0
  46. package/dist/parsing/data_structures/range_lookup.js +40 -0
  47. package/dist/parsing/data_structures/range_lookup.js.map +1 -0
  48. package/dist/parsing/expressions/expression.js +142 -0
  49. package/dist/parsing/expressions/expression.js.map +1 -0
  50. package/dist/parsing/expressions/f_string.js +29 -0
  51. package/dist/parsing/expressions/f_string.js.map +1 -0
  52. package/dist/parsing/expressions/identifier.js +26 -0
  53. package/dist/parsing/expressions/identifier.js.map +1 -0
  54. package/dist/parsing/expressions/number.js +41 -0
  55. package/dist/parsing/expressions/number.js.map +1 -0
  56. package/dist/parsing/expressions/operator.js +180 -0
  57. package/dist/parsing/expressions/operator.js.map +1 -0
  58. package/dist/parsing/expressions/reference.js +45 -0
  59. package/dist/parsing/expressions/reference.js.map +1 -0
  60. package/dist/parsing/expressions/string.js +34 -0
  61. package/dist/parsing/expressions/string.js.map +1 -0
  62. package/dist/parsing/functions/aggregate_function.js +58 -0
  63. package/dist/parsing/functions/aggregate_function.js.map +1 -0
  64. package/dist/parsing/functions/async_function.js +119 -0
  65. package/dist/parsing/functions/async_function.js.map +1 -0
  66. package/dist/parsing/functions/avg.js +43 -0
  67. package/dist/parsing/functions/avg.js.map +1 -0
  68. package/dist/parsing/functions/collect.js +52 -0
  69. package/dist/parsing/functions/collect.js.map +1 -0
  70. package/dist/parsing/functions/function.js +59 -0
  71. package/dist/parsing/functions/function.js.map +1 -0
  72. package/dist/parsing/functions/function_factory.js +259 -0
  73. package/dist/parsing/functions/function_factory.js.map +1 -0
  74. package/dist/parsing/functions/function_metadata.js +159 -0
  75. package/dist/parsing/functions/function_metadata.js.map +1 -0
  76. package/dist/parsing/functions/functions.js +47 -0
  77. package/dist/parsing/functions/functions.js.map +1 -0
  78. package/dist/parsing/functions/join.js +29 -0
  79. package/dist/parsing/functions/join.js.map +1 -0
  80. package/dist/parsing/functions/predicate_function.js +37 -0
  81. package/dist/parsing/functions/predicate_function.js.map +1 -0
  82. package/dist/parsing/functions/predicate_function_factory.js +19 -0
  83. package/dist/parsing/functions/predicate_function_factory.js.map +1 -0
  84. package/dist/parsing/functions/predicate_sum.js +33 -0
  85. package/dist/parsing/functions/predicate_sum.js.map +1 -0
  86. package/dist/parsing/functions/rand.js +17 -0
  87. package/dist/parsing/functions/rand.js.map +1 -0
  88. package/dist/parsing/functions/range.js +22 -0
  89. package/dist/parsing/functions/range.js.map +1 -0
  90. package/dist/parsing/functions/reducer_element.js +12 -0
  91. package/dist/parsing/functions/reducer_element.js.map +1 -0
  92. package/dist/parsing/functions/replace.js +23 -0
  93. package/dist/parsing/functions/replace.js.map +1 -0
  94. package/dist/parsing/functions/round.js +21 -0
  95. package/dist/parsing/functions/round.js.map +1 -0
  96. package/dist/parsing/functions/size.js +21 -0
  97. package/dist/parsing/functions/size.js.map +1 -0
  98. package/dist/parsing/functions/split.js +29 -0
  99. package/dist/parsing/functions/split.js.map +1 -0
  100. package/dist/parsing/functions/stringify.js +29 -0
  101. package/dist/parsing/functions/stringify.js.map +1 -0
  102. package/dist/parsing/functions/sum.js +38 -0
  103. package/dist/parsing/functions/sum.js.map +1 -0
  104. package/dist/parsing/functions/to_json.js +21 -0
  105. package/dist/parsing/functions/to_json.js.map +1 -0
  106. package/dist/parsing/functions/value_holder.js +16 -0
  107. package/dist/parsing/functions/value_holder.js.map +1 -0
  108. package/dist/parsing/logic/case.js +27 -0
  109. package/dist/parsing/logic/case.js.map +1 -0
  110. package/dist/parsing/logic/else.js +16 -0
  111. package/dist/parsing/logic/else.js.map +1 -0
  112. package/dist/parsing/logic/end.js +13 -0
  113. package/dist/parsing/logic/end.js.map +1 -0
  114. package/dist/parsing/logic/then.js +16 -0
  115. package/dist/parsing/logic/then.js.map +1 -0
  116. package/dist/parsing/logic/when.js +16 -0
  117. package/dist/parsing/logic/when.js.map +1 -0
  118. package/dist/parsing/operations/aggregated_return.js +35 -0
  119. package/dist/parsing/operations/aggregated_return.js.map +1 -0
  120. package/dist/parsing/operations/aggregated_with.js +41 -0
  121. package/dist/parsing/operations/aggregated_with.js.map +1 -0
  122. package/dist/parsing/operations/group_by.js +139 -0
  123. package/dist/parsing/operations/group_by.js.map +1 -0
  124. package/dist/parsing/operations/limit.js +38 -0
  125. package/dist/parsing/operations/limit.js.map +1 -0
  126. package/dist/parsing/operations/load.js +174 -0
  127. package/dist/parsing/operations/load.js.map +1 -0
  128. package/dist/parsing/operations/operation.js +81 -0
  129. package/dist/parsing/operations/operation.js.map +1 -0
  130. package/dist/parsing/operations/projection.js +21 -0
  131. package/dist/parsing/operations/projection.js.map +1 -0
  132. package/dist/parsing/operations/return.js +60 -0
  133. package/dist/parsing/operations/return.js.map +1 -0
  134. package/dist/parsing/operations/unwind.js +46 -0
  135. package/dist/parsing/operations/unwind.js.map +1 -0
  136. package/dist/parsing/operations/where.js +53 -0
  137. package/dist/parsing/operations/where.js.map +1 -0
  138. package/dist/parsing/operations/with.js +36 -0
  139. package/dist/parsing/operations/with.js.map +1 -0
  140. package/dist/parsing/parser.js +812 -0
  141. package/dist/parsing/parser.js.map +1 -0
  142. package/dist/parsing/token_to_node.js +121 -0
  143. package/dist/parsing/token_to_node.js.map +1 -0
  144. package/dist/tokenization/keyword.js +46 -0
  145. package/dist/tokenization/keyword.js.map +1 -0
  146. package/dist/tokenization/operator.js +28 -0
  147. package/dist/tokenization/operator.js.map +1 -0
  148. package/dist/tokenization/string_walker.js +165 -0
  149. package/dist/tokenization/string_walker.js.map +1 -0
  150. package/dist/tokenization/symbol.js +18 -0
  151. package/dist/tokenization/symbol.js.map +1 -0
  152. package/dist/tokenization/token.js +484 -0
  153. package/dist/tokenization/token.js.map +1 -0
  154. package/dist/tokenization/token_mapper.js +55 -0
  155. package/dist/tokenization/token_mapper.js.map +1 -0
  156. package/dist/tokenization/token_type.js +19 -0
  157. package/dist/tokenization/token_type.js.map +1 -0
  158. package/dist/tokenization/tokenizer.js +220 -0
  159. package/dist/tokenization/tokenizer.js.map +1 -0
  160. package/dist/tokenization/trie.js +111 -0
  161. package/dist/tokenization/trie.js.map +1 -0
  162. package/dist/utils/object_utils.js +19 -0
  163. package/dist/utils/object_utils.js.map +1 -0
  164. package/dist/utils/string_utils.js +110 -0
  165. package/dist/utils/string_utils.js.map +1 -0
  166. package/docs/flowquery.min.js +1 -1
  167. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  168. package/package.json +22 -4
  169. package/src/compute/runner.ts +45 -0
  170. package/src/index.browser.ts +118 -0
  171. package/src/index.node.ts +141 -0
  172. package/src/parsing/functions/async_function.ts +95 -0
  173. package/src/parsing/functions/function_factory.ts +230 -1
  174. package/src/parsing/functions/function_metadata.ts +238 -0
  175. package/src/parsing/functions/functions.ts +43 -0
  176. package/src/parsing/operations/load.ts +51 -2
  177. package/src/parsing/parser.ts +45 -4
  178. package/tests/parsing/function_plugins.test.ts +369 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Schema definition for function arguments and outputs.
3
+ * Compatible with JSON Schema for LLM consumption.
4
+ */
5
+ export interface ParameterSchema {
6
+ /** The parameter name */
7
+ name: string;
8
+ /** Description of the parameter */
9
+ description: string;
10
+ /** JSON Schema type: string, number, boolean, object, array, null */
11
+ type: "string" | "number" | "boolean" | "object" | "array" | "null" | string;
12
+ /** Whether the parameter is required (default: true) */
13
+ required?: boolean;
14
+ /** Default value if not provided */
15
+ default?: any;
16
+ /** For arrays, the schema of items */
17
+ items?: Omit<ParameterSchema, 'name' | 'required' | 'default'>;
18
+ /** For objects, the properties schema */
19
+ properties?: Record<string, Omit<ParameterSchema, 'name' | 'required'>>;
20
+ /** Enum of allowed values */
21
+ enum?: any[];
22
+ /** Example value */
23
+ example?: any;
24
+ }
25
+
26
+ /**
27
+ * Schema definition for function output.
28
+ */
29
+ export interface OutputSchema {
30
+ /** Description of the output */
31
+ description: string;
32
+ /** JSON Schema type */
33
+ type: "string" | "number" | "boolean" | "object" | "array" | "null" | string;
34
+ /** For arrays, the schema of items */
35
+ items?: Omit<OutputSchema, 'description'>;
36
+ /** For objects, the properties schema */
37
+ properties?: Record<string, Omit<ParameterSchema, 'name' | 'required'>>;
38
+ /** Example output value */
39
+ example?: any;
40
+ }
41
+
42
+ /**
43
+ * Metadata for a registered function, designed for LLM consumption.
44
+ */
45
+ export interface FunctionMetadata {
46
+ /** The function name */
47
+ name: string;
48
+ /** Human-readable description of what the function does */
49
+ description: string;
50
+ /** Category for grouping functions (e.g., "aggregation", "string", "data") */
51
+ category?: string;
52
+ /** Array of parameter schemas */
53
+ parameters: ParameterSchema[];
54
+ /** Output schema */
55
+ output: OutputSchema;
56
+ /** Example usage in FlowQuery syntax */
57
+ examples?: string[];
58
+ /** Whether this is an async data provider for LOAD operations */
59
+ isAsyncProvider?: boolean;
60
+ /** Additional notes or caveats */
61
+ notes?: string;
62
+ }
63
+
64
+ /**
65
+ * Options for registering a sync function with metadata.
66
+ */
67
+ export interface RegisterFunctionOptions {
68
+ /** Factory function that creates the Function instance */
69
+ factory: () => any;
70
+ /** Function metadata for documentation */
71
+ metadata: FunctionMetadata;
72
+ }
73
+
74
+ /**
75
+ * Options for registering an async data provider with metadata.
76
+ */
77
+ export interface RegisterAsyncProviderOptions {
78
+ /** Async generator or function that returns data */
79
+ provider: (...args: any[]) => AsyncGenerator<any, void, unknown> | Promise<any>;
80
+ /** Function metadata for documentation */
81
+ metadata: FunctionMetadata;
82
+ }
83
+
84
+ /**
85
+ * Built-in function metadata definitions.
86
+ */
87
+ export const BUILTIN_FUNCTION_METADATA: FunctionMetadata[] = [
88
+ {
89
+ name: "sum",
90
+ description: "Calculates the sum of numeric values across grouped rows",
91
+ category: "aggregation",
92
+ parameters: [
93
+ { name: "value", description: "Numeric value to sum", type: "number" }
94
+ ],
95
+ output: { description: "Sum of all values", type: "number", example: 150 },
96
+ examples: ["WITH [1, 2, 3] AS nums UNWIND nums AS n RETURN sum(n)"]
97
+ },
98
+ {
99
+ name: "avg",
100
+ description: "Calculates the average of numeric values across grouped rows",
101
+ category: "aggregation",
102
+ parameters: [
103
+ { name: "value", description: "Numeric value to average", type: "number" }
104
+ ],
105
+ output: { description: "Average of all values", type: "number", example: 50 },
106
+ examples: ["WITH [10, 20, 30] AS nums UNWIND nums AS n RETURN avg(n)"]
107
+ },
108
+ {
109
+ name: "collect",
110
+ description: "Collects values into an array across grouped rows",
111
+ category: "aggregation",
112
+ parameters: [
113
+ { name: "value", description: "Value to collect", type: "any" }
114
+ ],
115
+ output: { description: "Array of collected values", type: "array", example: [1, 2, 3] },
116
+ examples: ["WITH [1, 2, 3] AS nums UNWIND nums AS n RETURN collect(n)"]
117
+ },
118
+ {
119
+ name: "range",
120
+ description: "Generates an array of sequential integers",
121
+ category: "generator",
122
+ parameters: [
123
+ { name: "start", description: "Starting number (inclusive)", type: "number" },
124
+ { name: "end", description: "Ending number (inclusive)", type: "number" }
125
+ ],
126
+ output: { description: "Array of integers from start to end", type: "array", items: { type: "number" }, example: [1, 2, 3, 4, 5] },
127
+ examples: ["WITH range(1, 5) AS nums RETURN nums"]
128
+ },
129
+ {
130
+ name: "rand",
131
+ description: "Generates a random number between 0 and 1",
132
+ category: "generator",
133
+ parameters: [],
134
+ output: { description: "Random number between 0 and 1", type: "number", example: 0.7234 },
135
+ examples: ["WITH rand() AS r RETURN r"]
136
+ },
137
+ {
138
+ name: "round",
139
+ description: "Rounds a number to the nearest integer",
140
+ category: "math",
141
+ parameters: [
142
+ { name: "value", description: "Number to round", type: "number" }
143
+ ],
144
+ output: { description: "Rounded integer", type: "number", example: 4 },
145
+ examples: ["WITH 3.7 AS n RETURN round(n)"]
146
+ },
147
+ {
148
+ name: "split",
149
+ description: "Splits a string into an array by a delimiter",
150
+ category: "string",
151
+ parameters: [
152
+ { name: "text", description: "String to split", type: "string" },
153
+ { name: "delimiter", description: "Delimiter to split by", type: "string" }
154
+ ],
155
+ output: { description: "Array of string parts", type: "array", items: { type: "string" }, example: ["a", "b", "c"] },
156
+ examples: ["WITH 'a,b,c' AS s RETURN split(s, ',')"]
157
+ },
158
+ {
159
+ name: "join",
160
+ description: "Joins an array of strings with a delimiter",
161
+ category: "string",
162
+ parameters: [
163
+ { name: "array", description: "Array of values to join", type: "array" },
164
+ { name: "delimiter", description: "Delimiter to join with", type: "string" }
165
+ ],
166
+ output: { description: "Joined string", type: "string", example: "a,b,c" },
167
+ examples: ["WITH ['a', 'b', 'c'] AS arr RETURN join(arr, ',')"]
168
+ },
169
+ {
170
+ name: "replace",
171
+ description: "Replaces occurrences of a pattern in a string",
172
+ category: "string",
173
+ parameters: [
174
+ { name: "text", description: "Source string", type: "string" },
175
+ { name: "pattern", description: "Pattern to find", type: "string" },
176
+ { name: "replacement", description: "Replacement string", type: "string" }
177
+ ],
178
+ output: { description: "String with replacements", type: "string", example: "hello world" },
179
+ examples: ["WITH 'hello there' AS s RETURN replace(s, 'there', 'world')"]
180
+ },
181
+ {
182
+ name: "stringify",
183
+ description: "Converts a value to its JSON string representation",
184
+ category: "conversion",
185
+ parameters: [
186
+ { name: "value", description: "Value to stringify", type: "any" }
187
+ ],
188
+ output: { description: "JSON string", type: "string", example: "{\"a\":1}" },
189
+ examples: ["WITH {a: 1} AS obj RETURN stringify(obj)"]
190
+ },
191
+ {
192
+ name: "tojson",
193
+ description: "Parses a JSON string into an object",
194
+ category: "conversion",
195
+ parameters: [
196
+ { name: "text", description: "JSON string to parse", type: "string" }
197
+ ],
198
+ output: { description: "Parsed object or array", type: "object", example: { a: 1 } },
199
+ examples: ["WITH '{\"a\": 1}' AS s RETURN tojson(s)"]
200
+ },
201
+ {
202
+ name: "size",
203
+ description: "Returns the length of an array or string",
204
+ category: "utility",
205
+ parameters: [
206
+ { name: "value", description: "Array or string to measure", type: "array" }
207
+ ],
208
+ output: { description: "Length of the input", type: "number", example: 3 },
209
+ examples: ["WITH [1, 2, 3] AS arr RETURN size(arr)"]
210
+ },
211
+ {
212
+ name: "functions",
213
+ description: "Lists all registered functions with their metadata. Useful for discovering available functions and their documentation. Results include name, description, parameters, output schema, and usage examples.",
214
+ category: "introspection",
215
+ parameters: [
216
+ { name: "category", description: "Optional category to filter by (e.g., 'aggregation', 'string', 'math')", type: "string", required: false }
217
+ ],
218
+ output: {
219
+ description: "Array of function metadata objects",
220
+ type: "array",
221
+ items: {
222
+ type: "object",
223
+ properties: {
224
+ name: { description: "Function name", type: "string" },
225
+ description: { description: "What the function does", type: "string" },
226
+ category: { description: "Function category", type: "string" },
227
+ parameters: { description: "Array of parameter definitions", type: "array" },
228
+ output: { description: "Output schema", type: "object" },
229
+ examples: { description: "Usage examples", type: "array" }
230
+ }
231
+ }
232
+ },
233
+ examples: [
234
+ "WITH functions() AS funcs RETURN funcs",
235
+ "WITH functions('aggregation') AS funcs UNWIND funcs AS f RETURN f.name, f.description"
236
+ ]
237
+ }
238
+ ];
@@ -0,0 +1,43 @@
1
+ import Function from "./function";
2
+ import FunctionFactory from "./function_factory";
3
+
4
+ /**
5
+ * Built-in function that lists all registered functions with their metadata.
6
+ *
7
+ * Can be used in FlowQuery to discover available functions:
8
+ * - `WITH functions() AS funcs RETURN funcs` - returns all functions
9
+ * - `WITH functions('aggregation') AS funcs RETURN funcs` - returns functions in a category
10
+ *
11
+ * @example
12
+ * ```
13
+ * WITH functions() AS funcs
14
+ * UNWIND funcs AS func
15
+ * RETURN func.name, func.description
16
+ * ```
17
+ */
18
+ class Functions extends Function {
19
+ constructor() {
20
+ super("functions");
21
+ this._expectedParameterCount = null; // 0 or 1 parameter
22
+ }
23
+
24
+ public value(): any {
25
+ const children = this.getChildren();
26
+
27
+ if (children.length === 0) {
28
+ // Return all functions
29
+ return FunctionFactory.listFunctions();
30
+ } else if (children.length === 1) {
31
+ // Filter by category
32
+ const category = children[0].value();
33
+ if (typeof category === 'string') {
34
+ return FunctionFactory.listFunctions({ category });
35
+ }
36
+ throw new Error("functions() category parameter must be a string");
37
+ } else {
38
+ throw new Error("functions() takes 0 or 1 parameters");
39
+ }
40
+ }
41
+ }
42
+
43
+ export default Functions;
@@ -3,12 +3,14 @@ import CSV from "../components/csv";
3
3
  import {default as _JSON} from "../components/json";
4
4
  import Text from "../components/text";
5
5
  import Function from "../functions/function";
6
+ import AsyncFunction from "../functions/async_function";
6
7
  import AssociativeArray from "../data_structures/associative_array";
7
8
  import Reference from "../expressions/reference";
8
9
  import Expression from "../expressions/expression";
9
10
  import Headers from "../components/headers";
10
11
  import Post from "../components/post";
11
12
  import Lookup from "../data_structures/lookup";
13
+ import From from "../components/from";
12
14
 
13
15
  class Load extends Operation {
14
16
  private _value: any = null;
@@ -18,6 +20,29 @@ class Load extends Operation {
18
20
  public get type(): _JSON | CSV | Text {
19
21
  return this.children[0] as _JSON | CSV | Text;
20
22
  }
23
+
24
+ /**
25
+ * Gets the From component which contains either a URL expression or an AsyncFunction.
26
+ */
27
+ public get fromComponent(): From {
28
+ return this.children[1] as From;
29
+ }
30
+
31
+ /**
32
+ * Checks if the data source is an async function.
33
+ */
34
+ public get isAsyncFunction(): boolean {
35
+ return this.fromComponent.firstChild() instanceof AsyncFunction;
36
+ }
37
+
38
+ /**
39
+ * Gets the async function if the source is a function, otherwise null.
40
+ */
41
+ public get asyncFunction(): AsyncFunction | null {
42
+ const child = this.fromComponent.firstChild();
43
+ return child instanceof AsyncFunction ? child : null;
44
+ }
45
+
21
46
  public get from(): string {
22
47
  return this.children[1].value() as string;
23
48
  }
@@ -56,7 +81,22 @@ class Load extends Operation {
56
81
  ...(payload !== null ? {"body": JSON.stringify(payload.value())} : {})
57
82
  };
58
83
  }
59
- public async load(): Promise<any> {
84
+
85
+ /**
86
+ * Loads data from an async function source.
87
+ */
88
+ private async loadFromFunction(): Promise<void> {
89
+ const asyncFunc = this.asyncFunction!;
90
+ for await (const item of asyncFunc.execute()) {
91
+ this._value = item;
92
+ await this.next?.run();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Loads data from a URL source (original behavior).
98
+ */
99
+ private async loadFromUrl(): Promise<void> {
60
100
  const result = await fetch(this.from, this.options());
61
101
  let data: any = null;
62
102
  if(this.type instanceof _JSON) {
@@ -77,11 +117,20 @@ class Load extends Operation {
77
117
  await this.next?.run();
78
118
  }
79
119
  }
120
+
121
+ public async load(): Promise<any> {
122
+ if (this.isAsyncFunction) {
123
+ await this.loadFromFunction();
124
+ } else {
125
+ await this.loadFromUrl();
126
+ }
127
+ }
80
128
  public async run(): Promise<void> {
81
129
  try {
82
130
  await this.load();
83
131
  } catch(e) {
84
- throw new Error(`Failed to load data from ${this.from}. Error: ${e}`);
132
+ const source = this.isAsyncFunction ? this.asyncFunction?.name : this.from;
133
+ throw new Error(`Failed to load data from ${source}. Error: ${e}`);
85
134
  }
86
135
  }
87
136
  public value(): any {
@@ -4,6 +4,7 @@ import ASTNode from "./ast_node";
4
4
  import BaseParser from "./base_parser";
5
5
  import FunctionFactory from "./functions/function_factory";
6
6
  import Function from "./functions/function";
7
+ import AsyncFunction from "./functions/async_function";
7
8
  import AssociativeArray from "./data_structures/associative_array";
8
9
  import JSONArray from "./data_structures/json_array";
9
10
  import KeyValuePair from "./data_structures/key_value_pair";
@@ -218,11 +219,19 @@ class Parser extends BaseParser {
218
219
  this.expectAndSkipWhitespaceAndComments();
219
220
  const from = new From();
220
221
  load.addChild(from);
221
- const expression = this.parseExpression();
222
- if(expression === null) {
223
- throw new Error('Expected expression');
222
+
223
+ // Check if the source is an async function
224
+ const asyncFunc = this.parseAsyncFunction();
225
+ if (asyncFunc !== null) {
226
+ from.addChild(asyncFunc);
227
+ } else {
228
+ const expression = this.parseExpression();
229
+ if(expression === null) {
230
+ throw new Error('Expected expression or async function');
231
+ }
232
+ from.addChild(expression);
224
233
  }
225
- from.addChild(expression);
234
+
226
235
  this.expectAndSkipWhitespaceAndComments();
227
236
  if(this.token.isHeaders()) {
228
237
  const headers = new Headers();
@@ -569,6 +578,38 @@ class Parser extends BaseParser {
569
578
  return func;
570
579
  }
571
580
 
581
+ /**
582
+ * Parses an async function call for use in LOAD operations.
583
+ * Only matches if the identifier is registered as an async data provider.
584
+ *
585
+ * @returns An AsyncFunction node if a registered async function is found, otherwise null
586
+ */
587
+ private parseAsyncFunction(): AsyncFunction | null {
588
+ if(!this.token.isIdentifier()) {
589
+ return null;
590
+ }
591
+ if(this.token.value === null) {
592
+ return null;
593
+ }
594
+ // Only parse as async function if it's registered as an async provider
595
+ if(!FunctionFactory.isAsyncProvider(this.token.value)) {
596
+ return null;
597
+ }
598
+ if(!this.peek()?.isLeftParenthesis()) {
599
+ return null;
600
+ }
601
+ const asyncFunc = new AsyncFunction(this.token.value);
602
+ this.setNextToken(); // skip function name
603
+ this.setNextToken(); // skip left parenthesis
604
+ this.skipWhitespaceAndComments();
605
+ asyncFunc.parameters = Array.from(this.parseExpressions(AliasOption.NOT_ALLOWED));
606
+ this.skipWhitespaceAndComments();
607
+ if(!this.token.isRightParenthesis()) {
608
+ throw new Error('Expected right parenthesis');
609
+ }
610
+ this.setNextToken();
611
+ return asyncFunc;
612
+ }
572
613
  private parsePredicateFunction(): PredicateFunction | null {
573
614
  if(!this.ahead([Token.IDENTIFIER(""), Token.LEFT_PARENTHESIS, Token.IDENTIFIER(""), Token.IN])) {
574
615
  return null;