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