flowquery 1.0.16 → 1.0.17

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 (318) hide show
  1. package/.github/workflows/python-publish.yml +97 -0
  2. package/dist/compute/runner.d.ts +3 -2
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +7 -7
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +31 -0
  8. package/dist/graph/data.d.ts.map +1 -0
  9. package/dist/graph/data.js +110 -0
  10. package/dist/graph/data.js.map +1 -0
  11. package/dist/graph/database.d.ts +20 -0
  12. package/dist/graph/database.d.ts.map +1 -0
  13. package/dist/graph/database.js +77 -0
  14. package/dist/graph/database.js.map +1 -0
  15. package/dist/graph/hops.d.ts +11 -0
  16. package/dist/graph/hops.d.ts.map +1 -0
  17. package/dist/graph/hops.js +25 -0
  18. package/dist/graph/hops.js.map +1 -0
  19. package/dist/graph/node.d.ts +35 -0
  20. package/dist/graph/node.d.ts.map +1 -0
  21. package/dist/graph/node.js +113 -0
  22. package/dist/graph/node.js.map +1 -0
  23. package/dist/graph/node_data.d.ts +11 -0
  24. package/dist/graph/node_data.d.ts.map +1 -0
  25. package/dist/graph/node_data.js +20 -0
  26. package/dist/graph/node_data.js.map +1 -0
  27. package/dist/graph/node_reference.d.ts +10 -0
  28. package/dist/graph/node_reference.d.ts.map +1 -0
  29. package/dist/graph/node_reference.js +52 -0
  30. package/dist/graph/node_reference.js.map +1 -0
  31. package/dist/graph/pattern.d.ts +18 -0
  32. package/dist/graph/pattern.d.ts.map +1 -0
  33. package/dist/graph/pattern.js +114 -0
  34. package/dist/graph/pattern.js.map +1 -0
  35. package/dist/graph/pattern_expression.d.ts +14 -0
  36. package/dist/graph/pattern_expression.d.ts.map +1 -0
  37. package/dist/graph/pattern_expression.js +58 -0
  38. package/dist/graph/pattern_expression.js.map +1 -0
  39. package/dist/graph/patterns.d.ts +11 -0
  40. package/dist/graph/patterns.d.ts.map +1 -0
  41. package/dist/graph/patterns.js +49 -0
  42. package/dist/graph/patterns.js.map +1 -0
  43. package/dist/graph/physical_node.d.ts +10 -0
  44. package/dist/graph/physical_node.d.ts.map +1 -0
  45. package/dist/graph/physical_node.js +40 -0
  46. package/dist/graph/physical_node.js.map +1 -0
  47. package/dist/graph/physical_relationship.d.ts +10 -0
  48. package/dist/graph/physical_relationship.d.ts.map +1 -0
  49. package/dist/graph/physical_relationship.js +40 -0
  50. package/dist/graph/physical_relationship.js.map +1 -0
  51. package/dist/graph/relationship.d.ts +40 -0
  52. package/dist/graph/relationship.d.ts.map +1 -0
  53. package/dist/graph/relationship.js +124 -0
  54. package/dist/graph/relationship.js.map +1 -0
  55. package/dist/graph/relationship_data.d.ts +12 -0
  56. package/dist/graph/relationship_data.d.ts.map +1 -0
  57. package/dist/graph/relationship_data.js +40 -0
  58. package/dist/graph/relationship_data.js.map +1 -0
  59. package/dist/graph/relationship_match_collector.d.ts +19 -0
  60. package/dist/graph/relationship_match_collector.d.ts.map +1 -0
  61. package/dist/graph/relationship_match_collector.js +55 -0
  62. package/dist/graph/relationship_match_collector.js.map +1 -0
  63. package/dist/graph/relationship_reference.d.ts +8 -0
  64. package/dist/graph/relationship_reference.d.ts.map +1 -0
  65. package/dist/graph/relationship_reference.js +37 -0
  66. package/dist/graph/relationship_reference.js.map +1 -0
  67. package/dist/parsing/base_parser.d.ts +1 -0
  68. package/dist/parsing/base_parser.d.ts.map +1 -1
  69. package/dist/parsing/base_parser.js +4 -1
  70. package/dist/parsing/base_parser.js.map +1 -1
  71. package/dist/parsing/context.d.ts +2 -2
  72. package/dist/parsing/context.js +5 -5
  73. package/dist/parsing/expressions/boolean.d.ts +8 -0
  74. package/dist/parsing/expressions/boolean.d.ts.map +1 -0
  75. package/dist/parsing/expressions/boolean.js +26 -0
  76. package/dist/parsing/expressions/boolean.js.map +1 -0
  77. package/dist/parsing/expressions/expression.d.ts +4 -1
  78. package/dist/parsing/expressions/expression.d.ts.map +1 -1
  79. package/dist/parsing/expressions/expression.js +15 -8
  80. package/dist/parsing/expressions/expression.js.map +1 -1
  81. package/dist/parsing/expressions/operator.d.ts +1 -1
  82. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  83. package/dist/parsing/expressions/operator.js.map +1 -1
  84. package/dist/parsing/functions/function_factory.d.ts +13 -13
  85. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  86. package/dist/parsing/functions/function_factory.js +20 -18
  87. package/dist/parsing/functions/function_factory.js.map +1 -1
  88. package/dist/parsing/operations/create_node.d.ts +14 -0
  89. package/dist/parsing/operations/create_node.d.ts.map +1 -0
  90. package/dist/parsing/operations/create_node.js +51 -0
  91. package/dist/parsing/operations/create_node.js.map +1 -0
  92. package/dist/parsing/operations/create_relationship.d.ts +14 -0
  93. package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
  94. package/dist/parsing/operations/create_relationship.js +51 -0
  95. package/dist/parsing/operations/create_relationship.js.map +1 -0
  96. package/dist/parsing/operations/match.d.ts +15 -0
  97. package/dist/parsing/operations/match.d.ts.map +1 -0
  98. package/dist/parsing/operations/match.js +45 -0
  99. package/dist/parsing/operations/match.js.map +1 -0
  100. package/dist/parsing/operations/operation.d.ts +1 -0
  101. package/dist/parsing/operations/operation.d.ts.map +1 -1
  102. package/dist/parsing/operations/operation.js +6 -0
  103. package/dist/parsing/operations/operation.js.map +1 -1
  104. package/dist/parsing/operations/return.d.ts +1 -0
  105. package/dist/parsing/operations/return.d.ts.map +1 -1
  106. package/dist/parsing/operations/return.js +7 -1
  107. package/dist/parsing/operations/return.js.map +1 -1
  108. package/dist/parsing/operations/where.d.ts +1 -1
  109. package/dist/parsing/operations/where.d.ts.map +1 -1
  110. package/dist/parsing/operations/where.js +4 -0
  111. package/dist/parsing/operations/where.js.map +1 -1
  112. package/dist/parsing/parser.d.ts +10 -0
  113. package/dist/parsing/parser.d.ts.map +1 -1
  114. package/dist/parsing/parser.js +344 -5
  115. package/dist/parsing/parser.js.map +1 -1
  116. package/dist/parsing/token_to_node.d.ts.map +1 -1
  117. package/dist/parsing/token_to_node.js +7 -0
  118. package/dist/parsing/token_to_node.js.map +1 -1
  119. package/dist/tokenization/keyword.d.ts +1 -0
  120. package/dist/tokenization/keyword.d.ts.map +1 -1
  121. package/dist/tokenization/keyword.js +1 -0
  122. package/dist/tokenization/keyword.js.map +1 -1
  123. package/dist/tokenization/token.d.ts +4 -0
  124. package/dist/tokenization/token.d.ts.map +1 -1
  125. package/dist/tokenization/token.js +14 -1
  126. package/dist/tokenization/token.js.map +1 -1
  127. package/dist/tokenization/token_type.d.ts +1 -0
  128. package/dist/tokenization/token_type.d.ts.map +1 -1
  129. package/dist/tokenization/token_type.js +1 -0
  130. package/dist/tokenization/token_type.js.map +1 -1
  131. package/dist/tokenization/tokenizer.d.ts +2 -1
  132. package/dist/tokenization/tokenizer.d.ts.map +1 -1
  133. package/dist/tokenization/tokenizer.js +25 -12
  134. package/dist/tokenization/tokenizer.js.map +1 -1
  135. package/docs/flowquery.min.js +1 -1
  136. package/flowquery-py/README.md +166 -0
  137. package/flowquery-py/pyproject.toml +75 -0
  138. package/flowquery-py/setup_env.ps1 +92 -0
  139. package/flowquery-py/setup_env.sh +87 -0
  140. package/flowquery-py/src/__init__.py +34 -0
  141. package/flowquery-py/src/__main__.py +10 -0
  142. package/flowquery-py/src/compute/__init__.py +5 -0
  143. package/flowquery-py/src/compute/runner.py +60 -0
  144. package/flowquery-py/src/extensibility.py +52 -0
  145. package/flowquery-py/src/graph/__init__.py +31 -0
  146. package/flowquery-py/src/graph/data.py +118 -0
  147. package/flowquery-py/src/graph/database.py +82 -0
  148. package/flowquery-py/src/graph/hops.py +43 -0
  149. package/flowquery-py/src/graph/node.py +112 -0
  150. package/flowquery-py/src/graph/node_data.py +26 -0
  151. package/flowquery-py/src/graph/node_reference.py +49 -0
  152. package/flowquery-py/src/graph/pattern.py +125 -0
  153. package/flowquery-py/src/graph/pattern_expression.py +62 -0
  154. package/flowquery-py/src/graph/patterns.py +42 -0
  155. package/flowquery-py/src/graph/physical_node.py +40 -0
  156. package/flowquery-py/src/graph/physical_relationship.py +36 -0
  157. package/flowquery-py/src/graph/relationship.py +135 -0
  158. package/flowquery-py/src/graph/relationship_data.py +33 -0
  159. package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
  160. package/flowquery-py/src/graph/relationship_reference.py +21 -0
  161. package/flowquery-py/src/io/__init__.py +5 -0
  162. package/flowquery-py/src/io/command_line.py +67 -0
  163. package/flowquery-py/src/parsing/__init__.py +17 -0
  164. package/flowquery-py/src/parsing/alias.py +20 -0
  165. package/flowquery-py/src/parsing/alias_option.py +11 -0
  166. package/flowquery-py/src/parsing/ast_node.py +146 -0
  167. package/flowquery-py/src/parsing/base_parser.py +84 -0
  168. package/flowquery-py/src/parsing/components/__init__.py +19 -0
  169. package/flowquery-py/src/parsing/components/csv.py +8 -0
  170. package/flowquery-py/src/parsing/components/from_.py +10 -0
  171. package/flowquery-py/src/parsing/components/headers.py +12 -0
  172. package/flowquery-py/src/parsing/components/json.py +8 -0
  173. package/flowquery-py/src/parsing/components/null.py +10 -0
  174. package/flowquery-py/src/parsing/components/post.py +8 -0
  175. package/flowquery-py/src/parsing/components/text.py +8 -0
  176. package/flowquery-py/src/parsing/context.py +50 -0
  177. package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
  178. package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
  179. package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
  180. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
  181. package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
  182. package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
  183. package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
  184. package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
  185. package/flowquery-py/src/parsing/expressions/expression.py +138 -0
  186. package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
  187. package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
  188. package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
  189. package/flowquery-py/src/parsing/expressions/number.py +32 -0
  190. package/flowquery-py/src/parsing/expressions/operator.py +169 -0
  191. package/flowquery-py/src/parsing/expressions/reference.py +47 -0
  192. package/flowquery-py/src/parsing/expressions/string.py +27 -0
  193. package/flowquery-py/src/parsing/functions/__init__.py +75 -0
  194. package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
  195. package/flowquery-py/src/parsing/functions/async_function.py +62 -0
  196. package/flowquery-py/src/parsing/functions/avg.py +55 -0
  197. package/flowquery-py/src/parsing/functions/collect.py +75 -0
  198. package/flowquery-py/src/parsing/functions/function.py +68 -0
  199. package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
  200. package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
  201. package/flowquery-py/src/parsing/functions/functions.py +59 -0
  202. package/flowquery-py/src/parsing/functions/join.py +47 -0
  203. package/flowquery-py/src/parsing/functions/keys.py +34 -0
  204. package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
  205. package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
  206. package/flowquery-py/src/parsing/functions/rand.py +28 -0
  207. package/flowquery-py/src/parsing/functions/range_.py +34 -0
  208. package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
  209. package/flowquery-py/src/parsing/functions/replace.py +37 -0
  210. package/flowquery-py/src/parsing/functions/round_.py +32 -0
  211. package/flowquery-py/src/parsing/functions/size.py +32 -0
  212. package/flowquery-py/src/parsing/functions/split.py +47 -0
  213. package/flowquery-py/src/parsing/functions/stringify.py +47 -0
  214. package/flowquery-py/src/parsing/functions/sum.py +51 -0
  215. package/flowquery-py/src/parsing/functions/to_json.py +33 -0
  216. package/flowquery-py/src/parsing/functions/type_.py +47 -0
  217. package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
  218. package/flowquery-py/src/parsing/logic/__init__.py +15 -0
  219. package/flowquery-py/src/parsing/logic/case.py +29 -0
  220. package/flowquery-py/src/parsing/logic/else_.py +12 -0
  221. package/flowquery-py/src/parsing/logic/end.py +8 -0
  222. package/flowquery-py/src/parsing/logic/then.py +12 -0
  223. package/flowquery-py/src/parsing/logic/when.py +10 -0
  224. package/flowquery-py/src/parsing/operations/__init__.py +35 -0
  225. package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
  226. package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
  227. package/flowquery-py/src/parsing/operations/call.py +74 -0
  228. package/flowquery-py/src/parsing/operations/create_node.py +34 -0
  229. package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
  230. package/flowquery-py/src/parsing/operations/group_by.py +130 -0
  231. package/flowquery-py/src/parsing/operations/limit.py +22 -0
  232. package/flowquery-py/src/parsing/operations/load.py +140 -0
  233. package/flowquery-py/src/parsing/operations/match.py +29 -0
  234. package/flowquery-py/src/parsing/operations/operation.py +69 -0
  235. package/flowquery-py/src/parsing/operations/projection.py +21 -0
  236. package/flowquery-py/src/parsing/operations/return_op.py +50 -0
  237. package/flowquery-py/src/parsing/operations/unwind.py +37 -0
  238. package/flowquery-py/src/parsing/operations/where.py +41 -0
  239. package/flowquery-py/src/parsing/operations/with_op.py +18 -0
  240. package/flowquery-py/src/parsing/parser.py +1011 -0
  241. package/flowquery-py/src/parsing/token_to_node.py +109 -0
  242. package/flowquery-py/src/tokenization/__init__.py +23 -0
  243. package/flowquery-py/src/tokenization/keyword.py +48 -0
  244. package/flowquery-py/src/tokenization/operator.py +29 -0
  245. package/flowquery-py/src/tokenization/string_walker.py +158 -0
  246. package/flowquery-py/src/tokenization/symbol.py +19 -0
  247. package/flowquery-py/src/tokenization/token.py +659 -0
  248. package/flowquery-py/src/tokenization/token_mapper.py +52 -0
  249. package/flowquery-py/src/tokenization/token_type.py +21 -0
  250. package/flowquery-py/src/tokenization/tokenizer.py +214 -0
  251. package/flowquery-py/src/tokenization/trie.py +124 -0
  252. package/flowquery-py/src/utils/__init__.py +6 -0
  253. package/flowquery-py/src/utils/object_utils.py +20 -0
  254. package/flowquery-py/src/utils/string_utils.py +113 -0
  255. package/flowquery-py/tests/__init__.py +1 -0
  256. package/flowquery-py/tests/compute/__init__.py +1 -0
  257. package/flowquery-py/tests/compute/test_runner.py +1335 -0
  258. package/flowquery-py/tests/graph/__init__.py +1 -0
  259. package/flowquery-py/tests/graph/test_create.py +56 -0
  260. package/flowquery-py/tests/graph/test_data.py +73 -0
  261. package/flowquery-py/tests/graph/test_match.py +40 -0
  262. package/flowquery-py/tests/parsing/__init__.py +1 -0
  263. package/flowquery-py/tests/parsing/test_context.py +34 -0
  264. package/flowquery-py/tests/parsing/test_expression.py +49 -0
  265. package/flowquery-py/tests/parsing/test_parser.py +674 -0
  266. package/flowquery-py/tests/test_extensibility.py +611 -0
  267. package/flowquery-py/tests/tokenization/__init__.py +1 -0
  268. package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
  269. package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
  270. package/flowquery-py/tests/tokenization/test_trie.py +30 -0
  271. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  272. package/misc/apps/RAG/package.json +1 -1
  273. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
  274. package/misc/apps/RAG/src/components/index.ts +19 -10
  275. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +70 -140
  276. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
  277. package/package.json +1 -1
  278. package/src/compute/runner.ts +24 -19
  279. package/src/graph/data.ts +112 -0
  280. package/src/graph/database.ts +63 -0
  281. package/src/graph/hops.ts +22 -0
  282. package/src/graph/node.ts +99 -0
  283. package/src/graph/node_data.ts +18 -0
  284. package/src/graph/node_reference.ts +33 -0
  285. package/src/graph/pattern.ts +101 -0
  286. package/src/graph/pattern_expression.ts +37 -0
  287. package/src/graph/patterns.ts +36 -0
  288. package/src/graph/physical_node.ts +23 -0
  289. package/src/graph/physical_relationship.ts +23 -0
  290. package/src/graph/relationship.ts +116 -0
  291. package/src/graph/relationship_data.ts +27 -0
  292. package/src/graph/relationship_match_collector.ts +58 -0
  293. package/src/graph/relationship_reference.ts +24 -0
  294. package/src/parsing/base_parser.ts +20 -14
  295. package/src/parsing/context.ts +14 -14
  296. package/src/parsing/expressions/boolean.ts +21 -0
  297. package/src/parsing/expressions/expression.ts +34 -26
  298. package/src/parsing/expressions/operator.ts +19 -1
  299. package/src/parsing/functions/function_factory.ts +45 -45
  300. package/src/parsing/operations/create_node.ts +39 -0
  301. package/src/parsing/operations/create_relationship.ts +38 -0
  302. package/src/parsing/operations/match.ts +31 -0
  303. package/src/parsing/operations/operation.ts +3 -0
  304. package/src/parsing/operations/return.ts +11 -7
  305. package/src/parsing/operations/where.ts +10 -6
  306. package/src/parsing/parser.ts +346 -8
  307. package/src/parsing/token_to_node.ts +6 -0
  308. package/src/tokenization/keyword.ts +41 -40
  309. package/src/tokenization/token.ts +21 -1
  310. package/src/tokenization/token_type.ts +2 -1
  311. package/src/tokenization/tokenizer.ts +52 -31
  312. package/tests/compute/runner.test.ts +654 -0
  313. package/tests/extensibility.test.ts +97 -93
  314. package/tests/graph/create.test.ts +36 -0
  315. package/tests/graph/data.test.ts +58 -0
  316. package/tests/graph/match.test.ts +29 -0
  317. package/tests/parsing/parser.test.ts +273 -2
  318. package/tests/tokenization/tokenizer.test.ts +90 -0
