quantum-framework 0.18.0__py3-none-any.whl

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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,2619 @@
1
+ """
2
+ Quantum AST Nodes - Classes that represent elements of the Quantum language
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import List, Dict, Any, Optional, Union
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ class QuantumNode(ABC):
11
+ """Base class for all Quantum AST nodes"""
12
+
13
+ @abstractmethod
14
+ def to_dict(self) -> Dict[str, Any]:
15
+ """Convert the node to dictionary (debug/serialization)"""
16
+ pass
17
+
18
+ @abstractmethod
19
+ def validate(self) -> List[str]:
20
+ """Validate the node and return list of errors"""
21
+ pass
22
+
23
+
24
+ @dataclass
25
+ class QuantumParam:
26
+ """Represents a <q:param>"""
27
+ name: str
28
+ type: str = "string"
29
+ required: bool = False
30
+ default: Optional[str] = None
31
+ validation: Optional[str] = None
32
+ description: Optional[str] = None
33
+
34
+ # REST-specific (optional)
35
+ source: str = "auto" # auto, path, query, body, header, cookie
36
+
37
+ # Validation (same as SetNode)
38
+ validate_rule: Optional[str] = None
39
+ pattern: Optional[str] = None
40
+ min: Optional[Any] = None
41
+ max: Optional[Any] = None
42
+ minlength: Optional[int] = None
43
+ maxlength: Optional[int] = None
44
+ range: Optional[str] = None
45
+ enum: Optional[str] = None
46
+
47
+ # File upload (when type="binary")
48
+ maxsize: Optional[str] = None
49
+ accept: Optional[str] = None
50
+
51
+ def validate(self) -> List[str]:
52
+ errors = []
53
+ if not self.name:
54
+ errors.append("Param name is required")
55
+ return errors
56
+
57
+
58
+ @dataclass
59
+ class QuantumReturn:
60
+ """Represents a <q:return>"""
61
+ name: Optional[str] = None
62
+ type: str = "string"
63
+ value: Optional[str] = None
64
+ description: Optional[str] = None
65
+
66
+ def validate(self) -> List[str]:
67
+ errors = []
68
+ if not self.value:
69
+ errors.append("Return value is required")
70
+ return errors
71
+
72
+
73
+ @dataclass
74
+ class QuantumRoute:
75
+ """Represents a <q:route>"""
76
+ path: str
77
+ method: str = "GET"
78
+ returns: List[QuantumReturn] = None
79
+
80
+ def __post_init__(self):
81
+ if self.returns is None:
82
+ self.returns = []
83
+
84
+ def validate(self) -> List[str]:
85
+ errors = []
86
+ if not self.path:
87
+ errors.append("Route path is required")
88
+ if self.method not in ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']:
89
+ errors.append(f"Invalid HTTP method: {self.method}")
90
+ return errors
91
+
92
+
93
+ # MIGRATED: IfNode moved to feature-based structure
94
+ # Import from new location (Option C migration)
95
+ from .features.conditionals.src.ast_node import IfNode
96
+
97
+
98
+ class ComponentNode(QuantumNode):
99
+ """Represents a <q:component>"""
100
+
101
+ def __init__(self, name: str, component_type: str = "pure"):
102
+ self.name = name
103
+ self.component_type = component_type # pure, microservice, event-driven, worker, websocket, graphql, grpc, serverless
104
+
105
+ # Component-level config
106
+ self.port = None
107
+ self.base_path = None
108
+ self.health_endpoint = None
109
+ self.metrics_provider = None
110
+ self.trace_provider = None
111
+
112
+ # Phase G: Authentication & Security
113
+ self.require_auth = False # Require authentication to access component
114
+ self.require_role = None # Required role(s) to access (comma-separated: "admin,editor")
115
+ self.login_url = None # AUTH-4: overrides security.login_url for this component
116
+ self.require_permission = None # Required permission(s)
117
+
118
+ # HTML rendering & interactivity (Phase 1 & future Phase 3)
119
+ self.interactive = False # Enable client-side hydration (future)
120
+ self.has_html = False # Auto-detected if component contains HTML nodes
121
+
122
+ # Component elements
123
+ self.params: List[QuantumParam] = []
124
+ self.returns: List[QuantumReturn] = []
125
+ self.functions: List['FunctionNode'] = []
126
+ self.event_handlers: List['OnEventNode'] = []
127
+ self.script_blocks: List[str] = []
128
+ self.statements: List[QuantumNode] = [] # For control flow statements (includes HTML nodes)
129
+
130
+ # Resources (refs to Admin-configured resources)
131
+ self.resources: Dict[str, str] = {} # type -> ref_name (e.g., "database" -> "postgres-main")
132
+
133
+ def add_param(self, param: QuantumParam):
134
+ self.params.append(param)
135
+
136
+ def add_return(self, return_node: QuantumReturn):
137
+ self.returns.append(return_node)
138
+
139
+ def add_function(self, function: 'FunctionNode'):
140
+ function.container = self
141
+ self.functions.append(function)
142
+
143
+ def add_event_handler(self, handler: 'OnEventNode'):
144
+ self.event_handlers.append(handler)
145
+
146
+ def add_script(self, script: str):
147
+ self.script_blocks.append(script)
148
+
149
+ def add_statement(self, statement: QuantumNode):
150
+ """Add control flow statement (if, loop, set, etc)"""
151
+ self.statements.append(statement)
152
+
153
+ def add_resource(self, resource_type: str, ref_name: str):
154
+ """Add resource reference (from Admin panel)"""
155
+ self.resources[resource_type] = ref_name
156
+
157
+ def get_rest_endpoints(self) -> List['FunctionNode']:
158
+ """Get all functions that are REST endpoints"""
159
+ return [f for f in self.functions if f.is_rest_enabled()]
160
+
161
+ def to_dict(self) -> Dict[str, Any]:
162
+ return {
163
+ "type": "component",
164
+ "name": self.name,
165
+ "component_type": self.component_type,
166
+ "port": self.port,
167
+ "base_path": self.base_path,
168
+ "interactive": self.interactive,
169
+ "has_html": self.has_html,
170
+ "params": [p.__dict__ for p in self.params],
171
+ "returns": [r.__dict__ for r in self.returns],
172
+ "functions": [f.to_dict() for f in self.functions],
173
+ "event_handlers": [eh.to_dict() for eh in self.event_handlers],
174
+ "script_blocks": len(self.script_blocks),
175
+ "statements": len(self.statements),
176
+ "resources": self.resources
177
+ }
178
+
179
+ def validate(self) -> List[str]:
180
+ errors = []
181
+ if not self.name:
182
+ errors.append("Component name is required")
183
+
184
+ # Validate component type
185
+ valid_types = ['pure', 'microservice', 'event-driven', 'worker', 'websocket', 'graphql', 'grpc', 'serverless']
186
+ if self.component_type not in valid_types:
187
+ errors.append(f"Invalid component type: {self.component_type}. Must be one of {valid_types}")
188
+
189
+ for param in self.params:
190
+ errors.extend(param.validate())
191
+
192
+ for ret in self.returns:
193
+ errors.extend(ret.validate())
194
+
195
+ for func in self.functions:
196
+ errors.extend(func.validate())
197
+
198
+ for handler in self.event_handlers:
199
+ errors.extend(handler.validate())
200
+
201
+ for statement in self.statements:
202
+ errors.extend(statement.validate())
203
+
204
+ return errors
205
+
206
+
207
+ class ApplicationNode(QuantumNode):
208
+ """Represents a <q:application>"""
209
+
210
+ def __init__(self, app_id: str, app_type: str):
211
+ self.app_id = app_id
212
+ self.app_type = app_type
213
+ self.engine: Optional[str] = None # "2d" for game engine
214
+ self.routes: List[QuantumRoute] = []
215
+ self.components: List[ComponentNode] = []
216
+
217
+ # q:set e q:function escritos direto no <q:application>.
218
+ #
219
+ # Nao havia onde guarda-los, e os parsers por engine so olhavam os
220
+ # filhos do PROPRIO namespace (qt:, qg:, ui:), entao um
221
+ #
222
+ # <q:application type="terminal">
223
+ # <q:function name="salvar"> ... </q:function>
224
+ #
225
+ # era descartado sem uma palavra. O gerador do terminal so
226
+ # encontrava funcoes declaradas DENTRO de um <qt:screen>, e o
227
+ # on-click que apontava para a funcao global caia no ramo "acao
228
+ # desconhecida", virando um `self.notify("Action: salvar")` — o
229
+ # botao anunciava a acao em vez de executa-la.
230
+ self.functions: List[QuantumNode] = [] # FunctionNode list
231
+ self.state_vars: List[QuantumNode] = [] # SetNode list
232
+
233
+ # Game Engine 2D: game-specific children
234
+ self.scenes: List[QuantumNode] = [] # SceneNode list
235
+ self.behaviors: List[QuantumNode] = [] # BehaviorNode list
236
+ self.prefabs: List[QuantumNode] = [] # PrefabNode list
237
+ self.enemies: List[QuantumNode] = [] # EnemyNode list
238
+ self.persistent: List[QuantumNode] = [] # PersistentNode children (SetNodes)
239
+
240
+ # Terminal Engine: terminal-specific children
241
+ self.screens: List[QuantumNode] = [] # ScreenNode list
242
+ self.keybindings: List[QuantumNode] = [] # KeybindingNode list
243
+ self.services: List[QuantumNode] = [] # ServiceNode list
244
+ self.terminal_css: str = "" # Accumulated TCSS
245
+
246
+ # Testing Engine: testing-specific children
247
+ self.test_suites: List[QuantumNode] = [] # TestSuiteNode list
248
+ self.test_config: Optional[QuantumNode] = None # BrowserConfigNode
249
+ self.test_fixtures: List[QuantumNode] = [] # FixtureNode_Testing list
250
+ self.test_mocks: List[QuantumNode] = [] # MockNode_Testing list
251
+ self.test_auth_states: List[QuantumNode] = [] # AuthNode list
252
+
253
+ # UI Engine: multi-target UI children
254
+ self.ui_windows: List[QuantumNode] = [] # UIWindowNode list
255
+ self.ui_children: List[QuantumNode] = [] # Top-level UI nodes (no window)
256
+ self.ui_target: str = 'html' # html, textual, desktop
257
+ self.ui_theme: Optional[QuantumNode] = None # UIThemeNode (from ui:theme or theme attr)
258
+ self.ui_theme_preset: Optional[str] = None # Theme preset from theme attribute
259
+
260
+ def add_route(self, route: QuantumRoute):
261
+ self.routes.append(route)
262
+
263
+ def add_component(self, component: ComponentNode):
264
+ self.components.append(component)
265
+
266
+ def to_dict(self) -> Dict[str, Any]:
267
+ result = {
268
+ "type": "application",
269
+ "app_id": self.app_id,
270
+ "app_type": self.app_type,
271
+ "engine": self.engine,
272
+ "routes": [r.__dict__ for r in self.routes],
273
+ "components": [c.to_dict() for c in self.components],
274
+ }
275
+ if self.scenes:
276
+ result["scenes"] = [s.to_dict() for s in self.scenes]
277
+ if self.behaviors:
278
+ result["behaviors"] = [b.to_dict() for b in self.behaviors]
279
+ if self.prefabs:
280
+ result["prefabs"] = [p.to_dict() for p in self.prefabs]
281
+ if self.screens:
282
+ result["screens"] = [s.to_dict() for s in self.screens]
283
+ if self.keybindings:
284
+ result["keybindings"] = [k.to_dict() for k in self.keybindings]
285
+ if self.services:
286
+ result["services"] = [s.to_dict() for s in self.services]
287
+ if self.terminal_css:
288
+ result["terminal_css"] = self.terminal_css
289
+ if self.test_suites:
290
+ result["test_suites"] = [s.to_dict() for s in self.test_suites]
291
+ if self.test_config:
292
+ result["test_config"] = self.test_config.to_dict()
293
+ if self.test_fixtures:
294
+ result["test_fixtures"] = [f.to_dict() for f in self.test_fixtures]
295
+ if self.test_mocks:
296
+ result["test_mocks"] = [m.to_dict() for m in self.test_mocks]
297
+ if self.test_auth_states:
298
+ result["test_auth_states"] = [a.to_dict() for a in self.test_auth_states]
299
+ if self.ui_windows:
300
+ result["ui_windows"] = [w.to_dict() for w in self.ui_windows]
301
+ if self.ui_children:
302
+ result["ui_children"] = [c.to_dict() for c in self.ui_children]
303
+ if self.ui_target != 'html':
304
+ result["ui_target"] = self.ui_target
305
+ if self.ui_theme:
306
+ result["ui_theme"] = self.ui_theme.to_dict()
307
+ if self.ui_theme_preset:
308
+ result["ui_theme_preset"] = self.ui_theme_preset
309
+ return result
310
+
311
+ def validate(self) -> List[str]:
312
+ errors = []
313
+ if not self.app_id:
314
+ errors.append("Application ID is required")
315
+ if not self.app_type:
316
+ errors.append("Application type is required")
317
+
318
+ for route in self.routes:
319
+ errors.extend(route.validate())
320
+
321
+ for component in self.components:
322
+ errors.extend(component.validate())
323
+
324
+ # Validate game children
325
+ for scene in self.scenes:
326
+ if hasattr(scene, 'validate'):
327
+ errors.extend(scene.validate())
328
+ for behavior in self.behaviors:
329
+ if hasattr(behavior, 'validate'):
330
+ errors.extend(behavior.validate())
331
+ for prefab in self.prefabs:
332
+ if hasattr(prefab, 'validate'):
333
+ errors.extend(prefab.validate())
334
+
335
+ # Validate terminal children
336
+ for screen in self.screens:
337
+ if hasattr(screen, 'validate'):
338
+ errors.extend(screen.validate())
339
+ for keybinding in self.keybindings:
340
+ if hasattr(keybinding, 'validate'):
341
+ errors.extend(keybinding.validate())
342
+ for service in self.services:
343
+ if hasattr(service, 'validate'):
344
+ errors.extend(service.validate())
345
+
346
+ # Validate testing children
347
+ for suite in self.test_suites:
348
+ if hasattr(suite, 'validate'):
349
+ errors.extend(suite.validate())
350
+ if self.test_config and hasattr(self.test_config, 'validate'):
351
+ errors.extend(self.test_config.validate())
352
+ for fixture in self.test_fixtures:
353
+ if hasattr(fixture, 'validate'):
354
+ errors.extend(fixture.validate())
355
+ for mock in self.test_mocks:
356
+ if hasattr(mock, 'validate'):
357
+ errors.extend(mock.validate())
358
+ for auth in self.test_auth_states:
359
+ if hasattr(auth, 'validate'):
360
+ errors.extend(auth.validate())
361
+
362
+ # Validate UI children
363
+ for window in self.ui_windows:
364
+ if hasattr(window, 'validate'):
365
+ errors.extend(window.validate())
366
+ for ui_child in self.ui_children:
367
+ if hasattr(ui_child, 'validate'):
368
+ errors.extend(ui_child.validate())
369
+
370
+ return errors
371
+
372
+
373
+ class JobNode(QuantumNode):
374
+ """
375
+ Represents a <q:job> - Job queue for batch processing.
376
+
377
+ Examples:
378
+ <q:job name="sendEmail" queue="emails" priority="5">
379
+ <q:param name="to" type="string" />
380
+ <q:param name="subject" type="string" />
381
+ <q:mail to="{to}" subject="{subject}">...</q:mail>
382
+ </q:job>
383
+
384
+ <q:job name="processOrder" action="dispatch" delay="5m" />
385
+ """
386
+
387
+ def __init__(self, name: str, queue: str = 'default', action: str = 'define'):
388
+ self.name = name
389
+ self.queue = queue
390
+ self.action = action # define, dispatch, batch
391
+ self.delay: Optional[str] = None # e.g., '30s', '5m', '1h'
392
+ self.priority: int = 0
393
+ self.attempts: int = 3
394
+ self.backoff: str = '30s'
395
+ self.timeout: Optional[str] = None
396
+ self.params: List['QuantumParam'] = []
397
+ self.body: List['QuantumNode'] = []
398
+
399
+ # Legacy compatibility
400
+ self.job_id: Optional[str] = name
401
+ self.schedule: Optional[str] = None
402
+ self.tasks: List[Dict[str, Any]] = []
403
+
404
+ def add_param(self, param: 'QuantumParam'):
405
+ """Add parameter to job"""
406
+ self.params.append(param)
407
+
408
+ def add_statement(self, statement: 'QuantumNode'):
409
+ """Add statement to job body"""
410
+ self.body.append(statement)
411
+
412
+ def to_dict(self) -> Dict[str, Any]:
413
+ return {
414
+ "type": "job",
415
+ "name": self.name,
416
+ "queue": self.queue,
417
+ "action": self.action,
418
+ "delay": self.delay,
419
+ "priority": self.priority,
420
+ "attempts": self.attempts,
421
+ "backoff": self.backoff,
422
+ "timeout": self.timeout,
423
+ "params": [p.__dict__ for p in self.params],
424
+ "body_statements": len(self.body),
425
+ # Legacy
426
+ "job_id": self.job_id,
427
+ "schedule": self.schedule,
428
+ "tasks": self.tasks
429
+ }
430
+
431
+ def validate(self) -> List[str]:
432
+ errors = []
433
+ if not self.name:
434
+ errors.append("Job name is required")
435
+ if self.action not in ['define', 'dispatch', 'batch']:
436
+ errors.append(f"Invalid job action: {self.action}. Must be define, dispatch, or batch")
437
+ if self.priority < 0 or self.priority > 10:
438
+ errors.append(f"Job priority must be between 0 and 10")
439
+ for param in self.params:
440
+ errors.extend(param.validate())
441
+ for statement in self.body:
442
+ if hasattr(statement, 'validate'):
443
+ errors.extend(statement.validate())
444
+ return errors
445
+
446
+ def __repr__(self):
447
+ return f'<JobNode name={self.name} queue={self.queue} action={self.action}>'
448
+
449
+
450
+ @dataclass
451
+ class ScheduleNode(QuantumNode):
452
+ """
453
+ Represents a <q:schedule> - Scheduled task execution (like cfschedule).
454
+
455
+ Examples:
456
+ <q:schedule name="dailyReport" interval="1d" timezone="America/Sao_Paulo">
457
+ <q:query datasource="db" name="stats">SELECT * FROM daily_stats</q:query>
458
+ <q:mail to="admin@example.com" subject="Daily Report">...</q:mail>
459
+ </q:schedule>
460
+
461
+ <q:schedule name="cleanup" cron="0 2 * * *" retry="3">
462
+ <q:query datasource="db">DELETE FROM temp_data WHERE created_at < NOW() - INTERVAL 7 DAY</q:query>
463
+ </q:schedule>
464
+
465
+ <q:schedule name="oneTimeTask" at="2024-12-25T00:00:00" enabled="true" />
466
+ """
467
+ name: str
468
+ action: str = 'run' # run, pause, resume, delete
469
+ interval: Optional[str] = None # 30s, 5m, 1h, 1d
470
+ cron: Optional[str] = None # cron expression
471
+ at: Optional[str] = None # specific datetime (ISO 8601)
472
+ timezone: str = 'UTC'
473
+ retry: int = 3
474
+ timeout: Optional[str] = None
475
+ overlap: bool = False # Allow overlapping executions
476
+ enabled: bool = True
477
+ body: List['QuantumNode'] = field(default_factory=list)
478
+
479
+ def add_statement(self, statement: 'QuantumNode'):
480
+ """Add statement to schedule body"""
481
+ self.body.append(statement)
482
+
483
+ def to_dict(self) -> Dict[str, Any]:
484
+ return {
485
+ "type": "schedule",
486
+ "name": self.name,
487
+ "action": self.action,
488
+ "interval": self.interval,
489
+ "cron": self.cron,
490
+ "at": self.at,
491
+ "timezone": self.timezone,
492
+ "retry": self.retry,
493
+ "timeout": self.timeout,
494
+ "overlap": self.overlap,
495
+ "enabled": self.enabled,
496
+ "body_statements": len(self.body)
497
+ }
498
+
499
+ def validate(self) -> List[str]:
500
+ errors = []
501
+ if not self.name:
502
+ errors.append("Schedule name is required")
503
+ if self.action not in ['run', 'pause', 'resume', 'delete']:
504
+ errors.append(f"Invalid schedule action: {self.action}. Must be run, pause, resume, or delete")
505
+ # Must have at least one trigger type for 'run' action
506
+ if self.action == 'run' and not (self.interval or self.cron or self.at):
507
+ errors.append("Schedule requires 'interval', 'cron', or 'at' attribute for run action")
508
+ if self.retry < 0:
509
+ errors.append("Schedule retry must be non-negative")
510
+ for statement in self.body:
511
+ if hasattr(statement, 'validate'):
512
+ errors.extend(statement.validate())
513
+ return errors
514
+
515
+ def __repr__(self):
516
+ trigger = self.interval or self.cron or self.at or 'none'
517
+ return f'<ScheduleNode name={self.name} trigger={trigger}>'
518
+
519
+
520
+ @dataclass
521
+ class ThreadNode(QuantumNode):
522
+ """
523
+ Represents a <q:thread> - Async thread execution (like cfthread).
524
+
525
+ Examples:
526
+ <q:thread name="sendEmails" priority="high">
527
+ <q:loop query="pendingEmails">
528
+ <q:mail to="{email}" subject="Notification">...</q:mail>
529
+ </q:loop>
530
+ </q:thread>
531
+
532
+ <q:thread name="processImages" timeout="5m" onComplete="handleComplete">
533
+ <q:invoke url="/api/process" method="POST" />
534
+ </q:thread>
535
+
536
+ <q:thread name="worker1" action="join" /> <!-- Wait for thread -->
537
+ <q:thread name="worker1" action="terminate" /> <!-- Kill thread -->
538
+ """
539
+ name: str
540
+ action: str = 'run' # run, join, terminate
541
+ priority: str = 'normal' # low, normal, high
542
+ timeout: Optional[str] = None # e.g., '30s', '5m'
543
+ on_complete: Optional[str] = None # Callback function name
544
+ on_error: Optional[str] = None # Error handler function name
545
+ body: List['QuantumNode'] = field(default_factory=list)
546
+
547
+ def add_statement(self, statement: 'QuantumNode'):
548
+ """Add statement to thread body"""
549
+ self.body.append(statement)
550
+
551
+ def to_dict(self) -> Dict[str, Any]:
552
+ return {
553
+ "type": "thread",
554
+ "name": self.name,
555
+ "action": self.action,
556
+ "priority": self.priority,
557
+ "timeout": self.timeout,
558
+ "on_complete": self.on_complete,
559
+ "on_error": self.on_error,
560
+ "body_statements": len(self.body)
561
+ }
562
+
563
+ def validate(self) -> List[str]:
564
+ errors = []
565
+ if not self.name:
566
+ errors.append("Thread name is required")
567
+ if self.action not in ['run', 'join', 'terminate']:
568
+ errors.append(f"Invalid thread action: {self.action}. Must be run, join, or terminate")
569
+ if self.priority not in ['low', 'normal', 'high']:
570
+ errors.append(f"Invalid thread priority: {self.priority}. Must be low, normal, or high")
571
+ # Body required only for 'run' action
572
+ if self.action == 'run' and not self.body:
573
+ errors.append("Thread with action='run' requires body statements")
574
+ for statement in self.body:
575
+ if hasattr(statement, 'validate'):
576
+ errors.extend(statement.validate())
577
+ return errors
578
+
579
+ def __repr__(self):
580
+ return f'<ThreadNode name={self.name} action={self.action} priority={self.priority}>'
581
+
582
+
583
+ # MIGRATED: LoopNode moved to feature-based structure
584
+ # Import from new location (Option C migration)
585
+ from .features.loops.src.ast_node import LoopNode
586
+
587
+ # MIGRATED: FunctionNode and RestConfig moved to feature-based structure
588
+ # Import from new location (Option C migration)
589
+ from .features.functions.src.ast_node import FunctionNode, RestConfig
590
+
591
+
592
+
593
+
594
+ class DispatchEventNode(QuantumNode):
595
+ """Represents a <q:dispatchEvent> - Publish event"""
596
+
597
+ def __init__(self, event: str):
598
+ self.event = event
599
+ self.data = None
600
+ self.queue = None
601
+ self.exchange = None
602
+ self.routing_key = None
603
+ self.priority = "normal" # low, normal, high
604
+ self.delay = None # e.g., "5s"
605
+ self.ttl = None # e.g., "60s"
606
+ self.metadata = None
607
+
608
+ def to_dict(self) -> Dict[str, Any]:
609
+ return {
610
+ "type": "dispatchEvent",
611
+ "event": self.event,
612
+ "data": self.data,
613
+ "queue": self.queue,
614
+ "exchange": self.exchange,
615
+ "routing_key": self.routing_key,
616
+ "priority": self.priority,
617
+ "delay": self.delay,
618
+ "ttl": self.ttl,
619
+ "metadata": self.metadata
620
+ }
621
+
622
+ def validate(self) -> List[str]:
623
+ errors = []
624
+ if not self.event:
625
+ errors.append("Event name is required for dispatchEvent")
626
+ if self.priority not in ['low', 'normal', 'high']:
627
+ errors.append(f"Invalid priority: {self.priority}. Must be low, normal, or high")
628
+ return errors
629
+
630
+
631
+ class OnEventNode(QuantumNode):
632
+ """Represents a <q:onEvent> - Subscribe to event"""
633
+
634
+ def __init__(self, event: str):
635
+ self.event = event # Event pattern (e.g., "user.*" or "user.created")
636
+ self.queue = None
637
+ self.max_retries = 0
638
+ self.retry_delay = None # e.g., "30s"
639
+ self.dead_letter = None # Dead letter queue
640
+ self.filter = None # Filter expression
641
+ self.concurrent = 1 # Parallel processing
642
+ self.prefetch = 1 # Prefetch count
643
+ self.timeout = None # Handler timeout
644
+ self.body: List[QuantumNode] = []
645
+
646
+ def add_statement(self, statement: QuantumNode):
647
+ """Add statement to event handler body"""
648
+ self.body.append(statement)
649
+
650
+ def to_dict(self) -> Dict[str, Any]:
651
+ return {
652
+ "type": "onEvent",
653
+ "event": self.event,
654
+ "queue": self.queue,
655
+ "max_retries": self.max_retries,
656
+ "retry_delay": self.retry_delay,
657
+ "dead_letter": self.dead_letter,
658
+ "filter": self.filter,
659
+ "concurrent": self.concurrent,
660
+ "prefetch": self.prefetch,
661
+ "timeout": self.timeout,
662
+ "body_statements": len(self.body)
663
+ }
664
+
665
+ def validate(self) -> List[str]:
666
+ errors = []
667
+ if not self.event:
668
+ errors.append("Event pattern is required for onEvent")
669
+
670
+ # Validate body
671
+ for statement in self.body:
672
+ errors.extend(statement.validate())
673
+
674
+ return errors
675
+
676
+
677
+ # MIGRATED: SetNode and PersistNode moved to feature-based structure
678
+ # Import from new location (Option C migration)
679
+ from .features.state_management.src.ast_node import SetNode
680
+
681
+
682
+ class QueryNode(QuantumNode):
683
+ """Represents a <q:query> - Database query component"""
684
+
685
+ def __init__(self, name: str, datasource: str, sql: str, params: List['QueryParamNode'] = None):
686
+ self.name = name
687
+ self.datasource = datasource
688
+ self.sql = sql
689
+ self.params = params or []
690
+
691
+ # Optional attributes
692
+ self.source = None # For Query-of-Queries
693
+ self.mode = None # None for normal, "rag" for RAG pipeline
694
+ self.rag_model = None # LLM model override for RAG queries
695
+ self.cache = False
696
+ self.ttl = None
697
+ self.reactive = False
698
+ self.interval = None
699
+ self.paginate = False # Enable automatic pagination
700
+ self.page = None
701
+ self.page_expr = None # DB-9: page as an expression (default {query.page})
702
+ self.sortable = False # UI-13: ORDER BY the URL's ?sort=/&dir=
703
+ self.page_size = 20
704
+ self.timeout = None
705
+ self.maxrows = None
706
+ self.result = None # Variable name for metadata
707
+
708
+ def add_param(self, param: 'QueryParamNode'):
709
+ """Add parameter to query"""
710
+ self.params.append(param)
711
+
712
+ def to_dict(self) -> Dict[str, Any]:
713
+ return {
714
+ "type": "query",
715
+ "name": self.name,
716
+ "datasource": self.datasource,
717
+ "sql": self.sql[:50] + "..." if len(self.sql) > 50 else self.sql,
718
+ "params": [p.to_dict() for p in self.params],
719
+ "cache": self.cache,
720
+ "reactive": self.reactive
721
+ }
722
+
723
+ def validate(self) -> List[str]:
724
+ errors = []
725
+ # name is optional: a write query (UPDATE/INSERT/DELETE) has no result
726
+ # set to bind. Reads without a name simply cannot be referenced.
727
+ if not self.datasource and not self.source:
728
+ errors.append("Query requires either 'datasource' or 'source' attribute")
729
+ if not self.sql:
730
+ errors.append("Query SQL is required")
731
+
732
+ # Validate params
733
+ for param in self.params:
734
+ errors.extend(param.validate())
735
+
736
+ return errors
737
+
738
+
739
+ class QueryParamNode(QuantumNode):
740
+ """Represents a <q:param> within q:query"""
741
+
742
+ def __init__(self, name: str, value: Any, param_type: str):
743
+ self.name = name
744
+ self.value = value
745
+ self.param_type = param_type # string, integer, decimal, boolean, datetime, date, time, array, json
746
+
747
+ # Optional attributes
748
+ self.null = False
749
+ self.max_length = None
750
+ self.scale = None
751
+
752
+ def to_dict(self) -> Dict[str, Any]:
753
+ return {
754
+ "type": "queryParam",
755
+ "name": self.name,
756
+ "value": str(self.value)[:20] + "..." if self.value and len(str(self.value)) > 20 else self.value,
757
+ "param_type": self.param_type,
758
+ "null": self.null
759
+ }
760
+
761
+ def validate(self) -> List[str]:
762
+ errors = []
763
+ if not self.name:
764
+ errors.append("Query parameter name is required")
765
+ if self.value is None and not self.null:
766
+ errors.append(f"Query parameter '{self.name}' cannot be null")
767
+ if self.param_type not in ['string', 'integer', 'decimal', 'boolean', 'datetime', 'date', 'time', 'array', 'json']:
768
+ errors.append(f"Invalid parameter type: {self.param_type}")
769
+
770
+ return errors
771
+
772
+
773
+ # MIGRATED: InvokeNode and InvokeHeaderNode moved to feature-based structure
774
+ # Import from new location (Option C migration)
775
+ from .features.invocation.src.ast_node import InvokeNode, InvokeHeaderNode
776
+
777
+ # MIGRATED: DataNode and related nodes moved to feature-based structure
778
+ # Import from new location (Option C migration)
779
+ from .features.data_import.src.ast_node import (
780
+ DataNode, ColumnNode, FieldNode, TransformNode,
781
+ FilterNode, SortNode, LimitNode, ComputeNode, HeaderNode
782
+ )
783
+
784
+
785
+ # ============================================
786
+ # HTML RENDERING NODES (Phase 1)
787
+ # ============================================
788
+
789
+ # HTML void elements that are self-closing
790
+ HTML_VOID_ELEMENTS = {
791
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
792
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'
793
+ }
794
+
795
+
796
+ class HTMLNode(QuantumNode):
797
+ """
798
+ Represents an HTML element that should be rendered to output.
799
+
800
+ Phase 1: Server-side rendering only
801
+ Phase 3: Can be marked for client-side hydration
802
+
803
+ Examples:
804
+ <div class="container">...</div>
805
+ <img src="/logo.png" />
806
+ <a href="/about">About</a>
807
+ <button q:click="handleClick">Click me</button> (future)
808
+ """
809
+
810
+ def __init__(
811
+ self,
812
+ tag: str,
813
+ attributes: Optional[Dict[str, str]] = None,
814
+ children: Optional[List[QuantumNode]] = None,
815
+ self_closing: bool = False
816
+ ):
817
+ self.tag = tag
818
+ self.attributes = attributes or {}
819
+ self.children = children or []
820
+ self.self_closing = self_closing or tag in HTML_VOID_ELEMENTS
821
+
822
+ # Future: client-side event handlers (Phase 3)
823
+ self.has_events = self._detect_event_handlers()
824
+
825
+ def _detect_event_handlers(self) -> bool:
826
+ """Detect if element has client-side event handlers (q:click, q:change, etc)"""
827
+ event_attributes = ['q:click', 'q:change', 'q:input', 'q:submit', 'q:keyup', 'q:keydown']
828
+ return any(attr in self.attributes for attr in event_attributes)
829
+
830
+ def to_dict(self) -> Dict[str, Any]:
831
+ return {
832
+ "type": "html",
833
+ "tag": self.tag,
834
+ "attributes": self.attributes,
835
+ "children_count": len(self.children),
836
+ "self_closing": self.self_closing,
837
+ "has_events": self.has_events
838
+ }
839
+
840
+ def validate(self) -> List[str]:
841
+ errors = []
842
+ if not self.tag:
843
+ errors.append("HTML tag name is required")
844
+
845
+ # Validate children
846
+ for child in self.children:
847
+ if hasattr(child, 'validate'):
848
+ errors.extend(child.validate())
849
+
850
+ return errors
851
+
852
+ def __repr__(self):
853
+ return f'<HTMLNode tag={self.tag} attrs={len(self.attributes)} children={len(self.children)}>'
854
+
855
+
856
+ class TextNode(QuantumNode):
857
+ """
858
+ Represents raw text content, possibly with databinding expressions.
859
+
860
+ Examples:
861
+ "Hello World"
862
+ "User: {user.name}"
863
+ "Total: ${price * quantity}"
864
+ "Items: {items.length}"
865
+ """
866
+
867
+ def __init__(self, content: str):
868
+ self.content = content
869
+ self.has_databinding = '{' in content and '}' in content
870
+
871
+ def to_dict(self) -> Dict[str, Any]:
872
+ preview = self.content[:50] + '...' if len(self.content) > 50 else self.content
873
+ return {
874
+ "type": "text",
875
+ "content": preview,
876
+ "has_databinding": self.has_databinding,
877
+ "length": len(self.content)
878
+ }
879
+
880
+ def validate(self) -> List[str]:
881
+ # Text nodes are always valid
882
+ return []
883
+
884
+ def __repr__(self):
885
+ preview = self.content[:30] + '...' if len(self.content) > 30 else self.content
886
+ return f'<TextNode "{preview}">'
887
+
888
+
889
+ class DocTypeNode(QuantumNode):
890
+ """
891
+ Represents HTML DOCTYPE declaration.
892
+
893
+ Example:
894
+ <!DOCTYPE html>
895
+ """
896
+
897
+ def __init__(self, value: str = "html"):
898
+ self.value = value
899
+
900
+ def to_dict(self) -> Dict[str, Any]:
901
+ return {
902
+ "type": "doctype",
903
+ "value": self.value
904
+ }
905
+
906
+ def validate(self) -> List[str]:
907
+ return []
908
+
909
+ def __repr__(self):
910
+ return f'<DocTypeNode {self.value}>'
911
+
912
+
913
+ class CommentNode(QuantumNode):
914
+ """
915
+ Represents HTML/XML comments.
916
+
917
+ Example:
918
+ <!-- This is a comment -->
919
+ """
920
+
921
+ def __init__(self, content: str):
922
+ self.content = content
923
+
924
+ def to_dict(self) -> Dict[str, Any]:
925
+ return {
926
+ "type": "comment",
927
+ "content": self.content[:50] + '...' if len(self.content) > 50 else self.content
928
+ }
929
+
930
+ def validate(self) -> List[str]:
931
+ return []
932
+
933
+ def __repr__(self):
934
+ return f'<CommentNode>'
935
+
936
+
937
+ # ============================================
938
+ # COMPONENT COMPOSITION NODES (Phase 2)
939
+ # ============================================
940
+
941
+ class ImportNode(QuantumNode):
942
+ """
943
+ Represents component import declaration.
944
+
945
+ Phase 2: Component Composition
946
+
947
+ Examples:
948
+ <q:import component="Header" />
949
+ <q:import component="Button" from="./components/ui" />
950
+ <q:import component="AdminLayout" as="Layout" />
951
+
952
+ Game Engine Imports (SMW Componentization):
953
+ <q:import behavior="PlayerBehavior" from="./behaviors" />
954
+ <q:import prefab="KoopaGreen" from="./prefabs" />
955
+ <q:import tilemap="yi1" from="./levels" />
956
+ """
957
+
958
+ def __init__(
959
+ self,
960
+ component: Optional[str] = None,
961
+ from_path: Optional[str] = None,
962
+ alias: Optional[str] = None,
963
+ behavior: Optional[str] = None,
964
+ prefab: Optional[str] = None,
965
+ tilemap: Optional[str] = None
966
+ ):
967
+ self.component = component
968
+ self.from_path = from_path
969
+ self.alias = alias or component or behavior or prefab or tilemap
970
+
971
+ # Game engine imports
972
+ self.behavior = behavior
973
+ self.prefab = prefab
974
+ self.tilemap = tilemap
975
+
976
+ @property
977
+ def import_type(self) -> str:
978
+ """Return the type of import: component, behavior, prefab, or tilemap."""
979
+ if self.behavior:
980
+ return "behavior"
981
+ elif self.prefab:
982
+ return "prefab"
983
+ elif self.tilemap:
984
+ return "tilemap"
985
+ return "component"
986
+
987
+ @property
988
+ def name(self) -> str:
989
+ """Return the imported item's name regardless of type."""
990
+ return self.component or self.behavior or self.prefab or self.tilemap or ""
991
+
992
+ def to_dict(self) -> Dict[str, Any]:
993
+ return {
994
+ "type": "import",
995
+ "import_type": self.import_type,
996
+ "component": self.component,
997
+ "behavior": self.behavior,
998
+ "prefab": self.prefab,
999
+ "tilemap": self.tilemap,
1000
+ "from": self.from_path,
1001
+ "alias": self.alias
1002
+ }
1003
+
1004
+ def validate(self) -> List[str]:
1005
+ errors = []
1006
+ if not self.component and not self.behavior and not self.prefab and not self.tilemap:
1007
+ errors.append("One of component, behavior, prefab, or tilemap is required for q:import")
1008
+ return errors
1009
+
1010
+ def __repr__(self):
1011
+ return f'<ImportNode {self.import_type}={self.name}>'
1012
+
1013
+
1014
+ class SlotNode(QuantumNode):
1015
+ """
1016
+ Represents content projection slot.
1017
+
1018
+ Phase 2: Component Composition
1019
+ Allows parent components to inject content into child components.
1020
+
1021
+ Examples:
1022
+ <q:slot /> <!-- Default slot -->
1023
+ <q:slot name="header" /> <!-- Named slot -->
1024
+ <q:slot name="footer">Default content here</q:slot>
1025
+ """
1026
+
1027
+ def __init__(
1028
+ self,
1029
+ name: str = "default",
1030
+ default_content: Optional[List[QuantumNode]] = None
1031
+ ):
1032
+ self.name = name
1033
+ self.default_content = default_content or []
1034
+
1035
+ def to_dict(self) -> Dict[str, Any]:
1036
+ return {
1037
+ "type": "slot",
1038
+ "name": self.name,
1039
+ "has_default": len(self.default_content) > 0
1040
+ }
1041
+
1042
+ def validate(self) -> List[str]:
1043
+ errors = []
1044
+ # Validate default content
1045
+ for node in self.default_content:
1046
+ if hasattr(node, 'validate'):
1047
+ errors.extend(node.validate())
1048
+ return errors
1049
+
1050
+ def __repr__(self):
1051
+ return f'<SlotNode name={self.name}>'
1052
+
1053
+
1054
+ class ComponentCallNode(QuantumNode):
1055
+ """
1056
+ Represents a call to another component (used internally during composition).
1057
+
1058
+ Phase 2: Component Composition
1059
+
1060
+ Examples:
1061
+ <Header title="Products" />
1062
+ <Button label="Save" color="green" />
1063
+ <Card title="Info">
1064
+ <p>Card content here</p>
1065
+ </Card>
1066
+ """
1067
+
1068
+ def __init__(
1069
+ self,
1070
+ component_name: str,
1071
+ props: Optional[Dict[str, str]] = None,
1072
+ children: Optional[List[QuantumNode]] = None
1073
+ ):
1074
+ self.component_name = component_name
1075
+ self.props = props or {}
1076
+ self.children = children or []
1077
+
1078
+ def to_dict(self) -> Dict[str, Any]:
1079
+ return {
1080
+ "type": "component_call",
1081
+ "component": self.component_name,
1082
+ "props": self.props,
1083
+ "children_count": len(self.children)
1084
+ }
1085
+
1086
+ def validate(self) -> List[str]:
1087
+ errors = []
1088
+ if not self.component_name:
1089
+ errors.append("Component name is required")
1090
+
1091
+ # Validate children
1092
+ for child in self.children:
1093
+ if hasattr(child, 'validate'):
1094
+ errors.extend(child.validate())
1095
+
1096
+ return errors
1097
+
1098
+ def __repr__(self):
1099
+ return f'<ComponentCallNode component={self.component_name} props={len(self.props)}>'
1100
+
1101
+
1102
+ # ============================================
1103
+ # FORMS & ACTIONS NODES (Phase A)
1104
+ # ============================================
1105
+
1106
+ class ActionNode(QuantumNode):
1107
+ """
1108
+ Represents a server-side action handler for forms.
1109
+
1110
+ Phase A: Forms & Actions
1111
+ Actions handle POST/PUT/DELETE requests with automatic form data binding,
1112
+ validation, and redirect support.
1113
+
1114
+ Examples:
1115
+ <q:action name="createUser" method="POST">
1116
+ <q:param name="email" type="email" required="true" />
1117
+ <q:param name="password" type="string" minlength="8" />
1118
+ <q:query datasource="db">
1119
+ INSERT INTO users (email, password) VALUES (:email, :password)
1120
+ </q:query>
1121
+ <q:redirect url="/users" />
1122
+ </q:action>
1123
+
1124
+ <q:action name="deleteProduct" method="DELETE">
1125
+ <q:param name="id" type="integer" required="true" />
1126
+ <q:query datasource="db">
1127
+ DELETE FROM products WHERE id = :id
1128
+ </q:query>
1129
+ <q:redirect url="/products" flash="Product deleted successfully" />
1130
+ </q:action>
1131
+ """
1132
+
1133
+ def __init__(
1134
+ self,
1135
+ name: str,
1136
+ method: str = "POST"
1137
+ ):
1138
+ self.name = name
1139
+ self.method = method.upper()
1140
+ self.params: List[QuantumParam] = []
1141
+ self.body: List[QuantumNode] = []
1142
+ # UI-10 (M17): params from a table's schema — see quantum/runtime/table_params.py
1143
+ self.table: Optional[str] = None
1144
+ self.table_datasource: Optional[str] = None
1145
+ self.table_columns: Optional[List[str]] = None
1146
+
1147
+ # Action configuration
1148
+ self.validate_csrf = True # Auto CSRF protection
1149
+ self.rate_limit = None # e.g., "10/minute"
1150
+ self.require_auth = False # Require authentication
1151
+
1152
+ def add_param(self, param: QuantumParam):
1153
+ """Add parameter to action"""
1154
+ self.params.append(param)
1155
+
1156
+ def add_statement(self, statement: QuantumNode):
1157
+ """Add statement to action body"""
1158
+ self.body.append(statement)
1159
+
1160
+ def to_dict(self) -> Dict[str, Any]:
1161
+ return {
1162
+ "type": "action",
1163
+ "name": self.name,
1164
+ "method": self.method,
1165
+ "params": [p.__dict__ for p in self.params],
1166
+ "body_statements": len(self.body),
1167
+ "validate_csrf": self.validate_csrf,
1168
+ "rate_limit": self.rate_limit,
1169
+ "require_auth": self.require_auth
1170
+ }
1171
+
1172
+ def validate(self) -> List[str]:
1173
+ errors = []
1174
+ if not self.name:
1175
+ errors.append("Action name is required")
1176
+
1177
+ if self.method not in ['POST', 'PUT', 'DELETE', 'PATCH']:
1178
+ errors.append(f"Invalid action method: {self.method}. Must be POST, PUT, DELETE, or PATCH")
1179
+
1180
+ # Validate params
1181
+ for param in self.params:
1182
+ errors.extend(param.validate())
1183
+
1184
+ # Validate body
1185
+ for statement in self.body:
1186
+ if hasattr(statement, 'validate'):
1187
+ errors.extend(statement.validate())
1188
+
1189
+ return errors
1190
+
1191
+ def __repr__(self):
1192
+ return f'<ActionNode name={self.name} method={self.method}>'
1193
+
1194
+
1195
+ class RedirectNode(QuantumNode):
1196
+ """
1197
+ Represents a redirect response.
1198
+
1199
+ Phase A: Forms & Actions
1200
+ Used within actions to redirect after processing.
1201
+
1202
+ Examples:
1203
+ <q:redirect url="/thank-you" />
1204
+ <q:redirect url="/users/{userId}" />
1205
+ <q:redirect url="/products" flash="Product created successfully" />
1206
+ <q:redirect url="/error" status="500" flash="An error occurred" />
1207
+ """
1208
+
1209
+ def __init__(
1210
+ self,
1211
+ url: str,
1212
+ flash: Optional[str] = None,
1213
+ status: int = 302
1214
+ ):
1215
+ self.url = url
1216
+ self.flash = flash # Flash message to show on next page
1217
+ self.status = status # HTTP status code (302 default)
1218
+
1219
+ def to_dict(self) -> Dict[str, Any]:
1220
+ return {
1221
+ "type": "redirect",
1222
+ "url": self.url,
1223
+ "flash": self.flash,
1224
+ "status": self.status
1225
+ }
1226
+
1227
+ def validate(self) -> List[str]:
1228
+ errors = []
1229
+ if not self.url:
1230
+ errors.append("Redirect URL is required")
1231
+
1232
+ # Validate status code
1233
+ if self.status not in [301, 302, 303, 307, 308]:
1234
+ errors.append(f"Invalid redirect status: {self.status}. Must be 301, 302, 303, 307, or 308")
1235
+
1236
+ return errors
1237
+
1238
+ def __repr__(self):
1239
+ return f'<RedirectNode url={self.url}>'
1240
+
1241
+
1242
+ class FlashNode(QuantumNode):
1243
+ """
1244
+ Represents a flash message (temporary message shown once).
1245
+
1246
+ Phase A: Forms & Actions
1247
+ Flash messages persist across redirects and are shown once.
1248
+
1249
+ Examples:
1250
+ <q:flash type="success" message="User created successfully" />
1251
+ <q:flash type="error" message="{errorMessage}" />
1252
+ <q:flash type="warning">Please verify your email</q:flash>
1253
+ """
1254
+
1255
+ def __init__(
1256
+ self,
1257
+ message: str,
1258
+ flash_type: str = "info"
1259
+ ):
1260
+ self.message = message
1261
+ self.flash_type = flash_type # info, success, warning, error
1262
+
1263
+ def to_dict(self) -> Dict[str, Any]:
1264
+ return {
1265
+ "type": "flash",
1266
+ "message": self.message,
1267
+ "flash_type": self.flash_type
1268
+ }
1269
+
1270
+ def validate(self) -> List[str]:
1271
+ errors = []
1272
+ if not self.message:
1273
+ errors.append("Flash message is required")
1274
+
1275
+ if self.flash_type not in ['info', 'success', 'warning', 'error']:
1276
+ errors.append(f"Invalid flash type: {self.flash_type}. Must be info, success, warning, or error")
1277
+
1278
+ return errors
1279
+
1280
+ def __repr__(self):
1281
+ return f'<FlashNode type={self.flash_type}>'
1282
+
1283
+
1284
+ class FileNode(QuantumNode):
1285
+ """
1286
+ Represents file upload handling (q:file).
1287
+
1288
+ Phase H: File Uploads
1289
+ Handles file uploads with validation and storage.
1290
+
1291
+ Examples:
1292
+ <q:file action="upload" file="{avatar}" destination="./uploads/" />
1293
+ <q:file action="upload" file="{document}" destination="./files/" nameConflict="makeUnique" />
1294
+ """
1295
+
1296
+ def __init__(
1297
+ self,
1298
+ action: str,
1299
+ file: str,
1300
+ destination: str = "./uploads/",
1301
+ name_conflict: str = "error", # error, overwrite, skip, makeUnique
1302
+ result: str = None
1303
+ ):
1304
+ self.action = action # upload, delete, move, copy
1305
+ self.file = file # Variable name containing file data
1306
+ self.destination = destination # Where to save file
1307
+ self.name_conflict = name_conflict # What to do if file exists
1308
+ self.result = result # Optional variable name to store upload result
1309
+
1310
+ def to_dict(self) -> Dict[str, Any]:
1311
+ return {
1312
+ "type": "file",
1313
+ "action": self.action,
1314
+ "file": self.file,
1315
+ "destination": self.destination,
1316
+ "name_conflict": self.name_conflict,
1317
+ "result": self.result
1318
+ }
1319
+
1320
+ def validate(self) -> List[str]:
1321
+ errors = []
1322
+
1323
+ if self.action not in ['upload', 'delete', 'move', 'copy']:
1324
+ errors.append(f"Invalid file action: {self.action}")
1325
+
1326
+ if not self.file:
1327
+ errors.append("File variable is required")
1328
+
1329
+ if self.action == 'upload' and not self.destination:
1330
+ errors.append("Destination is required for upload action")
1331
+
1332
+ if self.name_conflict not in ['error', 'overwrite', 'skip', 'makeUnique']:
1333
+ errors.append(f"Invalid nameConflict: {self.name_conflict}")
1334
+
1335
+ return errors
1336
+
1337
+ def __repr__(self):
1338
+ return f'<FileNode action={self.action} file={self.file}>'
1339
+
1340
+
1341
+ class MailNode(QuantumNode):
1342
+ """
1343
+ Represents email sending (q:mail).
1344
+
1345
+ Phase I: Email Sending
1346
+ ColdFusion cfmail-inspired email functionality.
1347
+
1348
+ Examples:
1349
+ <q:mail to="{email}" from="noreply@app.com" subject="Welcome!">
1350
+ <h1>Welcome {name}!</h1>
1351
+ </q:mail>
1352
+ """
1353
+
1354
+ def __init__(
1355
+ self,
1356
+ to: str,
1357
+ subject: str,
1358
+ from_addr: str = None,
1359
+ cc: str = None,
1360
+ bcc: str = None,
1361
+ reply_to: str = None,
1362
+ type: str = "html", # html or text
1363
+ charset: str = "UTF-8"
1364
+ ):
1365
+ self.to = to # Recipient email(s)
1366
+ self.subject = subject # Email subject
1367
+ self.from_addr = from_addr # Sender email
1368
+ self.cc = cc # CC recipients
1369
+ self.bcc = bcc # BCC recipients
1370
+ self.reply_to = reply_to # Reply-To address
1371
+ self.type = type # html or text
1372
+ self.charset = charset # Character encoding
1373
+ self.body = "" # Email body (set from tag content)
1374
+ self.attachments = [] # List of file attachments
1375
+
1376
+ def to_dict(self) -> Dict[str, Any]:
1377
+ return {
1378
+ "type": "mail",
1379
+ "to": self.to,
1380
+ "subject": self.subject,
1381
+ "from": self.from_addr,
1382
+ "cc": self.cc,
1383
+ "bcc": self.bcc,
1384
+ "reply_to": self.reply_to,
1385
+ "mail_type": self.type,
1386
+ "charset": self.charset,
1387
+ "body": self.body,
1388
+ "attachments": self.attachments
1389
+ }
1390
+
1391
+ def validate(self) -> List[str]:
1392
+ errors = []
1393
+
1394
+ if not self.to:
1395
+ errors.append("Email recipient (to) is required")
1396
+
1397
+ if not self.subject:
1398
+ errors.append("Email subject is required")
1399
+
1400
+ if self.type not in ['html', 'text']:
1401
+ errors.append(f"Invalid email type: {self.type}. Must be 'html' or 'text'")
1402
+
1403
+ return errors
1404
+
1405
+ def __repr__(self):
1406
+ return f'<MailNode to={self.to} subject={self.subject}>'
1407
+
1408
+
1409
+ # MIGRATED: KnowledgeNode moved to feature-based structure
1410
+ # Import from new location (Option C migration)
1411
+ from .features.knowledge_base.src import KnowledgeNode, KnowledgeSourceNode
1412
+
1413
+ # GAME ENGINE 2D: Import game AST nodes
1414
+ from .features.game_engine_2d.src.ast_nodes import (
1415
+ SceneNode, SpriteNode, PhysicsNode, ColliderNode, AnimationNode,
1416
+ CameraNode, InputNode, SoundNode, ParticleNode, TimerNode,
1417
+ SpawnNode, HudNode, TweenNode, TilemapNode, TilemapLayerNode,
1418
+ BehaviorNode, UseNode, PrefabNode, InstanceNode, GroupNode,
1419
+ StateMachineNode, StateNode, TransitionNode,
1420
+ )
1421
+
1422
+
1423
+ class LLMMessageNode(QuantumNode):
1424
+ """
1425
+ Represents <q:message> inside <q:llm> for chat mode.
1426
+
1427
+ Examples:
1428
+ <q:message role="system">You are a helpful assistant</q:message>
1429
+ <q:message role="user">{userQuestion}</q:message>
1430
+ """
1431
+
1432
+ def __init__(self, role: str, content: str):
1433
+ self.role = role # "system", "user", "assistant"
1434
+ self.content = content
1435
+
1436
+ def to_dict(self) -> Dict[str, Any]:
1437
+ return {
1438
+ "type": "llm_message",
1439
+ "role": self.role,
1440
+ "content": self.content[:50] + '...' if len(self.content) > 50 else self.content
1441
+ }
1442
+
1443
+ def validate(self) -> List[str]:
1444
+ errors = []
1445
+ if self.role not in ['system', 'user', 'assistant']:
1446
+ errors.append(f"Invalid message role: {self.role}. Must be system, user, or assistant")
1447
+ if not self.content:
1448
+ errors.append("Message content is required")
1449
+ return errors
1450
+
1451
+ def __repr__(self):
1452
+ return f'<LLMMessageNode role={self.role}>'
1453
+
1454
+
1455
+ class LLMNode(QuantumNode):
1456
+ """
1457
+ Represents <q:llm> - LLM invocation via Ollama/compatible API.
1458
+
1459
+ Examples:
1460
+ <q:llm name="greeting" model="phi3">
1461
+ <q:prompt>Say hello to {userName}</q:prompt>
1462
+ </q:llm>
1463
+
1464
+ <q:llm name="chat" model="mistral">
1465
+ <q:message role="system">You are a helpful assistant</q:message>
1466
+ <q:message role="user">{question}</q:message>
1467
+ </q:llm>
1468
+
1469
+ <q:llm name="data" model="llama3" responseFormat="json">
1470
+ <q:prompt>Extract name and age from: {text}</q:prompt>
1471
+ </q:llm>
1472
+ """
1473
+
1474
+ def __init__(self, name: str):
1475
+ self.name = name # Result variable name
1476
+ self.model = None # e.g. "phi3", "mistral", "llama3"
1477
+ self.endpoint = None # Override base URL (optional)
1478
+ self.prompt = None # Prompt text (with databinding)
1479
+ self.system = None # System prompt (optional)
1480
+ self.messages: List[LLMMessageNode] = [] # Chat mode messages
1481
+ self.temperature = None # 0.0-2.0
1482
+ self.max_tokens = None # Token limit
1483
+ self.response_format = None # "text" or "json"
1484
+ self.cache = False
1485
+ self.ttl = None # Cache TTL in seconds (None = no expiry)
1486
+ self.provider = None # ollama, openai, anthropic, auto
1487
+ self.api_key = None # For cloud providers
1488
+ self.timeout = 30
1489
+
1490
+ def to_dict(self) -> Dict[str, Any]:
1491
+ return {
1492
+ "type": "llm",
1493
+ "name": self.name,
1494
+ "model": self.model,
1495
+ "endpoint": self.endpoint,
1496
+ "prompt": self.prompt[:50] + '...' if self.prompt and len(self.prompt) > 50 else self.prompt,
1497
+ "system": self.system,
1498
+ "messages": [m.to_dict() for m in self.messages],
1499
+ "temperature": self.temperature,
1500
+ "max_tokens": self.max_tokens,
1501
+ "response_format": self.response_format,
1502
+ "cache": self.cache,
1503
+ "timeout": self.timeout
1504
+ }
1505
+
1506
+ def validate(self) -> List[str]:
1507
+ errors = []
1508
+ if not self.name:
1509
+ errors.append("LLM name (result variable) is required")
1510
+ if not self.prompt and not self.messages:
1511
+ errors.append("LLM requires either a <q:prompt> or <q:message> children")
1512
+ for msg in self.messages:
1513
+ errors.extend(msg.validate())
1514
+ return errors
1515
+
1516
+ def __repr__(self):
1517
+ mode = "chat" if self.messages else "completion"
1518
+ return f'<LLMNode name={self.name} model={self.model} mode={mode}>'
1519
+
1520
+
1521
+ class TransactionNode(QuantumNode):
1522
+ """
1523
+ Represents database transaction (q:transaction).
1524
+
1525
+ Phase D: Database Backend
1526
+ Ensures atomic operations - all queries succeed or all rollback.
1527
+
1528
+ Examples:
1529
+ <q:transaction>
1530
+ <q:query>UPDATE accounts SET balance = balance - 100 WHERE id = 1</q:query>
1531
+ <q:query>UPDATE accounts SET balance = balance + 100 WHERE id = 2</q:query>
1532
+ </q:transaction>
1533
+ """
1534
+
1535
+ def __init__(
1536
+ self,
1537
+ isolation_level: str = "READ_COMMITTED"
1538
+ ):
1539
+ self.isolation_level = isolation_level # READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
1540
+ self.statements: List[QuantumNode] = [] # Queries and other statements inside transaction
1541
+
1542
+ def add_statement(self, statement: QuantumNode):
1543
+ """Add statement to transaction"""
1544
+ self.statements.append(statement)
1545
+
1546
+ def to_dict(self) -> Dict[str, Any]:
1547
+ return {
1548
+ "type": "transaction",
1549
+ "isolation_level": self.isolation_level,
1550
+ "statements": [s.to_dict() if hasattr(s, 'to_dict') else str(s) for s in self.statements]
1551
+ }
1552
+
1553
+ def validate(self) -> List[str]:
1554
+ errors = []
1555
+
1556
+ if not self.statements:
1557
+ errors.append("Transaction must contain at least one statement")
1558
+
1559
+ valid_levels = ['READ_UNCOMMITTED', 'READ_COMMITTED', 'REPEATABLE_READ', 'SERIALIZABLE']
1560
+ if self.isolation_level not in valid_levels:
1561
+ errors.append(f"Invalid isolation level: {self.isolation_level}")
1562
+
1563
+ return errors
1564
+
1565
+ def __repr__(self):
1566
+ return f'<TransactionNode isolation={self.isolation_level} statements={len(self.statements)}>'
1567
+
1568
+
1569
+ class InvokeHeaderNode(QuantumNode):
1570
+ """
1571
+ Represents an HTTP header within q:invoke.
1572
+
1573
+ Example:
1574
+ <q:invoke url="..." method="POST">
1575
+ <q:header name="Authorization" value="Bearer {token}" />
1576
+ </q:invoke>
1577
+ """
1578
+
1579
+ def __init__(self, name: str, value: str):
1580
+ self.name = name
1581
+ self.value = value
1582
+
1583
+ def to_dict(self) -> Dict[str, Any]:
1584
+ return {
1585
+ "type": "invoke_header",
1586
+ "name": self.name,
1587
+ "value": self.value
1588
+ }
1589
+
1590
+ def validate(self) -> List[str]:
1591
+ errors = []
1592
+ if not self.name:
1593
+ errors.append("Invoke header requires 'name' attribute")
1594
+ if self.value is None:
1595
+ errors.append(f"Invoke header '{self.name}' requires 'value' attribute")
1596
+ return errors
1597
+
1598
+ def __repr__(self):
1599
+ return f'<InvokeHeaderNode name={self.name}>'
1600
+
1601
+
1602
+ class HeaderNode(QuantumNode):
1603
+ """
1604
+ Represents an HTTP header for data sources (q:data).
1605
+
1606
+ Example:
1607
+ <q:data name="api_data" source="https://api.example.com/data" type="json">
1608
+ <q:header name="Authorization" value="Bearer {token}" />
1609
+ </q:data>
1610
+ """
1611
+
1612
+ def __init__(self, name: str, value: str):
1613
+ self.name = name
1614
+ self.value = value
1615
+
1616
+ def to_dict(self) -> Dict[str, Any]:
1617
+ return {
1618
+ "type": "header",
1619
+ "name": self.name,
1620
+ "value": self.value
1621
+ }
1622
+
1623
+ def validate(self) -> List[str]:
1624
+ errors = []
1625
+ if not self.name:
1626
+ errors.append("Header requires 'name' attribute")
1627
+ if self.value is None:
1628
+ errors.append(f"Header '{self.name}' requires 'value' attribute")
1629
+ return errors
1630
+
1631
+ def __repr__(self):
1632
+ return f'<HeaderNode name={self.name}>'
1633
+
1634
+
1635
+ class ColumnNode(QuantumNode):
1636
+ """
1637
+ Represents a column definition for CSV data import.
1638
+
1639
+ Example:
1640
+ <q:data name="users" source="users.csv" type="csv">
1641
+ <q:column name="id" type="integer" required="true" />
1642
+ <q:column name="email" type="string" validate="email" />
1643
+ <q:column name="age" type="integer" min="0" max="150" />
1644
+ </q:data>
1645
+ """
1646
+
1647
+ def __init__(self, name: str, col_type: str = 'string'):
1648
+ self.name = name
1649
+ self.col_type = col_type
1650
+ self.required: bool = False
1651
+ self.default: Optional[str] = None
1652
+ self.validate_rule: Optional[str] = None
1653
+ self.pattern: Optional[str] = None
1654
+ self.min: Optional[Union[int, float, str]] = None
1655
+ self.max: Optional[Union[int, float, str]] = None
1656
+ self.minlength: Optional[int] = None
1657
+ self.maxlength: Optional[int] = None
1658
+ self.range: Optional[str] = None
1659
+ self.enum: Optional[str] = None
1660
+
1661
+ def to_dict(self) -> Dict[str, Any]:
1662
+ return {
1663
+ "type": "column",
1664
+ "name": self.name,
1665
+ "col_type": self.col_type,
1666
+ "required": self.required,
1667
+ "default": self.default,
1668
+ "validate_rule": self.validate_rule,
1669
+ "pattern": self.pattern,
1670
+ "min": self.min,
1671
+ "max": self.max,
1672
+ "minlength": self.minlength,
1673
+ "maxlength": self.maxlength,
1674
+ "range": self.range,
1675
+ "enum": self.enum
1676
+ }
1677
+
1678
+ def validate(self) -> List[str]:
1679
+ errors = []
1680
+ if not self.name:
1681
+ errors.append("Column requires 'name' attribute")
1682
+ return errors
1683
+
1684
+ def __repr__(self):
1685
+ return f'<ColumnNode name={self.name} type={self.col_type}>'
1686
+
1687
+
1688
+ class FieldNode(QuantumNode):
1689
+ """
1690
+ Represents a field mapping for XML data import.
1691
+
1692
+ Example:
1693
+ <q:data name="catalog" source="products.xml" type="xml">
1694
+ <q:field name="id" xpath="//product/@id" type="integer" />
1695
+ <q:field name="name" xpath="//product/name/text()" />
1696
+ </q:data>
1697
+ """
1698
+
1699
+ def __init__(self, name: str, xpath: str, field_type: str = 'string'):
1700
+ self.name = name
1701
+ self.xpath = xpath
1702
+ self.field_type = field_type
1703
+
1704
+ def to_dict(self) -> Dict[str, Any]:
1705
+ return {
1706
+ "type": "field",
1707
+ "name": self.name,
1708
+ "xpath": self.xpath,
1709
+ "field_type": self.field_type
1710
+ }
1711
+
1712
+ def validate(self) -> List[str]:
1713
+ errors = []
1714
+ if not self.name:
1715
+ errors.append("Field requires 'name' attribute")
1716
+ if not self.xpath:
1717
+ errors.append("Field requires 'xpath' attribute")
1718
+ return errors
1719
+
1720
+ def __repr__(self):
1721
+ return f'<FieldNode name={self.name} xpath={self.xpath}>'
1722
+
1723
+
1724
+ class FilterNode(QuantumNode):
1725
+ """
1726
+ Represents a filter operation in data transformation.
1727
+
1728
+ Example:
1729
+ <q:transform>
1730
+ <q:filter condition="age >= 18" />
1731
+ </q:transform>
1732
+ """
1733
+
1734
+ def __init__(self, condition: str):
1735
+ self.condition = condition
1736
+
1737
+ def to_dict(self) -> Dict[str, Any]:
1738
+ return {
1739
+ "type": "filter",
1740
+ "condition": self.condition
1741
+ }
1742
+
1743
+ def validate(self) -> List[str]:
1744
+ errors = []
1745
+ if not self.condition:
1746
+ errors.append("Filter requires 'condition' attribute")
1747
+ return errors
1748
+
1749
+ def __repr__(self):
1750
+ return f'<FilterNode condition={self.condition}>'
1751
+
1752
+
1753
+ class SortNode(QuantumNode):
1754
+ """
1755
+ Represents a sort operation in data transformation.
1756
+
1757
+ Example:
1758
+ <q:transform>
1759
+ <q:sort by="name" order="asc" />
1760
+ </q:transform>
1761
+ """
1762
+
1763
+ def __init__(self, by: str, order: str = 'asc'):
1764
+ self.by = by
1765
+ self.order = order
1766
+
1767
+ def to_dict(self) -> Dict[str, Any]:
1768
+ return {
1769
+ "type": "sort",
1770
+ "by": self.by,
1771
+ "order": self.order
1772
+ }
1773
+
1774
+ def validate(self) -> List[str]:
1775
+ errors = []
1776
+ if not self.by:
1777
+ errors.append("Sort requires 'by' attribute")
1778
+ if self.order not in ('asc', 'desc'):
1779
+ errors.append(f"Sort order must be 'asc' or 'desc', got: {self.order}")
1780
+ return errors
1781
+
1782
+ def __repr__(self):
1783
+ return f'<SortNode by={self.by} order={self.order}>'
1784
+
1785
+
1786
+ class LimitNode(QuantumNode):
1787
+ """
1788
+ Represents a limit operation in data transformation.
1789
+
1790
+ Example:
1791
+ <q:transform>
1792
+ <q:limit value="10" />
1793
+ </q:transform>
1794
+ """
1795
+
1796
+ def __init__(self, value: int):
1797
+ self.value = value
1798
+
1799
+ def to_dict(self) -> Dict[str, Any]:
1800
+ return {
1801
+ "type": "limit",
1802
+ "value": self.value
1803
+ }
1804
+
1805
+ def validate(self) -> List[str]:
1806
+ errors = []
1807
+ if not isinstance(self.value, int) or self.value < 0:
1808
+ errors.append("Limit value must be a non-negative integer")
1809
+ return errors
1810
+
1811
+ def __repr__(self):
1812
+ return f'<LimitNode value={self.value}>'
1813
+
1814
+
1815
+ class ComputeNode(QuantumNode):
1816
+ """
1817
+ Represents a compute operation in data transformation.
1818
+
1819
+ Example:
1820
+ <q:transform>
1821
+ <q:compute field="full_name" expression="first_name + ' ' + last_name" />
1822
+ </q:transform>
1823
+ """
1824
+
1825
+ def __init__(self, field: str, expression: str, comp_type: str = 'string'):
1826
+ self.field = field
1827
+ self.expression = expression
1828
+ self.comp_type = comp_type
1829
+
1830
+ def to_dict(self) -> Dict[str, Any]:
1831
+ return {
1832
+ "type": "compute",
1833
+ "field": self.field,
1834
+ "expression": self.expression,
1835
+ "comp_type": self.comp_type
1836
+ }
1837
+
1838
+ def validate(self) -> List[str]:
1839
+ errors = []
1840
+ if not self.field:
1841
+ errors.append("Compute requires 'field' attribute")
1842
+ if not self.expression:
1843
+ errors.append("Compute requires 'expression' attribute")
1844
+ return errors
1845
+
1846
+ def __repr__(self):
1847
+ return f'<ComputeNode field={self.field}>'
1848
+
1849
+
1850
+ class TransformNode(QuantumNode):
1851
+ """
1852
+ Represents a container for data transformation operations.
1853
+
1854
+ Example:
1855
+ <q:data name="filtered_users" source="users.csv" type="csv">
1856
+ <q:transform>
1857
+ <q:filter condition="age >= 18" />
1858
+ <q:sort by="name" order="asc" />
1859
+ <q:limit value="100" />
1860
+ </q:transform>
1861
+ </q:data>
1862
+ """
1863
+
1864
+ def __init__(self):
1865
+ self.operations: List[QuantumNode] = []
1866
+
1867
+ def add_operation(self, operation: QuantumNode):
1868
+ """Add a transformation operation (filter, sort, limit, compute)"""
1869
+ self.operations.append(operation)
1870
+
1871
+ def to_dict(self) -> Dict[str, Any]:
1872
+ return {
1873
+ "type": "transform",
1874
+ "operations": [op.to_dict() for op in self.operations]
1875
+ }
1876
+
1877
+ def validate(self) -> List[str]:
1878
+ errors = []
1879
+ for op in self.operations:
1880
+ errors.extend(op.validate())
1881
+ return errors
1882
+
1883
+ def __repr__(self):
1884
+ return f'<TransformNode operations={len(self.operations)}>'
1885
+
1886
+
1887
+ class DataNode(QuantumNode):
1888
+ """
1889
+ Represents a data import statement (q:data).
1890
+
1891
+ Supports importing data from various sources:
1892
+ - CSV files
1893
+ - XML files
1894
+ - JSON APIs
1895
+ - Excel files
1896
+
1897
+ Example:
1898
+ <q:data name="users" source="users.csv" type="csv" delimiter="," header="true">
1899
+ <q:column name="id" type="integer" />
1900
+ <q:column name="name" type="string" />
1901
+ <q:transform>
1902
+ <q:filter condition="id > 0" />
1903
+ </q:transform>
1904
+ </q:data>
1905
+ """
1906
+
1907
+ def __init__(self, name: str, source: str, data_type: str = 'csv'):
1908
+ self.name = name
1909
+ self.source = source
1910
+ self.data_type = data_type
1911
+
1912
+ # Caching options
1913
+ self.cache: bool = True
1914
+ self.ttl: Optional[int] = None
1915
+
1916
+ # CSV-specific options
1917
+ self.delimiter: str = ','
1918
+ self.quote: str = '"'
1919
+ self.header: bool = True
1920
+ self.encoding: str = 'utf-8'
1921
+ self.skip_rows: int = 0
1922
+
1923
+ # XML-specific options
1924
+ self.xpath: Optional[str] = None
1925
+ self.namespace: Optional[str] = None
1926
+
1927
+ # Result metadata
1928
+ self.result: Optional[str] = None
1929
+ self.on_error: str = 'fail' # DATA-4: 'fail' or 'continue'
1930
+
1931
+ # Child elements
1932
+ self.columns: List[ColumnNode] = []
1933
+ self.fields: List[FieldNode] = []
1934
+ self.transforms: List[TransformNode] = []
1935
+ self.headers: List[HeaderNode] = []
1936
+
1937
+ def add_column(self, column: 'ColumnNode'):
1938
+ """Add a column definition (for CSV)"""
1939
+ self.columns.append(column)
1940
+
1941
+ def add_field(self, field: 'FieldNode'):
1942
+ """Add a field mapping (for XML)"""
1943
+ self.fields.append(field)
1944
+
1945
+ def add_transform(self, transform: 'TransformNode'):
1946
+ """Add a transform container"""
1947
+ self.transforms.append(transform)
1948
+
1949
+ def add_header(self, header: 'HeaderNode'):
1950
+ """Add an HTTP header (for remote sources)"""
1951
+ self.headers.append(header)
1952
+
1953
+ def to_dict(self) -> Dict[str, Any]:
1954
+ return {
1955
+ "type": "data",
1956
+ "name": self.name,
1957
+ "source": self.source,
1958
+ "data_type": self.data_type,
1959
+ "cache": self.cache,
1960
+ "ttl": self.ttl,
1961
+ "delimiter": self.delimiter,
1962
+ "quote": self.quote,
1963
+ "header": self.header,
1964
+ "encoding": self.encoding,
1965
+ "skip_rows": self.skip_rows,
1966
+ "xpath": self.xpath,
1967
+ "namespace": self.namespace,
1968
+ "result": self.result,
1969
+ "columns": [c.to_dict() for c in self.columns],
1970
+ "fields": [f.to_dict() for f in self.fields],
1971
+ "transforms": [t.to_dict() for t in self.transforms],
1972
+ "headers": [h.to_dict() for h in self.headers]
1973
+ }
1974
+
1975
+ def validate(self) -> List[str]:
1976
+ errors = []
1977
+ if not self.name:
1978
+ errors.append("Data requires 'name' attribute")
1979
+ if not self.source:
1980
+ errors.append("Data requires 'source' attribute")
1981
+
1982
+ for col in self.columns:
1983
+ errors.extend(col.validate())
1984
+ for field in self.fields:
1985
+ errors.extend(field.validate())
1986
+ for transform in self.transforms:
1987
+ errors.extend(transform.validate())
1988
+ for header in self.headers:
1989
+ errors.extend(header.validate())
1990
+
1991
+ return errors
1992
+
1993
+ def __repr__(self):
1994
+ return f'<DataNode name={self.name} source={self.source} type={self.data_type}>'
1995
+
1996
+
1997
+ # ============================================
1998
+ # MESSAGE QUEUE NODES (Phase: Message Queue System)
1999
+ # ============================================
2000
+
2001
+ @dataclass
2002
+ class MessageHeaderNode(QuantumNode):
2003
+ """
2004
+ Represents a message header for q:message.
2005
+
2006
+ Example:
2007
+ <q:message topic="orders.created">
2008
+ <q:header name="priority" value="high" />
2009
+ <q:header name="correlation-id" value="{requestId}" />
2010
+ </q:message>
2011
+ """
2012
+ name: str = ""
2013
+ value: str = ""
2014
+
2015
+ def to_dict(self) -> Dict[str, Any]:
2016
+ return {
2017
+ "type": "message_header",
2018
+ "name": self.name,
2019
+ "value": self.value
2020
+ }
2021
+
2022
+ def validate(self) -> List[str]:
2023
+ errors = []
2024
+ if not self.name:
2025
+ errors.append("Message header requires 'name' attribute")
2026
+ return errors
2027
+
2028
+ def __repr__(self):
2029
+ return f'<MessageHeaderNode name={self.name}>'
2030
+
2031
+
2032
+ @dataclass
2033
+ class MessageNode(QuantumNode):
2034
+ """
2035
+ Represents a message publish/send operation (q:message).
2036
+
2037
+ Supports three modes:
2038
+ - publish: Pub/Sub to a topic (fanout)
2039
+ - send: Direct queue send (point-to-point)
2040
+ - request: Request/reply pattern with timeout
2041
+
2042
+ Examples:
2043
+ <!-- Publish to topic -->
2044
+ <q:message name="result" topic="orders.created" type="publish">
2045
+ <q:body>{orderData}</q:body>
2046
+ </q:message>
2047
+
2048
+ <!-- Send to queue -->
2049
+ <q:message name="taskId" queue="email-queue" type="send">
2050
+ <q:header name="priority" value="high" />
2051
+ <q:body>{"to": "{email}", "subject": "Welcome!"}</q:body>
2052
+ </q:message>
2053
+
2054
+ <!-- Request/reply -->
2055
+ <q:message name="response" queue="calculator" type="request" timeout="5000">
2056
+ <q:body>{"operation": "add", "a": 5, "b": 3}</q:body>
2057
+ </q:message>
2058
+ """
2059
+ name: Optional[str] = None # result variable name
2060
+ topic: Optional[str] = None # for pub/sub
2061
+ queue: Optional[str] = None # for direct queue
2062
+ type: str = 'publish' # publish, send, request
2063
+ timeout: Optional[str] = None # for request type (ms)
2064
+ headers: List['MessageHeaderNode'] = field(default_factory=list)
2065
+ body: Optional[str] = None
2066
+
2067
+ def add_header(self, header: 'MessageHeaderNode'):
2068
+ """Add a header to the message"""
2069
+ self.headers.append(header)
2070
+
2071
+ def to_dict(self) -> Dict[str, Any]:
2072
+ return {
2073
+ "type": "message",
2074
+ "name": self.name,
2075
+ "topic": self.topic,
2076
+ "queue": self.queue,
2077
+ "message_type": self.type,
2078
+ "timeout": self.timeout,
2079
+ "headers": [h.to_dict() for h in self.headers],
2080
+ "body": self.body[:50] + '...' if self.body and len(self.body) > 50 else self.body
2081
+ }
2082
+
2083
+ def validate(self) -> List[str]:
2084
+ errors = []
2085
+
2086
+ if not self.topic and not self.queue:
2087
+ errors.append("Message requires either 'topic' or 'queue' attribute")
2088
+
2089
+ if self.topic and self.queue:
2090
+ errors.append("Message cannot have both 'topic' and 'queue' attributes")
2091
+
2092
+ if self.type not in ['publish', 'send', 'request']:
2093
+ errors.append(f"Invalid message type: {self.type}. Must be publish, send, or request")
2094
+
2095
+ if self.type == 'request' and not self.timeout:
2096
+ # Default timeout for request type
2097
+ pass # Will use default in runtime
2098
+
2099
+ for header in self.headers:
2100
+ errors.extend(header.validate())
2101
+
2102
+ return errors
2103
+
2104
+ def __repr__(self):
2105
+ target = self.topic if self.topic else self.queue
2106
+ return f'<MessageNode type={self.type} target={target}>'
2107
+
2108
+
2109
+ @dataclass
2110
+ class SubscribeNode(QuantumNode):
2111
+ """
2112
+ Represents a subscription to topics or queue consumption (q:subscribe).
2113
+
2114
+ Examples:
2115
+ <!-- Subscribe to topic -->
2116
+ <q:subscribe name="orderHandler" topic="orders.*" ack="auto">
2117
+ <q:onMessage>
2118
+ <q:set name="order" value="{message.body}" />
2119
+ <q:log message="Received order: {order.id}" />
2120
+ </q:onMessage>
2121
+ <q:onError>
2122
+ <q:log level="error" message="Failed to process: {error}" />
2123
+ </q:onError>
2124
+ </q:subscribe>
2125
+
2126
+ <!-- Consume from queue with manual ack -->
2127
+ <q:subscribe name="emailWorker" queue="email-queue" ack="manual" prefetch="10">
2128
+ <q:onMessage>
2129
+ <q:mail to="{message.body.to}" subject="{message.body.subject}" />
2130
+ <q:messageAck />
2131
+ </q:onMessage>
2132
+ </q:subscribe>
2133
+ """
2134
+ name: str = ""
2135
+ topic: Optional[str] = None # single topic
2136
+ topics: Optional[str] = None # comma-separated or pattern
2137
+ queue: Optional[str] = None # for queue consumption
2138
+ ack: str = 'auto' # auto, manual
2139
+ prefetch: int = 1 # prefetch count
2140
+ on_message: List['QuantumNode'] = field(default_factory=list)
2141
+ on_error: List['QuantumNode'] = field(default_factory=list)
2142
+
2143
+ def add_on_message_statement(self, statement: 'QuantumNode'):
2144
+ """Add statement to on_message handler"""
2145
+ self.on_message.append(statement)
2146
+
2147
+ def add_on_error_statement(self, statement: 'QuantumNode'):
2148
+ """Add statement to on_error handler"""
2149
+ self.on_error.append(statement)
2150
+
2151
+ def to_dict(self) -> Dict[str, Any]:
2152
+ return {
2153
+ "type": "subscribe",
2154
+ "name": self.name,
2155
+ "topic": self.topic,
2156
+ "topics": self.topics,
2157
+ "queue": self.queue,
2158
+ "ack": self.ack,
2159
+ "prefetch": self.prefetch,
2160
+ "on_message_count": len(self.on_message),
2161
+ "on_error_count": len(self.on_error)
2162
+ }
2163
+
2164
+ def validate(self) -> List[str]:
2165
+ errors = []
2166
+
2167
+ if not self.name:
2168
+ errors.append("Subscribe requires 'name' attribute")
2169
+
2170
+ if not self.topic and not self.topics and not self.queue:
2171
+ errors.append("Subscribe requires 'topic', 'topics', or 'queue' attribute")
2172
+
2173
+ if self.ack not in ['auto', 'manual']:
2174
+ errors.append(f"Invalid ack mode: {self.ack}. Must be 'auto' or 'manual'")
2175
+
2176
+ if self.prefetch < 1:
2177
+ errors.append(f"Prefetch must be at least 1 (got {self.prefetch})")
2178
+
2179
+ for statement in self.on_message:
2180
+ if hasattr(statement, 'validate'):
2181
+ errors.extend(statement.validate())
2182
+
2183
+ for statement in self.on_error:
2184
+ if hasattr(statement, 'validate'):
2185
+ errors.extend(statement.validate())
2186
+
2187
+ return errors
2188
+
2189
+ def __repr__(self):
2190
+ target = self.topic or self.topics or self.queue
2191
+ return f'<SubscribeNode name={self.name} target={target}>'
2192
+
2193
+
2194
+ @dataclass
2195
+ class QueueNode(QuantumNode):
2196
+ """
2197
+ Represents queue/exchange declaration and management (q:queue).
2198
+
2199
+ Actions:
2200
+ - declare: Create queue/exchange if not exists
2201
+ - purge: Remove all messages from queue
2202
+ - delete: Delete the queue
2203
+ - info: Get queue information (message count, consumers, etc.)
2204
+
2205
+ Examples:
2206
+ <!-- Declare a durable queue -->
2207
+ <q:queue name="email-queue" action="declare" durable="true" />
2208
+
2209
+ <!-- Declare with dead letter queue -->
2210
+ <q:queue name="orders" action="declare" durable="true"
2211
+ deadLetterQueue="orders-dlq" ttl="86400000" />
2212
+
2213
+ <!-- Purge messages -->
2214
+ <q:queue name="temp-queue" action="purge" />
2215
+
2216
+ <!-- Get queue info -->
2217
+ <q:queue name="orders" action="info" result="queueInfo" />
2218
+ """
2219
+ name: str = ""
2220
+ action: str = 'declare' # declare, purge, delete, info
2221
+ durable: bool = True
2222
+ exclusive: bool = False
2223
+ auto_delete: bool = False
2224
+ dead_letter_queue: Optional[str] = None
2225
+ ttl: Optional[int] = None # message TTL in ms
2226
+ result: Optional[str] = None # for info action
2227
+
2228
+ def to_dict(self) -> Dict[str, Any]:
2229
+ return {
2230
+ "type": "queue",
2231
+ "name": self.name,
2232
+ "action": self.action,
2233
+ "durable": self.durable,
2234
+ "exclusive": self.exclusive,
2235
+ "auto_delete": self.auto_delete,
2236
+ "dead_letter_queue": self.dead_letter_queue,
2237
+ "ttl": self.ttl,
2238
+ "result": self.result
2239
+ }
2240
+
2241
+ def validate(self) -> List[str]:
2242
+ errors = []
2243
+
2244
+ if not self.name:
2245
+ errors.append("Queue requires 'name' attribute")
2246
+
2247
+ if self.action not in ['declare', 'purge', 'delete', 'info']:
2248
+ errors.append(f"Invalid queue action: {self.action}. Must be declare, purge, delete, or info")
2249
+
2250
+ if self.action == 'info' and not self.result:
2251
+ errors.append("Queue 'info' action requires 'result' attribute")
2252
+
2253
+ if self.ttl is not None and self.ttl < 0:
2254
+ errors.append(f"TTL must be non-negative (got {self.ttl})")
2255
+
2256
+ return errors
2257
+
2258
+ def __repr__(self):
2259
+ return f'<QueueNode name={self.name} action={self.action}>'
2260
+
2261
+
2262
+ @dataclass
2263
+ class MessageAckNode(QuantumNode):
2264
+ """
2265
+ Represents message acknowledgment (q:messageAck).
2266
+
2267
+ Used inside q:subscribe with ack="manual" to acknowledge
2268
+ successful message processing.
2269
+
2270
+ Example:
2271
+ <q:subscribe name="worker" queue="tasks" ack="manual">
2272
+ <q:onMessage>
2273
+ <!-- Process message -->
2274
+ <q:set name="processed" value="true" />
2275
+ <!-- Acknowledge on success -->
2276
+ <q:messageAck />
2277
+ </q:onMessage>
2278
+ </q:subscribe>
2279
+ """
2280
+
2281
+ def to_dict(self) -> Dict[str, Any]:
2282
+ return {
2283
+ "type": "message_ack"
2284
+ }
2285
+
2286
+ def validate(self) -> List[str]:
2287
+ return []
2288
+
2289
+ def __repr__(self):
2290
+ return '<MessageAckNode>'
2291
+
2292
+
2293
+ @dataclass
2294
+ class MessageNackNode(QuantumNode):
2295
+ """
2296
+ Represents negative message acknowledgment (q:messageNack).
2297
+
2298
+ Used inside q:subscribe with ack="manual" to reject a message.
2299
+ The requeue attribute controls whether the message is requeued.
2300
+
2301
+ Examples:
2302
+ <!-- Reject and requeue (for retry) -->
2303
+ <q:messageNack requeue="true" />
2304
+
2305
+ <!-- Reject without requeue (send to DLQ if configured) -->
2306
+ <q:messageNack requeue="false" />
2307
+ """
2308
+ requeue: bool = True
2309
+
2310
+ def to_dict(self) -> Dict[str, Any]:
2311
+ return {
2312
+ "type": "message_nack",
2313
+ "requeue": self.requeue
2314
+ }
2315
+
2316
+ def validate(self) -> List[str]:
2317
+ return []
2318
+
2319
+ def __repr__(self):
2320
+ return f'<MessageNackNode requeue={self.requeue}>'
2321
+
2322
+
2323
+ # =============================================================================
2324
+ # Python Scripting Nodes (q:python, q:pyimport, q:class, q:decorator)
2325
+ # =============================================================================
2326
+
2327
+ @dataclass
2328
+ class PythonNode(QuantumNode):
2329
+ """
2330
+ Represents a Python code block (q:python).
2331
+
2332
+ Allows embedding native Python code within Quantum components.
2333
+ The 'q' bridge object provides access to the Quantum context.
2334
+
2335
+ Attributes:
2336
+ code: The Python code to execute
2337
+ scope: Variable scope ('component', 'isolated', 'module')
2338
+ async_mode: Whether to execute as async code
2339
+ timeout: Maximum execution time (e.g., '30s')
2340
+ result: Variable name to store the result
2341
+
2342
+ Examples:
2343
+ <!-- Simple Python block -->
2344
+ <q:python>
2345
+ import pandas as pd
2346
+ df = pd.read_csv('data.csv')
2347
+ q.total = df['sales'].sum()
2348
+ </q:python>
2349
+
2350
+ <!-- Async execution -->
2351
+ <q:python async="true" timeout="30s">
2352
+ import aiohttp
2353
+ async with aiohttp.ClientSession() as session:
2354
+ response = await session.get(url)
2355
+ q.data = await response.json()
2356
+ </q:python>
2357
+
2358
+ <!-- Isolated scope (variables don't leak) -->
2359
+ <q:python scope="isolated">
2360
+ temp = calculate_something()
2361
+ q.export('result', temp)
2362
+ </q:python>
2363
+
2364
+ <!-- With result capture -->
2365
+ <q:python result="calculation">
2366
+ return sum([1, 2, 3, 4, 5])
2367
+ </q:python>
2368
+ """
2369
+ code: str
2370
+ scope: str = "component" # component, isolated, module
2371
+ async_mode: bool = False
2372
+ timeout: Optional[str] = None
2373
+ result: Optional[str] = None
2374
+
2375
+ def to_dict(self) -> Dict[str, Any]:
2376
+ return {
2377
+ "type": "python",
2378
+ "code": self.code,
2379
+ "scope": self.scope,
2380
+ "async": self.async_mode,
2381
+ "timeout": self.timeout,
2382
+ "result": self.result
2383
+ }
2384
+
2385
+ def validate(self) -> List[str]:
2386
+ errors = []
2387
+ if not self.code or not self.code.strip():
2388
+ errors.append("Python code block cannot be empty")
2389
+ if self.scope not in ('component', 'isolated', 'module'):
2390
+ errors.append(f"Invalid scope: {self.scope}. Must be 'component', 'isolated', or 'module'")
2391
+ return errors
2392
+
2393
+ def __repr__(self):
2394
+ code_preview = self.code[:50] + '...' if len(self.code) > 50 else self.code
2395
+ return f'<PythonNode scope={self.scope} code="{code_preview}">'
2396
+
2397
+
2398
+ @dataclass
2399
+ class PyImportNode(QuantumNode):
2400
+ """
2401
+ Represents a Python module import (q:pyimport).
2402
+
2403
+ Imports Python modules into the component's namespace for use
2404
+ in q:python blocks and expressions.
2405
+
2406
+ Attributes:
2407
+ module: The module to import (e.g., 'pandas', 'sklearn.ensemble')
2408
+ alias: Optional alias (e.g., 'pd' for pandas)
2409
+ names: Specific names to import from the module
2410
+
2411
+ Examples:
2412
+ <!-- Import with alias -->
2413
+ <q:pyimport module="pandas" as="pd" />
2414
+ <q:pyimport module="numpy" as="np" />
2415
+
2416
+ <!-- Import specific names -->
2417
+ <q:pyimport module="sklearn.ensemble" names="RandomForestClassifier, GradientBoostingClassifier" />
2418
+
2419
+ <!-- Import everything from module -->
2420
+ <q:pyimport module="math" names="*" />
2421
+
2422
+ <!-- Simple import -->
2423
+ <q:pyimport module="json" />
2424
+ """
2425
+ module: str
2426
+ alias: Optional[str] = None
2427
+ names: List[str] = field(default_factory=list)
2428
+
2429
+ def to_dict(self) -> Dict[str, Any]:
2430
+ return {
2431
+ "type": "pyimport",
2432
+ "module": self.module,
2433
+ "alias": self.alias,
2434
+ "names": self.names
2435
+ }
2436
+
2437
+ def validate(self) -> List[str]:
2438
+ errors = []
2439
+ if not self.module:
2440
+ errors.append("Module name is required for q:pyimport")
2441
+ if self.alias and self.names:
2442
+ errors.append("Cannot use both 'as' and 'names' in the same import")
2443
+ return errors
2444
+
2445
+ def __repr__(self):
2446
+ if self.alias:
2447
+ return f'<PyImportNode module={self.module} as={self.alias}>'
2448
+ elif self.names:
2449
+ return f'<PyImportNode from={self.module} names={self.names}>'
2450
+ return f'<PyImportNode module={self.module}>'
2451
+
2452
+
2453
+ @dataclass
2454
+ class PyClassNode(QuantumNode):
2455
+ """
2456
+ Represents an inline Python class definition (q:class).
2457
+
2458
+ Allows defining Python classes within Quantum components.
2459
+
2460
+ Attributes:
2461
+ name: The class name
2462
+ code: The class body (methods, attributes)
2463
+ bases: Base classes to inherit from
2464
+ decorators: Decorators to apply to the class
2465
+
2466
+ Examples:
2467
+ <q:class name="OrderProcessor">
2468
+ def __init__(self, db):
2469
+ self.db = db
2470
+
2471
+ def process(self, order_id):
2472
+ order = self.db.get(order_id)
2473
+ return self.validate(order)
2474
+
2475
+ def validate(self, order):
2476
+ return order.total > 0
2477
+ </q:class>
2478
+
2479
+ <!-- With inheritance -->
2480
+ <q:class name="AdminUser" bases="User, Permissions">
2481
+ role = 'admin'
2482
+
2483
+ def has_permission(self, perm):
2484
+ return True
2485
+ </q:class>
2486
+ """
2487
+ name: str
2488
+ code: str
2489
+ bases: List[str] = field(default_factory=list)
2490
+ decorators: List[str] = field(default_factory=list)
2491
+
2492
+ def to_dict(self) -> Dict[str, Any]:
2493
+ return {
2494
+ "type": "pyclass",
2495
+ "name": self.name,
2496
+ "code": self.code,
2497
+ "bases": self.bases,
2498
+ "decorators": self.decorators
2499
+ }
2500
+
2501
+ def validate(self) -> List[str]:
2502
+ errors = []
2503
+ if not self.name:
2504
+ errors.append("Class name is required for q:class")
2505
+ if not self.name.isidentifier():
2506
+ errors.append(f"Invalid class name: {self.name}")
2507
+ return errors
2508
+
2509
+ def __repr__(self):
2510
+ bases_str = f"({', '.join(self.bases)})" if self.bases else ""
2511
+ return f'<PyClassNode name={self.name}{bases_str}>'
2512
+
2513
+
2514
+ @dataclass
2515
+ class PyDecoratorNode(QuantumNode):
2516
+ """
2517
+ Represents a Python decorator definition (q:decorator).
2518
+
2519
+ Allows defining reusable decorators for functions.
2520
+
2521
+ Attributes:
2522
+ name: The decorator name
2523
+ code: The decorator implementation
2524
+ params: Parameters the decorator accepts
2525
+
2526
+ Examples:
2527
+ <!-- Caching decorator -->
2528
+ <q:decorator name="cached" params="ttl">
2529
+ from functools import wraps
2530
+ def decorator(func):
2531
+ cache = {}
2532
+ @wraps(func)
2533
+ def wrapper(*args):
2534
+ if args in cache:
2535
+ return cache[args]
2536
+ result = func(*args)
2537
+ cache[args] = result
2538
+ return result
2539
+ return wrapper
2540
+ return decorator
2541
+ </q:decorator>
2542
+
2543
+ <!-- Logging decorator -->
2544
+ <q:decorator name="logged">
2545
+ import logging
2546
+ def decorator(func):
2547
+ def wrapper(*args, **kwargs):
2548
+ logging.info(f"Calling {func.__name__}")
2549
+ return func(*args, **kwargs)
2550
+ return wrapper
2551
+ return decorator
2552
+ </q:decorator>
2553
+ """
2554
+ name: str
2555
+ code: str
2556
+ params: List[str] = field(default_factory=list)
2557
+
2558
+ def to_dict(self) -> Dict[str, Any]:
2559
+ return {
2560
+ "type": "pydecorator",
2561
+ "name": self.name,
2562
+ "code": self.code,
2563
+ "params": self.params
2564
+ }
2565
+
2566
+ def validate(self) -> List[str]:
2567
+ errors = []
2568
+ if not self.name:
2569
+ errors.append("Decorator name is required")
2570
+ if not self.name.isidentifier():
2571
+ errors.append(f"Invalid decorator name: {self.name}")
2572
+ return errors
2573
+
2574
+ def __repr__(self):
2575
+ return f'<PyDecoratorNode name={self.name}>'
2576
+
2577
+
2578
+ @dataclass
2579
+ class PyExprNode(QuantumNode):
2580
+ """
2581
+ Represents an inline Python expression (q:expr or {py: ...}).
2582
+
2583
+ Used for embedding Python expressions within templates and attributes.
2584
+
2585
+ Attributes:
2586
+ expr: The Python expression to evaluate
2587
+ format_spec: Optional format specification
2588
+
2589
+ Examples:
2590
+ <!-- In attributes -->
2591
+ <q:set name="total" value="{py: sum(items)}" />
2592
+
2593
+ <!-- With formatting -->
2594
+ <span>{py: price * 1.1:.2f}</span>
2595
+
2596
+ <!-- List comprehension -->
2597
+ <q:set name="adults" value="{py: [u for u in users if u.age >= 18]}" />
2598
+
2599
+ <!-- Ternary expression -->
2600
+ <span class="{py: 'active' if is_active else 'inactive'}">Status</span>
2601
+ """
2602
+ expr: str
2603
+ format_spec: Optional[str] = None
2604
+
2605
+ def to_dict(self) -> Dict[str, Any]:
2606
+ return {
2607
+ "type": "pyexpr",
2608
+ "expr": self.expr,
2609
+ "format_spec": self.format_spec
2610
+ }
2611
+
2612
+ def validate(self) -> List[str]:
2613
+ errors = []
2614
+ if not self.expr or not self.expr.strip():
2615
+ errors.append("Python expression cannot be empty")
2616
+ return errors
2617
+
2618
+ def __repr__(self):
2619
+ return f'<PyExprNode expr="{self.expr}">'