@@ -0,0 +1,68 @@
1
+ """Base class for all functions in FlowQuery."""
2
+
3
+ from typing import List, Optional, Any
4
+
5
+ from ..ast_node import ASTNode
6
+
7
+
8
+ class Function(ASTNode):
9
+ """Base class for all functions in FlowQuery.
10
+
11
+ Functions can have parameters and may support the DISTINCT modifier.
12
+ Subclasses implement specific function logic.
13
+
14
+ Example:
15
+ func = FunctionFactory.create("sum")
16
+ func.parameters = [expression1, expression2]
17
+ """
18
+
19
+ def __init__(self, name: Optional[str] = None):
20
+ """Creates a new Function with the given name.
21
+
22
+ Args:
23
+ name: The function name
24
+ """
25
+ super().__init__()
26
+ self._name = name or self.__class__.__name__
27
+ self._expected_parameter_count: Optional[int] = None
28
+ self._supports_distinct: bool = False
29
+
30
+ @property
31
+ def parameters(self) -> List[ASTNode]:
32
+ """Gets the function parameters."""
33
+ return self.children
34
+
35
+ @parameters.setter
36
+ def parameters(self, nodes: List[ASTNode]) -> None:
37
+ """Sets the function parameters.
38
+
39
+ Args:
40
+ nodes: Array of AST nodes representing the function arguments
41
+
42
+ Raises:
43
+ ValueError: If the number of parameters doesn't match expected count
44
+ """
45
+ if self._expected_parameter_count is not None and self._expected_parameter_count != len(nodes):
46
+ raise ValueError(
47
+ f"Function {self._name} expected {self._expected_parameter_count} parameters, "
48
+ f"but got {len(nodes)}"
49
+ )
50
+ self.children = nodes
51
+
52
+ @property
53
+ def name(self) -> str:
54
+ return self._name
55
+
56
+ def __str__(self) -> str:
57
+ return f"Function ({self._name})"
58
+
59
+ @property
60
+ def distinct(self) -> bool:
61
+ return self._supports_distinct
62
+
63
+ @distinct.setter
64
+ def distinct(self, value: bool) -> None:
65
+ if self._supports_distinct:
66
+ self._supports_distinct = value
67
+ else:
68
+ raise ValueError(f"Function {self._name} does not support distinct")
@@ -0,0 +1,173 @@
1
+ """Factory for creating function instances by name."""
2
+
3
+ from typing import Any, Callable, Dict, List, Optional
4
+
5
+ from .function import Function
6
+ from .async_function import AsyncFunction
7
+ from .predicate_function import PredicateFunction
8
+ from .function_metadata import (
9
+ FunctionMetadata,
10
+ get_function_metadata,
11
+ get_registered_function_factory,
12
+ get_registered_function_metadata,
13
+ )
14
+
15
+
16
+ class FunctionFactory:
17
+ """Factory for creating function instances by name.
18
+
19
+ All functions are registered via the @FunctionDef decorator.
20
+ Maps function names (case-insensitive) to their corresponding implementation classes.
21
+ Supports built-in functions like sum, avg, collect, range, split, join, etc.
22
+
23
+ Example:
24
+ sum_func = FunctionFactory.create("sum")
25
+ avg_func = FunctionFactory.create("AVG")
26
+ """
27
+
28
+ @staticmethod
29
+ def get_async_provider(name: str) -> Optional[Callable]:
30
+ """Gets an async data provider by name.
31
+
32
+ Args:
33
+ name: The function name (case-insensitive)
34
+
35
+ Returns:
36
+ The async data provider, or None if not found
37
+ """
38
+ return get_registered_function_factory(name.lower())
39
+
40
+ @staticmethod
41
+ def is_async_provider(name: str) -> bool:
42
+ """Checks if a function name is registered as an async data provider.
43
+
44
+ Args:
45
+ name: The function name (case-insensitive)
46
+
47
+ Returns:
48
+ True if the function is an async data provider
49
+ """
50
+ return get_registered_function_factory(name.lower(), "async") is not None
51
+
52
+ @staticmethod
53
+ def get_metadata(name: str) -> Optional[FunctionMetadata]:
54
+ """Gets metadata for a specific function.
55
+
56
+ Args:
57
+ name: The function name (case-insensitive)
58
+
59
+ Returns:
60
+ The function metadata, or None if not found
61
+ """
62
+ return get_function_metadata(name.lower())
63
+
64
+ @staticmethod
65
+ def list_functions(
66
+ category: Optional[str] = None,
67
+ async_only: bool = False,
68
+ sync_only: bool = False
69
+ ) -> List[FunctionMetadata]:
70
+ """Lists all registered functions with their metadata.
71
+
72
+ Args:
73
+ category: Optional category filter
74
+ async_only: If True, only return async functions
75
+ sync_only: If True, only return sync functions
76
+
77
+ Returns:
78
+ Array of function metadata
79
+ """
80
+ result: List[FunctionMetadata] = []
81
+
82
+ for meta in get_registered_function_metadata():
83
+ if category and meta.category != category:
84
+ continue
85
+ if async_only and meta.category != "async":
86
+ continue
87
+ if sync_only and meta.category == "async":
88
+ continue
89
+ result.append(meta)
90
+
91
+ return result
92
+
93
+ @staticmethod
94
+ def list_function_names() -> List[str]:
95
+ """Lists all registered function names.
96
+
97
+ Returns:
98
+ Array of function names
99
+ """
100
+ return [m.name for m in get_registered_function_metadata()]
101
+
102
+ @staticmethod
103
+ def to_json() -> Dict[str, Any]:
104
+ """Gets all function metadata as a JSON-serializable object for LLM consumption.
105
+
106
+ Returns:
107
+ Object with functions grouped by category
108
+ """
109
+ functions = FunctionFactory.list_functions()
110
+ categories = list(set(f.category for f in functions if f.category))
111
+ return {"functions": functions, "categories": categories}
112
+
113
+ @staticmethod
114
+ def create(name: str) -> Function:
115
+ """Creates a function instance by name.
116
+
117
+ Args:
118
+ name: The function name (case-insensitive)
119
+
120
+ Returns:
121
+ A Function instance of the appropriate type
122
+
123
+ Raises:
124
+ ValueError: If the function name is not registered
125
+ """
126
+ lower_name = name.lower()
127
+
128
+ # Check decorator-registered functions
129
+ decorator_factory = get_registered_function_factory(lower_name)
130
+ if decorator_factory:
131
+ return decorator_factory()
132
+
133
+ raise ValueError(f"Unknown function: {name}")
134
+
135
+ @staticmethod
136
+ def create_predicate(name: str) -> PredicateFunction:
137
+ """Creates a predicate function instance by name.
138
+
139
+ Args:
140
+ name: The function name (case-insensitive)
141
+
142
+ Returns:
143
+ A PredicateFunction instance of the appropriate type
144
+
145
+ Raises:
146
+ ValueError: If the predicate function name is not registered
147
+ """
148
+ lower_name = name.lower()
149
+
150
+ decorator_factory = get_registered_function_factory(lower_name, "predicate")
151
+ if decorator_factory:
152
+ return decorator_factory()
153
+
154
+ raise ValueError(f"Unknown predicate function: {name}")
155
+
156
+ @staticmethod
157
+ def create_async(name: str) -> AsyncFunction:
158
+ """Creates an async function instance by name.
159
+
160
+ Args:
161
+ name: The function name (case-insensitive)
162
+
163
+ Returns:
164
+ An AsyncFunction instance of the appropriate type
165
+
166
+ Raises:
167
+ ValueError: If the async function name is not registered
168
+ """
169
+ lower_name = name.lower()
170
+ decorator_factory = get_registered_function_factory(lower_name, "async")
171
+ if decorator_factory:
172
+ return decorator_factory()
173
+ raise ValueError(f"Unknown async function: {name}")
@@ -0,0 +1,149 @@
1
+ """Function metadata and decorator for FlowQuery functions."""
2
+
3
+ from typing import Any, Callable, Dict, List, Optional, TypedDict, Union
4
+ from dataclasses import dataclass
5
+
6
+
7
+ # Type definitions
8
+ FunctionCategory = str # "scalar" | "aggregate" | "predicate" | "async" | string
9
+
10
+
11
+ class ParameterSchema(TypedDict, total=False):
12
+ """Schema definition for function arguments."""
13
+ name: str
14
+ description: str
15
+ type: str # "string" | "number" | "boolean" | "object" | "array" | "null"
16
+ required: bool
17
+ default: Any
18
+ items: Dict[str, Any]
19
+ properties: Dict[str, Any]
20
+ enum: List[Any]
21
+ example: Any
22
+
23
+
24
+ class OutputSchema(TypedDict, total=False):
25
+ """Schema definition for function output."""
26
+ description: str
27
+ type: str
28
+ items: Dict[str, Any]
29
+ properties: Dict[str, Any]
30
+ example: Any
31
+
32
+
33
+ @dataclass
34
+ class FunctionMetadata:
35
+ """Metadata for a registered function, designed for LLM consumption."""
36
+ name: str
37
+ description: str
38
+ category: FunctionCategory
39
+ parameters: List[ParameterSchema]
40
+ output: OutputSchema
41
+ examples: Optional[List[str]] = None
42
+ notes: Optional[str] = None
43
+
44
+
45
+ class FunctionDefOptions(TypedDict, total=False):
46
+ """Decorator options - metadata without the name (derived from class)."""
47
+ description: str
48
+ category: FunctionCategory
49
+ parameters: List[ParameterSchema]
50
+ output: OutputSchema
51
+ examples: List[str]
52
+ notes: str
53
+
54
+
55
+ class FunctionRegistry:
56
+ """Centralized registry for function metadata, factories, and async providers."""
57
+
58
+ _metadata: Dict[str, FunctionMetadata] = {}
59
+ _factories: Dict[str, Callable[[], Any]] = {}
60
+
61
+ @classmethod
62
+ def register(cls, constructor: type, options: FunctionDefOptions) -> None:
63
+ """Registers a regular function class."""
64
+ instance = constructor()
65
+ display_name = getattr(instance, 'name', constructor.__name__).lower()
66
+ category = options.get('category', '')
67
+ registry_key = f"{display_name}:{category}" if category else display_name
68
+
69
+ metadata = FunctionMetadata(
70
+ name=display_name,
71
+ description=options.get('description', ''),
72
+ category=options.get('category', 'scalar'),
73
+ parameters=options.get('parameters', []),
74
+ output=options.get('output', {'description': '', 'type': 'any'}),
75
+ examples=options.get('examples'),
76
+ notes=options.get('notes'),
77
+ )
78
+ cls._metadata[registry_key] = metadata
79
+
80
+ if category != 'predicate':
81
+ cls._factories[display_name] = lambda c=constructor: c()
82
+ cls._factories[registry_key] = lambda c=constructor: c()
83
+
84
+ @classmethod
85
+ def get_all_metadata(cls) -> List[FunctionMetadata]:
86
+ return list(cls._metadata.values())
87
+
88
+ @classmethod
89
+ def get_metadata(cls, name: str, category: Optional[str] = None) -> Optional[FunctionMetadata]:
90
+ lower_name = name.lower()
91
+ if category:
92
+ return cls._metadata.get(f"{lower_name}:{category}")
93
+ for meta in cls._metadata.values():
94
+ if meta.name.lower() == lower_name:
95
+ return meta
96
+ return None
97
+
98
+ @classmethod
99
+ def get_factory(cls, name: str, category: Optional[str] = None) -> Optional[Callable[[], Any]]:
100
+ lower_name = name.lower()
101
+ if category:
102
+ return cls._factories.get(f"{lower_name}:{category}")
103
+ return cls._factories.get(lower_name)
104
+
105
+
106
+ def FunctionDef(options: FunctionDefOptions):
107
+ """Class decorator that registers function metadata.
108
+
109
+ The function name is derived from the class's constructor.
110
+
111
+ Args:
112
+ options: Function metadata (excluding name)
113
+
114
+ Returns:
115
+ Class decorator
116
+
117
+ Example:
118
+ @FunctionDef({
119
+ 'description': "Adds two numbers",
120
+ 'category': "scalar",
121
+ 'parameters': [
122
+ {'name': "a", 'description': "First number", 'type': "number"},
123
+ {'name': "b", 'description': "Second number", 'type': "number"}
124
+ ],
125
+ 'output': {'description': "Sum of a and b", 'type': "number"},
126
+ })
127
+ class AddFunction(Function):
128
+ def __init__(self):
129
+ super().__init__("add")
130
+ """
131
+ def decorator(cls: type) -> type:
132
+ FunctionRegistry.register(cls, options)
133
+ return cls
134
+ return decorator
135
+
136
+
137
+ def get_registered_function_metadata() -> List[FunctionMetadata]:
138
+ """Gets all registered function metadata from decorators."""
139
+ return FunctionRegistry.get_all_metadata()
140
+
141
+
142
+ def get_registered_function_factory(name: str, category: Optional[str] = None) -> Optional[Callable[[], Any]]:
143
+ """Gets a registered function factory by name."""
144
+ return FunctionRegistry.get_factory(name, category)
145
+
146
+
147
+ def get_function_metadata(name: str, category: Optional[str] = None) -> Optional[FunctionMetadata]:
148
+ """Gets metadata for a specific function by name."""
149
+ return FunctionRegistry.get_metadata(name, category)
@@ -0,0 +1,59 @@
1
+ """Functions introspection function."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from .function import Function
6
+ from .function_factory import FunctionFactory
7
+ from .function_metadata import FunctionDef
8
+
9
+
10
+ @FunctionDef({
11
+ "description": "Lists all registered functions with their metadata. Useful for discovering available functions and their documentation.",
12
+ "category": "scalar",
13
+ "parameters": [
14
+ {"name": "category", "description": "Optional category to filter by (e.g., 'aggregation', 'string', 'math')", "type": "string", "required": False}
15
+ ],
16
+ "output": {
17
+ "description": "Array of function metadata objects",
18
+ "type": "array",
19
+ "items": {
20
+ "type": "object",
21
+ "properties": {
22
+ "name": {"description": "Function name", "type": "string"},
23
+ "description": {"description": "What the function does", "type": "string"},
24
+ "category": {"description": "Function category", "type": "string"},
25
+ "parameters": {"description": "Array of parameter definitions", "type": "array"},
26
+ "output": {"description": "Output schema", "type": "object"},
27
+ "examples": {"description": "Usage examples", "type": "array"}
28
+ }
29
+ }
30
+ },
31
+ "examples": [
32
+ "WITH functions() AS funcs RETURN funcs",
33
+ "WITH functions('aggregation') AS funcs UNWIND funcs AS f RETURN f.name, f.description"
34
+ ]
35
+ })
36
+ class Functions(Function):
37
+ """Functions introspection function.
38
+
39
+ Lists all registered functions with their metadata.
40
+ """
41
+
42
+ def __init__(self):
43
+ super().__init__("functions")
44
+ self._expected_parameter_count = None # 0 or 1 parameter
45
+
46
+ def value(self) -> Any:
47
+ children = self.get_children()
48
+
49
+ if len(children) == 0:
50
+ # Return all functions
51
+ return FunctionFactory.list_functions()
52
+ elif len(children) == 1:
53
+ # Filter by category
54
+ category = children[0].value()
55
+ if isinstance(category, str):
56
+ return FunctionFactory.list_functions(category=category)
57
+ raise ValueError("functions() category parameter must be a string")
58
+ else:
59
+ raise ValueError("functions() takes 0 or 1 parameters")
@@ -0,0 +1,47 @@
1
+ """Join function."""
2
+
3
+ from typing import Any, List
4
+
5
+ from .function import Function
6
+ from ..ast_node import ASTNode
7
+ from ..expressions.string import String
8
+ from .function_metadata import FunctionDef
9
+
10
+
11
+ @FunctionDef({
12
+ "description": "Joins an array of strings with a delimiter",
13
+ "category": "scalar",
14
+ "parameters": [
15
+ {"name": "array", "description": "Array of values to join", "type": "array"},
16
+ {"name": "delimiter", "description": "Delimiter to join with", "type": "string"}
17
+ ],
18
+ "output": {"description": "Joined string", "type": "string", "example": "a,b,c"},
19
+ "examples": ["WITH ['a', 'b', 'c'] AS arr RETURN join(arr, ',')"]
20
+ })
21
+ class Join(Function):
22
+ """Join function.
23
+
24
+ Joins an array of strings with a delimiter.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__("join")
29
+ self._expected_parameter_count = 2
30
+
31
+ @property
32
+ def parameters(self) -> List[ASTNode]:
33
+ return self.get_children()
34
+
35
+ @parameters.setter
36
+ def parameters(self, nodes: List[ASTNode]) -> None:
37
+ if len(nodes) == 1:
38
+ nodes.append(String(""))
39
+ for node in nodes:
40
+ self.add_child(node)
41
+
42
+ def value(self) -> Any:
43
+ array = self.get_children()[0].value()
44
+ delimiter = self.get_children()[1].value()
45
+ if not isinstance(array, list) or not isinstance(delimiter, str):
46
+ raise ValueError("Invalid arguments for join function")
47
+ return delimiter.join(str(item) for item in array)
@@ -0,0 +1,34 @@
1
+ """Keys function."""
2
+
3
+ from typing import Any, List
4
+
5
+ from .function import Function
6
+ from .function_metadata import FunctionDef
7
+
8
+
9
+ @FunctionDef({
10
+ "description": "Returns the keys of an object (associative array) as an array",
11
+ "category": "scalar",
12
+ "parameters": [
13
+ {"name": "object", "description": "Object to extract keys from", "type": "object"}
14
+ ],
15
+ "output": {"description": "Array of keys", "type": "array", "example": ["name", "age"]},
16
+ "examples": ["WITH { name: 'Alice', age: 30 } AS obj RETURN keys(obj)"]
17
+ })
18
+ class Keys(Function):
19
+ """Keys function.
20
+
21
+ Returns the keys of an object (associative array) as an array.
22
+ """
23
+
24
+ def __init__(self):
25
+ super().__init__("keys")
26
+ self._expected_parameter_count = 1
27
+
28
+ def value(self) -> Any:
29
+ obj = self.get_children()[0].value()
30
+ if obj is None:
31
+ return []
32
+ if not isinstance(obj, dict):
33
+ raise ValueError("keys() expects an object, not an array or primitive")
34
+ return list(obj.keys())
@@ -0,0 +1,46 @@
1
+ """Base class for predicate functions in FlowQuery."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from ..ast_node import ASTNode
6
+ from ..expressions.expression import Expression
7
+ from ..expressions.reference import Reference
8
+ from .value_holder import ValueHolder
9
+
10
+
11
+ class PredicateFunction(ASTNode):
12
+ """Base class for predicate functions."""
13
+
14
+ def __init__(self, name: Optional[str] = None):
15
+ super().__init__()
16
+ self._name = name or self.__class__.__name__
17
+ self._value_holder = ValueHolder()
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return self._name
22
+
23
+ @property
24
+ def reference(self) -> Reference:
25
+ return self.first_child()
26
+
27
+ @property
28
+ def array(self) -> ASTNode:
29
+ return self.get_children()[1].first_child()
30
+
31
+ @property
32
+ def _return(self) -> Expression:
33
+ return self.get_children()[2]
34
+
35
+ @property
36
+ def where(self) -> Optional['Where']:
37
+ from ..operations.where import Where
38
+ if len(self.get_children()) == 4:
39
+ return self.get_children()[3]
40
+ return None
41
+
42
+ def value(self) -> Any:
43
+ raise NotImplementedError("Method not implemented.")
44
+
45
+ def __str__(self) -> str:
46
+ return f"PredicateFunction ({self._name})"
@@ -0,0 +1,47 @@
1
+ """PredicateSum function."""
2
+
3
+ from typing import Any, List, Optional
4
+
5
+ from .predicate_function import PredicateFunction
6
+ from .function_metadata import FunctionDef
7
+
8
+
9
+ @FunctionDef({
10
+ "description": "Calculates the sum of values in an array with optional filtering. Uses list comprehension syntax: sum(variable IN array [WHERE condition] | expression)",
11
+ "category": "predicate",
12
+ "parameters": [
13
+ {"name": "variable", "description": "Variable name to bind each element", "type": "string"},
14
+ {"name": "array", "description": "Array to iterate over", "type": "array"},
15
+ {"name": "expression", "description": "Expression to sum for each element", "type": "any"},
16
+ {"name": "where", "description": "Optional filter condition", "type": "boolean", "required": False}
17
+ ],
18
+ "output": {"description": "Sum of the evaluated expressions", "type": "number", "example": 6},
19
+ "examples": [
20
+ "WITH [1, 2, 3] AS nums RETURN sum(n IN nums | n)",
21
+ "WITH [1, 2, 3, 4] AS nums RETURN sum(n IN nums WHERE n > 1 | n * 2)"
22
+ ]
23
+ })
24
+ class PredicateSum(PredicateFunction):
25
+ """PredicateSum function.
26
+
27
+ Calculates the sum of values in an array with optional filtering.
28
+ """
29
+
30
+ def __init__(self):
31
+ super().__init__("sum")
32
+
33
+ def value(self) -> Any:
34
+ self.reference.referred = self._value_holder
35
+ array = self.array.value()
36
+ if array is None or not isinstance(array, list):
37
+ raise ValueError("Invalid array for sum function")
38
+
39
+ _sum: Optional[Any] = None
40
+ for item in array:
41
+ self._value_holder.holder = item
42
+ if self.where is None or self.where.value():
43
+ if _sum is None:
44
+ _sum = self._return.value()
45
+ else:
46
+ _sum += self._return.value()
47
+ return _sum
@@ -0,0 +1,28 @@
1
+ """Rand function."""
2
+
3
+ import random
4
+ from typing import Any
5
+
6
+ from .function import Function
7
+ from .function_metadata import FunctionDef
8
+
9
+
10
+ @FunctionDef({
11
+ "description": "Generates a random number between 0 and 1",
12
+ "category": "scalar",
13
+ "parameters": [],
14
+ "output": {"description": "Random number between 0 and 1", "type": "number", "example": 0.7234},
15
+ "examples": ["WITH rand() AS r RETURN r"]
16
+ })
17
+ class Rand(Function):
18
+ """Rand function.
19
+
20
+ Generates a random number between 0 and 1.
21
+ """
22
+
23
+ def __init__(self):
24
+ super().__init__("rand")
25
+ self._expected_parameter_count = 0
26
+
27
+ def value(self) -> Any:
28
+ return random.random()
@@ -0,0 +1,34 @@
1
+ """Range function."""
2
+
3
+ from typing import Any, List
4
+
5
+ from .function import Function
6
+ from .function_metadata import FunctionDef
7
+
8
+
9
+ @FunctionDef({
10
+ "description": "Generates an array of sequential integers",
11
+ "category": "scalar",
12
+ "parameters": [
13
+ {"name": "start", "description": "Starting number (inclusive)", "type": "number"},
14
+ {"name": "end", "description": "Ending number (inclusive)", "type": "number"}
15
+ ],
16
+ "output": {"description": "Array of integers from start to end", "type": "array", "items": {"type": "number"}, "example": [1, 2, 3, 4, 5]},
17
+ "examples": ["WITH range(1, 5) AS nums RETURN nums"]
18
+ })
19
+ class Range(Function):
20
+ """Range function.
21
+
22
+ Generates an array of sequential integers.
23
+ """
24
+
25
+ def __init__(self):
26
+ super().__init__("range")
27
+ self._expected_parameter_count = 2
28
+
29
+ def value(self) -> Any:
30
+ start = self.get_children()[0].value()
31
+ end = self.get_children()[1].value()
32
+ if not isinstance(start, (int, float)) or not isinstance(end, (int, float)):
33
+ raise ValueError("Invalid arguments for range function")
34
+ return list(range(int(start), int(end) + 1))