quantum-framework 0.17.0__tar.gz → 0.19.0__tar.gz

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 (373) hide show
  1. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/PKG-INFO +1 -1
  2. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/pyproject.toml +1 -1
  3. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/check.py +23 -2
  4. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/runner.py +15 -1
  5. quantum_framework-0.19.0/quantum/cli/schema_plan.py +273 -0
  6. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/ast_nodes.py +6 -0
  7. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/ast_nodes.py +45 -1
  8. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/parser.py +55 -1
  9. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/query_parser.py +21 -1
  10. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/action_parser.py +15 -0
  11. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/action_handler.py +85 -22
  12. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component.py +5 -0
  13. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/database_service.py +12 -0
  14. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/query_executor.py +38 -4
  15. quantum_framework-0.19.0/quantum/runtime/history.py +203 -0
  16. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/renderer.py +2 -0
  17. quantum_framework-0.19.0/quantum/runtime/table_params.py +163 -0
  18. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_console.py +48 -3
  19. quantum_framework-0.19.0/quantum/runtime/ui_form_rules.py +193 -0
  20. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_html_adapter.py +1 -1
  21. quantum_framework-0.19.0/quantum/runtime/ui_page_renderer.py +423 -0
  22. quantum_framework-0.19.0/quantum/runtime/ui_pager.py +73 -0
  23. quantum_framework-0.19.0/quantum/runtime/ui_table_edit.py +139 -0
  24. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_view.py +84 -10
  25. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/web_server.py +92 -1
  26. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/PKG-INFO +1 -1
  27. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/SOURCES.txt +6 -0
  28. quantum_framework-0.17.0/quantum/runtime/ui_page_renderer.py +0 -232
  29. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/LICENSE +0 -0
  30. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/README.md +0 -0
  31. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/__init__.py +0 -0
  32. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/__init__.py +0 -0
  33. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/admin.py +0 -0
  34. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/__init__.py +0 -0
  35. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/build.py +0 -0
  36. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/dev.py +0 -0
  37. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/docs.py +0 -0
  38. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/lint.py +0 -0
  39. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/migrate.py +0 -0
  40. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/new.py +0 -0
  41. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/serve.py +0 -0
  42. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/commands/test.py +0 -0
  43. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/deploy.py +0 -0
  44. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/hot_reload.py +0 -0
  45. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/jobs.py +0 -0
  46. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/mq.py +0 -0
  47. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/pkg.py +0 -0
  48. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/server_process.py +0 -0
  49. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/cli/utils.py +0 -0
  50. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/__init__.py +0 -0
  51. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/base_generator.py +0 -0
  52. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/cli.py +0 -0
  53. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/expression_transformer.py +0 -0
  54. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/javascript/__init__.py +0 -0
  55. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/javascript/generator.py +0 -0
  56. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/optimizer.py +0 -0
  57. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/__init__.py +0 -0
  58. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/generator.py +0 -0
  59. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/python/runtime.py +0 -0
  60. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/compiler/transpiler.py +0 -0
  61. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/__init__.py +0 -0
  62. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/config_env.py +0 -0
  63. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/docs_links.py +0 -0
  64. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expression_diagnostics.py +0 -0
  65. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expression_stdlib.py +0 -0
  66. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/expressions.py +0 -0
  67. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/agents/src/__init__.py +0 -0
  68. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/agents/src/ast_node.py +0 -0
  69. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
  70. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
  71. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/__init__.py +0 -0
  72. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
  73. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/data_import/src/runtime.py +0 -0
  74. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/__init__.py +0 -0
  75. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/ast_node.py +0 -0
  76. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/parser.py +0 -0
  77. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/dump/src/runtime.py +0 -0
  78. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/functions/src/__init__.py +0 -0
  79. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/functions/src/ast_node.py +0 -0
  80. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
  81. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
  82. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
  83. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/__init__.py +0 -0
  84. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
  85. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/invocation/src/runtime.py +0 -0
  86. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
  87. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
  88. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
  89. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/__init__.py +0 -0
  90. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/ast_node.py +0 -0
  91. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/parser.py +0 -0
  92. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/logging/src/runtime.py +0 -0
  93. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/loops/src/__init__.py +0 -0
  94. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/loops/src/ast_node.py +0 -0
  95. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/query/src/__init__.py +0 -0
  96. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/query/src/query_validators.py +0 -0
  97. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/state_management/src/__init__.py +0 -0
  98. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
  99. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
  100. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
  101. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
  102. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
  103. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
  104. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
  105. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/__init__.py +0 -0
  106. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/ast_node.py +0 -0
  107. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/theming/src/presets.py +0 -0
  108. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
  109. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/websocket/src/__init__.py +0 -0
  110. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
  111. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/guards.py +0 -0
  112. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/html_compat.py +0 -0
  113. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parser.py +0 -0
  114. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parser_registry.py +0 -0
  115. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/__init__.py +0 -0
  116. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/__init__.py +0 -0
  117. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
  118. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
  119. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
  120. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/ai/team_parser.py +0 -0
  121. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/base.py +0 -0
  122. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/__init__.py +0 -0
  123. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/import_parser.py +0 -0
  124. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
  125. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
  126. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
  127. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
  128. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
  129. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/__init__.py +0 -0
  130. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/data_parser.py +0 -0
  131. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
  132. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
  133. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/events/__init__.py +0 -0
  134. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
  135. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/__init__.py +0 -0
  136. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
  137. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
  138. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/__init__.py +0 -0
  139. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/function_parser.py +0 -0
  140. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/param_parser.py +0 -0
  141. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/functions/return_parser.py +0 -0
  142. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/__init__.py +0 -0
  143. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
  144. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/html/html_parser.py +0 -0
  145. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/__init__.py +0 -0
  146. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
  147. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
  148. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
  149. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/__init__.py +0 -0
  150. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
  151. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
  152. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
  153. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
  154. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
  155. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/routing/__init__.py +0 -0
  156. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/routing/route_parser.py +0 -0
  157. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/__init__.py +0 -0
  158. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
  159. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
  160. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
  161. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
  162. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/__init__.py +0 -0
  163. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/dump_parser.py +0 -0
  164. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/file_parser.py +0 -0
  165. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/log_parser.py +0 -0
  166. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/parsers/services/mail_parser.py +0 -0
  167. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/tiers.py +0 -0
  168. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/core/xml_lines.py +0 -0
  169. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/__init__.py +0 -0
  170. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/manager.py +0 -0
  171. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/manifest.py +0 -0
  172. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/registry.py +0 -0
  173. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/packages/resolver.py +0 -0
  174. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/__init__.py +0 -0
  175. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/hooks.py +0 -0
  176. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/loader.py +0 -0
  177. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/manifest.py +0 -0
  178. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/plugins/registry.py +0 -0
  179. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/__init__.py +0 -0
  180. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/__init__.py +0 -0
  181. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
  182. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
  183. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
  184. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
  185. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/agent_service.py +0 -0
  186. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ast_cache.py +0 -0
  187. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/auth_service.py +0 -0
  188. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component_composer.py +0 -0
  189. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/component_resolver.py +0 -0
  190. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/conditions.py +0 -0
  191. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/db_schema.py +0 -0
  192. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/dev_panel.py +0 -0
  193. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/email_service.py +0 -0
  194. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/error_handler.py +0 -0
  195. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/execution_context.py +0 -0
  196. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executor_registry.py +0 -0
  197. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/__init__.py +0 -0
  198. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/__init__.py +0 -0
  199. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
  200. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
  201. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
  202. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
  203. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/base.py +0 -0
  204. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
  205. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
  206. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
  207. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
  208. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
  209. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/__init__.py +0 -0
  210. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/data_executor.py +0 -0
  211. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
  212. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
  213. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
  214. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
  215. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
  216. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
  217. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
  218. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
  219. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
  220. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
  221. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
  222. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
  223. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
  224. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
  225. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
  226. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/__init__.py +0 -0
  227. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
  228. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/file_executor.py +0 -0
  229. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/log_executor.py +0 -0
  230. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
  231. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/expression_cache.py +0 -0
  232. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/file_upload_service.py +0 -0
  233. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/function_registry.py +0 -0
  234. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_builder.py +0 -0
  235. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_code_generator.py +0 -0
  236. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/game_templates.py +0 -0
  237. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/godot_code_generator.py +0 -0
  238. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/godot_templates.py +0 -0
  239. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/job_executor.py +0 -0
  240. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/knowledge_service.py +0 -0
  241. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_cache.py +0 -0
  242. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_providers.py +0 -0
  243. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/llm_service.py +0 -0
  244. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/local_server.py +0 -0
  245. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/logging_setup.py +0 -0
  246. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/message_broker.py +0 -0
  247. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/message_queue_service.py +0 -0
  248. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/param_validation.py +0 -0
  249. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/pypy_compat.py +0 -0
  250. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/python_bridge.py +0 -0
  251. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/query_validators.py +0 -0
  252. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/service_container.py +0 -0
  253. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_builder.py +0 -0
  254. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_code_generator.py +0 -0
  255. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/terminal_templates.py +0 -0
  256. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_builder.py +0 -0
  257. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_code_generator.py +0 -0
  258. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/testing_templates.py +0 -0
  259. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_builder.py +0 -0
  260. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_desktop.py +0 -0
  261. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_html_templates.py +0 -0
  262. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
  263. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_mobile_templates.py +0 -0
  264. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_textual_adapter.py +0 -0
  265. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_textual_templates.py +0 -0
  266. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_tokens.py +0 -0
  267. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/ui_validator.py +0 -0
  268. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/validators.py +0 -0
  269. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_adapter.py +0 -0
  270. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_service.py +0 -0
  271. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/websocket_transport.py +0 -0
  272. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/runtime/wsgi.py +0 -0
  273. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/services.py +0 -0
  274. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum/utils/__init__.py +0 -0
  275. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/__init__.py +0 -0
  276. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
  277. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-admin.css +0 -0
  278. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
  279. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/__init__.py +0 -0
  280. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/audit_service.py +0 -0
  281. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/auth_service.py +0 -0
  282. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/component_discovery.py +0 -0
  283. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/config_generator.py +0 -0
  284. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/connector_service.py +0 -0
  285. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/crud.py +0 -0
  286. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/database.py +0 -0
  287. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/db_setup_service.py +0 -0
  288. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/deploy_service.py +0 -0
  289. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/docker_service.py +0 -0
  290. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/environment_service.py +0 -0
  291. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/git_service.py +0 -0
  292. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/health_service.py +0 -0
  293. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/job_service.py +0 -0
  294. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/log_service.py +0 -0
  295. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/main.py +0 -0
  296. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/models.py +0 -0
  297. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/pipeline_service.py +0 -0
  298. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/resource_manager.py +0 -0
  299. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/schemas.py +0 -0
  300. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/secret_manager.py +0 -0
  301. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/settings_service.py +0 -0
  302. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/template_service.py +0 -0
  303. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/test_execution_service.py +0 -0
  304. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/test_generator.py +0 -0
  305. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/webhook_service.py +0 -0
  306. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/backend/websocket_manager.py +0 -0
  307. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
  308. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/agents.q +0 -0
  309. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/app/[name].q +0 -0
  310. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/applications.q +0 -0
  311. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/component/[...path].q +0 -0
  312. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/components.q +0 -0
  313. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/connectors.q +0 -0
  314. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/dashboard.q +0 -0
  315. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/database.q +0 -0
  316. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/features.q +0 -0
  317. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/index.q +0 -0
  318. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/jobs.q +0 -0
  319. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/login.q +0 -0
  320. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/logout.q +0 -0
  321. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/settings.q +0 -0
  322. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/source.q +0 -0
  323. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/components/admin/tests.q +0 -0
  324. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/run.py +0 -0
  325. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/__init__.py +0 -0
  326. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_base.py +0 -0
  327. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_component_test_generator.py +0 -0
  328. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/_yaml_editor.py +0 -0
  329. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/apps.py +0 -0
  330. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/auth.py +0 -0
  331. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/components.py +0 -0
  332. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/connectors.py +0 -0
  333. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/projects.py +0 -0
  334. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/repository.py +0 -0
  335. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/settings.py +0 -0
  336. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_admin/services/yaml_import.py +0 -0
  337. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
  338. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/entry_points.txt +0 -0
  339. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/requires.txt +0 -0
  340. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/quantum_framework.egg-info/top_level.txt +0 -0
  341. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/setup.cfg +0 -0
  342. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_agent.py +0 -0
  343. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_animations.py +0 -0
  344. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ast_cache.py +0 -0
  345. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_cli_commands.py +0 -0
  346. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_component_library.py +0 -0
  347. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_expression_cache.py +0 -0
  348. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_form_validation.py +0 -0
  349. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_codegen.py +0 -0
  350. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_e2e.py +0 -0
  351. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_engine.py +0 -0
  352. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_game_parser.py +0 -0
  353. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_godot_codegen.py +0 -0
  354. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_job_executor.py +0 -0
  355. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_llm_providers.py +0 -0
  356. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_message_broker.py +0 -0
  357. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_message_queue.py +0 -0
  358. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_mobile_target.py +0 -0
  359. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_multi_agent.py +0 -0
  360. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_packages.py +0 -0
  361. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_plugins.py +0 -0
  362. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_pypy_compat.py +0 -0
  363. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_python_scripting.py +0 -0
  364. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_terminal_codegen.py +0 -0
  365. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_terminal_parser.py +0 -0
  366. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_testing_codegen.py +0 -0
  367. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_testing_parser.py +0 -0
  368. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_theming.py +0 -0
  369. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_html_adapter.py +0 -0
  370. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_parity.py +0 -0
  371. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_parser.py +0 -0
  372. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_ui_textual_adapter.py +0 -0
  373. {quantum_framework-0.17.0 → quantum_framework-0.19.0}/tests/test_websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantum-framework
3
- Version: 0.17.0
3
+ Version: 0.19.0
4
4
  Summary: Full-stack declarative framework for building web, desktop, and mobile applications
5
5
  Author-email: Daniel Gregorio <danielgregorio2611@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantum-framework"
7
- version = "0.17.0"
7
+ version = "0.19.0"
8
8
  description = "Full-stack declarative framework for building web, desktop, and mobile applications"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -102,9 +102,30 @@ class ProjectChecker:
102
102
  self._fields(ast, rel, colunas, {nome: nome for nome in colunas}, None)
103
103
 
104
104
  def _queries(self, node, rel: str, colunas: Dict[str, List[str]]) -> None:
105
- from quantum.core.ast_nodes import QueryNode
106
- from quantum.runtime.db_schema import check_sql
105
+ from quantum.core.ast_nodes import ActionNode, QueryNode
106
+ from quantum.runtime.db_schema import check_sql, read_schema
107
107
  for filho in _children(node):
108
+ if isinstance(filho, ActionNode) and getattr(filho, 'table', None):
109
+ # UI-10: the table the action takes its params from exists, with those columns.
110
+ ds = filho.table_datasource
111
+ if ds not in self.datasources:
112
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
113
+ f'<q:action name="{filho.name}">: datasource {ds!r} is not '
114
+ f'declared in quantum.config.yaml'))
115
+ elif self._connection(ds) is not None:
116
+ esquema = read_schema(self._connection(ds))
117
+ tabela = esquema.get(filho.table)
118
+ if tabela is None:
119
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
120
+ f'<q:action name="{filho.name}" table="{filho.table}">: '
121
+ f'no such table (tables: {", ".join(esquema) or "none"})'))
122
+ else:
123
+ for coluna in filho.table_columns or []:
124
+ if coluna not in tabela.columns:
125
+ self.problems.append(Problem(
126
+ rel, getattr(filho, 'source_line', None),
127
+ f'<q:action name="{filho.name}" columns=...>: table "{filho.table}" '
128
+ f'has no column "{coluna}"'))
108
129
  if isinstance(filho, QueryNode):
109
130
  ds = filho.datasource
110
131
  if not ds or ds.startswith('knowledge:') or getattr(filho, 'source', None):
@@ -399,6 +399,15 @@ Examples:
399
399
  migrate_create_parser = migrate_subparsers.add_parser('create', help='Create a new migration')
400
400
  migrate_create_parser.add_argument('name', help='Migration name')
401
401
 
402
+ # M6 (DB-10): schema.sql as the source of truth
403
+ migrate_plan_parser = migrate_subparsers.add_parser(
404
+ 'plan', help='Compare schema.sql with the migrations and plan the migration')
405
+ migrate_plan_parser.add_argument('--schema', default='schema.sql', help='Schema file (default: schema.sql)')
406
+ migrate_plan_parser.add_argument('--write', metavar='NAME', help='Save the plan as migration V00N_NAME')
407
+ migrate_plan_parser.add_argument('--yes', action='store_true', help='Do not ask before writing')
408
+ migrate_plan_parser.add_argument('--allow-data-loss', action='store_true',
409
+ help='Write a plan that drops tables or columns')
410
+
402
411
  # Parse arguments
403
412
  args = parser.parse_args()
404
413
 
@@ -541,13 +550,18 @@ Examples:
541
550
  if failed:
542
551
  sys.exit(1)
543
552
 
553
+ elif args.migrate_command == 'plan':
554
+ from quantum.cli.schema_plan import run_plan
555
+ sys.exit(run_plan(Path.cwd(), args.schema, args.write, args.yes, args.allow_data_loss,
556
+ interactive=sys.stdin.isatty()))
557
+
544
558
  elif args.migrate_command == 'create':
545
559
  filepath = runner.create(args.name)
546
560
  print(f"Created migration: {filepath}")
547
561
  print(f"Also created rollback: {filepath.with_suffix('.down.sql')}")
548
562
 
549
563
  else:
550
- print("Usage: quantum migrate <status|up|down|create> [options]")
564
+ print("Usage: quantum migrate <status|up|down|create|plan> [options]")
551
565
  sys.exit(1)
552
566
 
553
567
  sys.exit(0)
@@ -0,0 +1,273 @@
1
+ """`quantum migrate plan`: a declarative schema (M6, DB-10).
2
+
3
+ You edit `schema.sql` — the tables as they should be. `quantum migrate plan`
4
+ compares it with the schema the migrations produce, shows what changes, marks
5
+ what loses data, and `--write` saves the migration (and its rollback) after
6
+ confirmation. `quantum migrate up` applies it as any other.
7
+
8
+ Both sides are built in in-memory SQLite databases — the migrations applied in
9
+ order, and schema.sql — and compared with the schema reader of `quantum check`
10
+ (quantum/runtime/db_schema.py). The development database is not read: the
11
+ plan is the same on any machine and in CI.
12
+
13
+ SQLite can add a column but not change one, so any other change to a table
14
+ rebuilds it: create the new table, copy the columns both have, drop the old,
15
+ rename. Before a plan is written it is checked: applied to the migrations'
16
+ schema it must produce exactly schema.sql's.
17
+ """
18
+
19
+ import re
20
+ import sqlite3
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Dict, List, Optional, Tuple
24
+
25
+ from quantum.runtime.db_schema import Column, read_schema
26
+
27
+ # Tables that belong to Quantum, not to the app's schema.
28
+ INTERNAL_TABLES = {'_migrations', 'quantum_history'}
29
+
30
+
31
+ class SchemaPlanError(Exception):
32
+ """The plan cannot be made (no schema.sql, invalid SQL, a change it cannot express)."""
33
+
34
+
35
+ @dataclass
36
+ class Step:
37
+ kind: str # add-table, drop-table, add-column, rebuild, add-index, drop-index
38
+ target: str # "posts" or "posts.summary"
39
+ detail: str = ''
40
+ sql: List[str] = field(default_factory=list)
41
+ loses_data: bool = False
42
+ warning: str = ''
43
+
44
+ def line(self) -> str:
45
+ mark = {'add-table': '+', 'add-column': '+', 'add-index': '+', 'drop-table': '-',
46
+ 'drop-index': '-', 'rebuild': '~'}.get(self.kind, '?')
47
+ text = f'{mark} {self.kind.replace("-", " ")} {self.target}'
48
+ if self.detail:
49
+ text += f' ({self.detail})'
50
+ if self.loses_data:
51
+ text += ' !! loses data'
52
+ if self.warning:
53
+ text += f' !! {self.warning}'
54
+ return text
55
+
56
+
57
+ def _database(scripts: List[Tuple[str, str]]) -> sqlite3.Connection:
58
+ conn = sqlite3.connect(':memory:')
59
+ for origin, sql in scripts:
60
+ try:
61
+ conn.executescript(sql)
62
+ except sqlite3.Error as exc:
63
+ raise SchemaPlanError(f'{origin}: {exc}') from exc
64
+ return conn
65
+
66
+
67
+ def _migration_scripts(migrations_dir: Path) -> List[Tuple[str, str]]:
68
+ if not migrations_dir.exists():
69
+ return []
70
+ files = [f for f in sorted(migrations_dir.glob('*.sql')) if not f.name.endswith('.down.sql')]
71
+ return [(f.name, f.read_text(encoding='utf-8')) for f in files if '_' in f.stem]
72
+
73
+
74
+ def _tables(conn) -> Dict[str, object]:
75
+ return {name: t for name, t in read_schema(conn).items() if name not in INTERNAL_TABLES}
76
+
77
+
78
+ def _create_sql(conn, table: str) -> str:
79
+ return conn.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
80
+ (table,)).fetchone()[0]
81
+
82
+
83
+ def _indexes(conn) -> Dict[str, Tuple[str, str]]:
84
+ """Explicit indexes: name -> (table, sql)."""
85
+ return {name: (table, sql) for name, table, sql in conn.execute(
86
+ "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index' AND sql IS NOT NULL")
87
+ if table not in INTERNAL_TABLES}
88
+
89
+
90
+ def _column_sql(column: Column) -> str:
91
+ parts = [f'"{column.name}"']
92
+ if column.type:
93
+ parts.append(column.type)
94
+ if column.not_null:
95
+ parts.append('NOT NULL')
96
+ if column.default is not None:
97
+ parts.append(f'DEFAULT {column.default}')
98
+ if column.options:
99
+ options = ', '.join("'" + o.replace("'", "''") + "'" for o in column.options)
100
+ parts.append(f'CHECK ("{column.name}" IN ({options}))')
101
+ if column.references:
102
+ parts.append(f'REFERENCES "{column.references[0]}"("{column.references[1]}")')
103
+ return ' '.join(parts)
104
+
105
+
106
+ def _differences(a: Column, b: Column) -> List[str]:
107
+ changed = []
108
+ for label, x, y in (('type', (a.type or '').upper(), (b.type or '').upper()),
109
+ ('NOT NULL', a.not_null, b.not_null), ('default', a.default, b.default),
110
+ ('primary key', a.primary_key, b.primary_key), ('CHECK', a.options, b.options),
111
+ ('references', a.references, b.references)):
112
+ if x != y:
113
+ changed.append(label)
114
+ return changed
115
+
116
+
117
+ def diff(current_db, desired_db) -> List[Step]:
118
+ """The steps that turn the first database's schema into the second's."""
119
+ current, desired = _tables(current_db), _tables(desired_db)
120
+ steps: List[Step] = []
121
+ rebuilt = set()
122
+
123
+ for name in sorted(set(desired) - set(current)):
124
+ steps.append(Step('add-table', name, sql=[_create_sql(desired_db, name) + ';']))
125
+
126
+ for name in sorted(set(current) & set(desired)):
127
+ before, after = current[name].columns, desired[name].columns
128
+ added = [c for c in after if c not in before]
129
+ removed = [c for c in before if c not in after]
130
+ changed = {c: _differences(before[c], after[c]) for c in before
131
+ if c in after and _differences(before[c], after[c])}
132
+ addable = all(not after[c].primary_key and (not after[c].not_null or after[c].default is not None)
133
+ for c in added)
134
+ if not removed and not changed and addable:
135
+ for c in added:
136
+ steps.append(Step('add-column', f'{name}.{c}', after[c].type or '',
137
+ [f'ALTER TABLE "{name}" ADD COLUMN {_column_sql(after[c])};']))
138
+ continue
139
+ if not (added or removed or changed):
140
+ continue
141
+ common = ', '.join(f'"{c}"' for c in after if c in before)
142
+ create = re.sub(r'^\s*CREATE\s+TABLE\s+("?)' + re.escape(name) + r'\1',
143
+ f'CREATE TABLE "{name}__new"', _create_sql(desired_db, name), count=1, flags=re.I)
144
+ details = [f'+ {c}' for c in added] + [f'- {c}' for c in removed] + \
145
+ [f'{c}: {", ".join(d)}' for c, d in changed.items()]
146
+ warning = ''
147
+ if any('NOT NULL' in d and after[c].not_null for c, d in changed.items()) or \
148
+ any(after[c].not_null and after[c].default is None for c in added):
149
+ warning = 'fails if existing rows have no value for a NOT NULL column'
150
+ steps.append(Step('rebuild', name, '; '.join(details), [
151
+ create + ';',
152
+ f'INSERT INTO "{name}__new" ({common}) SELECT {common} FROM "{name}";',
153
+ f'DROP TABLE "{name}";',
154
+ f'ALTER TABLE "{name}__new" RENAME TO "{name}";',
155
+ ], loses_data=bool(removed) or any('type' in d for d in changed.values()), warning=warning))
156
+ rebuilt.add(name)
157
+
158
+ for name in sorted(set(current) - set(desired)):
159
+ steps.append(Step('drop-table', name, sql=[f'DROP TABLE "{name}";'], loses_data=True))
160
+
161
+ # Indexes: a rebuilt table lost its own, so they are created again.
162
+ before_idx, after_idx = _indexes(current_db), _indexes(desired_db)
163
+ for name, (table, sql) in sorted(before_idx.items()):
164
+ if table in desired and table not in rebuilt and after_idx.get(name, (None, None))[1] != sql:
165
+ steps.append(Step('drop-index', name, sql=[f'DROP INDEX "{name}";']))
166
+ for name, (table, sql) in sorted(after_idx.items()):
167
+ if before_idx.get(name, (None, None))[1] != sql or table in rebuilt:
168
+ steps.append(Step('add-index', name, sql=[sql + ';']))
169
+ return steps
170
+
171
+
172
+ def _schema_only(conn) -> str:
173
+ """The CREATE statements of a database, tables before indexes, without its rows."""
174
+ rows = conn.execute("SELECT type, sql FROM sqlite_master WHERE sql IS NOT NULL "
175
+ "AND name NOT LIKE 'sqlite_%' ORDER BY type = 'index', rowid").fetchall()
176
+ return '\n'.join(sql + ';' for _type, sql in rows)
177
+
178
+
179
+ def _signature(conn) -> tuple:
180
+ tables = _tables(conn)
181
+ return (tuple(sorted((n, tuple(sorted((c.name, (c.type or '').upper(), c.not_null, c.default, c.primary_key,
182
+ tuple(c.options or ()), c.references)
183
+ for c in t.columns.values()))) for n, t in tables.items())),
184
+ tuple(sorted((n, s) for n, (_t, s) in _indexes(conn).items())))
185
+
186
+
187
+ @dataclass
188
+ class Plan:
189
+ steps: List[Step]
190
+ up_sql: str
191
+ down_sql: str
192
+
193
+ @property
194
+ def loses_data(self) -> bool:
195
+ return any(s.loses_data for s in self.steps)
196
+
197
+
198
+ def make_plan(schema_file: Path, migrations_dir: Path) -> Plan:
199
+ if not schema_file.exists():
200
+ raise SchemaPlanError(f'{schema_file} does not exist: write the tables as they should be, '
201
+ f'e.g. CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);')
202
+ migrations = _migration_scripts(migrations_dir)
203
+ current = _database(migrations)
204
+ desired = _database([(schema_file.name, schema_file.read_text(encoding='utf-8'))])
205
+ steps = diff(current, desired)
206
+ up = '\n'.join(s for step in steps for s in step.sql)
207
+ down = '\n'.join(s for step in diff(desired, current) for s in step.sql)
208
+ if steps:
209
+ # The plan must do what it says: applied, it yields schema.sql's schema.
210
+ # Checked on the structure only — what existing rows may break is the
211
+ # step's warning, shown in the plan, not a reason to hide the plan.
212
+ check = _database([('migrations (schema only)', _schema_only(current)), ('plan', up)])
213
+ if _signature(check) != _signature(desired):
214
+ raise SchemaPlanError('the generated migration does not reproduce schema.sql — '
215
+ 'write this change as a migration by hand (quantum migrate create)')
216
+ return Plan(steps, up, down)
217
+
218
+
219
+ def next_version(migrations_dir: Path) -> str:
220
+ numbers = [int(m.group(1)) for f in migrations_dir.glob('V*.sql')
221
+ for m in [re.match(r'V(\d+)_', f.name)] if m]
222
+ return f'V{(max(numbers) + 1 if numbers else 1):03d}'
223
+
224
+
225
+ def write_plan(plan: Plan, migrations_dir: Path, name: str) -> Path:
226
+ migrations_dir.mkdir(parents=True, exist_ok=True)
227
+ slug = re.sub(r'[^a-z0-9_]+', '_', name.lower()).strip('_') or 'schema'
228
+ version = next_version(migrations_dir)
229
+ path = migrations_dir / f'{version}_{slug}.sql'
230
+ header = '-- Generated by `quantum migrate plan` from schema.sql (DB-10)\n'
231
+ summary = '\n'.join(f'-- {s.line()}' for s in plan.steps)
232
+ path.write_text(f'{header}{summary}\n\n{plan.up_sql}\n', encoding='utf-8')
233
+ (migrations_dir / f'{version}_{slug}.down.sql').write_text(
234
+ f'{header}-- rollback\n\n{plan.down_sql}\n', encoding='utf-8')
235
+ return path
236
+
237
+
238
+ def run_plan(project: Path, schema: Optional[str], write: Optional[str], yes: bool,
239
+ allow_data_loss: bool, ask=input, interactive: bool = True) -> int:
240
+ """The `quantum migrate plan` command. Returns the exit code."""
241
+ from quantum.cli.commands.migrate import _paths_migrations
242
+ migrations_dir = project / _paths_migrations(project)
243
+ schema_file = project / (schema or 'schema.sql')
244
+ try:
245
+ plan = make_plan(schema_file, migrations_dir)
246
+ except SchemaPlanError as exc:
247
+ print(f'[ERROR] {exc}')
248
+ return 1
249
+ if not plan.steps:
250
+ print(f'Nothing to do: the migrations already produce {schema_file.name}.')
251
+ return 0
252
+ print(f'Plan: {schema_file.name} vs. the migrations in {migrations_dir.name}/\n')
253
+ for step in plan.steps:
254
+ print(' ' + step.line())
255
+ print('\nSQL:\n' + plan.up_sql)
256
+ if not write:
257
+ print('\nNothing written. `quantum migrate plan --write <name>` saves it as a migration.')
258
+ return 0
259
+ if plan.loses_data and not allow_data_loss:
260
+ print('\n[REFUSED] This plan loses data (marked !!). Check it, then run again with '
261
+ '--allow-data-loss.')
262
+ return 1
263
+ if not yes:
264
+ if not interactive:
265
+ print('\n[REFUSED] Confirm with --yes (not a terminal: nobody to ask).')
266
+ return 1
267
+ answer = ask(f'\nWrite migration {next_version(migrations_dir)}_{write}? [y/N] ').strip().lower()
268
+ if answer not in ('y', 'yes'):
269
+ print('Nothing written.')
270
+ return 1
271
+ path = write_plan(plan, migrations_dir, write)
272
+ print(f'Written: {path} (and {path.stem}.down.sql). Apply with `quantum migrate up`.')
273
+ return 0
@@ -698,6 +698,8 @@ class QueryNode(QuantumNode):
698
698
  self.interval = None
699
699
  self.paginate = False # Enable automatic pagination
700
700
  self.page = None
701
+ self.page_expr = None # DB-9: page as an expression (default {query.page})
702
+ self.sortable = False # UI-13: ORDER BY the URL's ?sort=/&dir=
701
703
  self.page_size = 20
702
704
  self.timeout = None
703
705
  self.maxrows = None
@@ -1137,6 +1139,10 @@ class ActionNode(QuantumNode):
1137
1139
  self.method = method.upper()
1138
1140
  self.params: List[QuantumParam] = []
1139
1141
  self.body: List[QuantumNode] = []
1142
+ # UI-10 (M17): params from a table's schema — see quantum/runtime/table_params.py
1143
+ self.table: Optional[str] = None
1144
+ self.table_datasource: Optional[str] = None
1145
+ self.table_columns: Optional[List[str]] = None
1140
1146
 
1141
1147
  # Action configuration
1142
1148
  self.validate_csrf = True # Auto CSRF protection
@@ -319,6 +319,9 @@ class UIFormNode(QuantumNode, UILayoutMixin):
319
319
 
320
320
  def __init__(self):
321
321
  self.on_submit: Optional[str] = None
322
+ self.rules: Optional[str] = None # UI-9: 'off' = no rules from the action
323
+ self.values: Optional[str] = None # UI-10: {row} whose fields fill the form
324
+ self.submit: Optional[str] = None # UI-10: text of the generated button
322
325
  self.children: List[QuantumNode] = []
323
326
 
324
327
  # Validation mode: 'client', 'server', or 'both' (default)
@@ -550,6 +553,8 @@ class UIInputNode(QuantumNode, UILayoutMixin):
550
553
  def __init__(self):
551
554
  self.bind: Optional[str] = None
552
555
  self.value: Optional[str] = None # initial value (UI-6)
556
+ self.search: Optional[str] = None # UI-12: id of the region it refreshes as you type
557
+ self.delay: Optional[int] = None # UI-12: ms after the last key (300 with search=)
553
558
  self.placeholder: Optional[str] = None
554
559
  self.input_type: str = "text" # text, password, email, number
555
560
  self.on_change: Optional[str] = None
@@ -673,6 +678,9 @@ class UITableNode(QuantumNode, UILayoutMixin):
673
678
  def __init__(self):
674
679
  self.source: Optional[str] = None
675
680
  self.as_var: Optional[str] = None # row variable for column content (UI-5)
681
+ self.sort: bool = False # UI-13: headers sort the query
682
+ self.edit: Optional[str] = None # UI-13: table whose cells are edited in place
683
+ self.edit_datasource: Optional[str] = None
676
684
  self.children: List[QuantumNode] = [] # UIColumnNode children
677
685
  self._init_layout_attrs()
678
686
 
@@ -697,6 +705,7 @@ class UIColumnNode(QuantumNode):
697
705
  self.label: Optional[str] = None
698
706
  self.column_width: Optional[str] = None
699
707
  self.align: Optional[str] = None # left, center, right
708
+ self.edit: bool = True # UI-13: edit="false" keeps it read-only
700
709
  # UI-5: a column with content draws it once per row (buttons, links).
701
710
  self.children: List[QuantumNode] = []
702
711
 
@@ -1870,6 +1879,41 @@ class UIDatePickerNode(QuantumNode, UILayoutMixin):
1870
1879
  return self._validate_layout()
1871
1880
 
1872
1881
 
1882
+ class UIPagerNode(QuantumNode, UILayoutMixin):
1883
+ """<ui:pager for="query"> — previous, page numbers and next over a paginated query (UI-11)."""
1884
+
1885
+ def __init__(self):
1886
+ self.for_query: Optional[str] = None
1887
+ self.param: str = 'page'
1888
+ self.window: int = 2
1889
+ self.children: List[QuantumNode] = []
1890
+ self._init_layout_attrs()
1891
+
1892
+ def to_dict(self) -> Dict[str, Any]:
1893
+ return {"type": "ui_pager", "for": self.for_query, "param": self.param}
1894
+
1895
+ def validate(self) -> List[str]:
1896
+ return [] if self.for_query else ["ui:pager needs for="]
1897
+
1898
+
1899
+ class UIHistoryNode(QuantumNode, UILayoutMixin):
1900
+ """<ui:history table="t" key="{row.id}" datasource="db"> — a row's change history (DB-11)."""
1901
+
1902
+ def __init__(self):
1903
+ self.table: Optional[str] = None
1904
+ self.key: Optional[str] = None
1905
+ self.datasource: Optional[str] = None
1906
+ self.limit: int = 50
1907
+ self.children: List[QuantumNode] = []
1908
+ self._init_layout_attrs()
1909
+
1910
+ def to_dict(self) -> Dict[str, Any]:
1911
+ return {"type": "ui_history", "table": self.table, "key": self.key}
1912
+
1913
+ def validate(self) -> List[str]:
1914
+ return []
1915
+
1916
+
1873
1917
  def is_ui_node(node) -> bool:
1874
1918
  """A ui:* element (UI-1): markup, drawn by a renderer, never executed."""
1875
1919
  return type(node).__module__ == __name__
@@ -1882,6 +1926,6 @@ CORE_TAGS = frozenset({
1882
1926
  'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
1883
1927
  'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
1884
1928
  'text', 'badge', 'alert', 'link', 'image', 'progress',
1885
- 'table', 'column', 'list', 'item',
1929
+ 'table', 'column', 'list', 'item', 'pager', 'history',
1886
1930
  'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
1887
1931
  })
@@ -28,7 +28,7 @@ from .ast_nodes import (
28
28
  UIToastNode, UIToastContainerNode,
29
29
  UICarouselNode, UISlideNode,
30
30
  UIStepperNode, UIStepNode,
31
- UICalendarNode, UIDatePickerNode,
31
+ UICalendarNode, UIDatePickerNode, UIPagerNode, UIHistoryNode,
32
32
  )
33
33
  from quantum.core.features.theming.src import UIThemeNode, UIColorNode
34
34
  from quantum.core.ast_nodes import TextNode
@@ -81,6 +81,8 @@ class UIParser:
81
81
  'select': '_parse_ui_select',
82
82
  'table': '_parse_ui_table',
83
83
  'column': '_parse_ui_column',
84
+ 'pager': '_parse_ui_pager',
85
+ 'history': '_parse_ui_history',
84
86
  'list': '_parse_ui_list',
85
87
  'item': '_parse_ui_item',
86
88
  'image': '_parse_ui_image',
@@ -359,6 +361,13 @@ class UIParser:
359
361
  node = UIFormNode()
360
362
  node.on_submit = element.get('on-submit')
361
363
  node.send = element.get('with')
364
+ # UI-9: rules="off" stops the form from taking its fields' rules from the action.
365
+ node.rules = element.get('rules')
366
+ # UI-10: an edit form opens with a row's values; the generated button's text.
367
+ node.values = element.get('values')
368
+ node.submit = element.get('submit')
369
+ if node.rules not in (None, 'on', 'off'):
370
+ raise UIParseError(f'<ui:form rules="{node.rules}">: use on (the default) or off')
362
371
 
363
372
  # Validation attributes
364
373
  node.validation_mode = element.get('validation', 'both')
@@ -430,6 +439,14 @@ class UIParser:
430
439
  node = UIInputNode()
431
440
  node.bind = element.get('bind')
432
441
  node.value = element.get('value')
442
+ # UI-12: search as you type — refresh the element with this id.
443
+ node.search = element.get('search')
444
+ espera = element.get('delay')
445
+ if espera is not None and not espera.isdigit():
446
+ raise UIParseError(f'<ui:input delay="{espera}">: milliseconds, e.g. delay="300"')
447
+ node.delay = int(espera) if espera is not None else (300 if node.search else None)
448
+ if node.search is not None and not node.bind:
449
+ raise UIParseError('<ui:input search=...> needs bind= (the URL parameter it sets)')
433
450
  node.placeholder = element.get('placeholder')
434
451
  node.input_type = element.get('type', 'text')
435
452
  node.on_change = element.get('on-change')
@@ -498,6 +515,14 @@ class UIParser:
498
515
  node = UITableNode()
499
516
  node.source = element.get('source')
500
517
  node.as_var = element.get('as')
518
+ # UI-13: sortable headers and cells edited in place.
519
+ node.sort = self._parse_bool(element.get('sort'))
520
+ node.edit = element.get('edit')
521
+ node.edit_datasource = element.get('datasource')
522
+ if node.edit and not node.edit_datasource:
523
+ raise UIParseError(f'<ui:table edit="{node.edit}"> needs datasource= (where the table is)')
524
+ if (node.sort or node.edit) and not node.source:
525
+ raise UIParseError('<ui:table sort/edit> needs source="{query}"')
501
526
  self._apply_layout_attrs(node, element)
502
527
  # Parse child <ui:column> elements
503
528
  self._parse_children(element, node)
@@ -509,9 +534,38 @@ class UIParser:
509
534
  node.label = element.get('label')
510
535
  node.column_width = element.get('width')
511
536
  node.align = element.get('align')
537
+ node.edit = element.get('edit', 'true') != 'false'
512
538
  self._parse_children(element, node)
513
539
  return node
514
540
 
541
+ def _parse_ui_pager(self, element: ET.Element) -> UIPagerNode:
542
+ node = UIPagerNode()
543
+ node.for_query = element.get('for')
544
+ if not node.for_query:
545
+ raise UIParseError('<ui:pager> needs for="<the paginated q:query>"')
546
+ node.param = element.get('param') or 'page'
547
+ janela = element.get('window')
548
+ if janela is not None and not janela.isdigit():
549
+ raise UIParseError(f'<ui:pager window="{janela}">: the number of pages shown each side of the current one')
550
+ node.window = int(janela) if janela is not None else 2
551
+ self._apply_layout_attrs(node, element)
552
+ return node
553
+
554
+ def _parse_ui_history(self, element: ET.Element) -> UIHistoryNode:
555
+ node = UIHistoryNode()
556
+ node.table = element.get('table')
557
+ node.key = element.get('key')
558
+ node.datasource = element.get('datasource')
559
+ missing = [a for a in ('table', 'key', 'datasource') if not element.get(a)]
560
+ if missing:
561
+ raise UIParseError(f'<ui:history> needs {", ".join(a + "=" for a in missing)}')
562
+ limit = element.get('limit')
563
+ if limit is not None and not limit.isdigit():
564
+ raise UIParseError(f'<ui:history limit="{limit}">: a number of entries')
565
+ node.limit = int(limit) if limit is not None else 50
566
+ self._apply_layout_attrs(node, element)
567
+ return node
568
+
515
569
  def _parse_ui_list(self, element: ET.Element) -> UIListNode:
516
570
  node = UIListNode()
517
571
  node.source = element.get('source')
@@ -77,7 +77,27 @@ class QueryParser(BaseTagParser):
77
77
  # Parse attributes
78
78
  query_node.source = source
79
79
  query_node.paginate = self.get_bool_attr(element, 'paginate', False)
80
- query_node.page = self.get_int_attr(element, 'page', 0) or None
80
+ # UI-13: ordered by the URL's ?sort=/&dir= (a <ui:table sort="true"> writes them)
81
+ query_node.sortable = self.get_bool_attr(element, 'sortable', False)
82
+ # DB-9: page= is an expression, resolved when the query runs; it was
83
+ # read as an int at parse time, so page="{query.page}" was always
84
+ # page 1. With paginate="true" and no page=, the page is the URL's
85
+ # `page` parameter — the one <ui:pager> writes.
86
+ pagina = element.get('page')
87
+ query_node.page = None
88
+ query_node.page_expr = None
89
+ if pagina is not None:
90
+ if '{' in pagina:
91
+ query_node.page_expr = pagina
92
+ elif pagina.strip().isdigit():
93
+ query_node.page = int(pagina)
94
+ else:
95
+ from quantum.core.parser import QuantumParseError
96
+ raise QuantumParseError(
97
+ f'<q:query name="{name}" page="{pagina}">: a page number or an expression, '
98
+ f'e.g. page="{{query.pagina}}"')
99
+ elif query_node.paginate:
100
+ query_node.page_expr = '{query.page}'
81
101
  # Accept both spellings: the codebase convention is camelCase, but
82
102
  # snake_case reads naturally in XML and the examples used it —
83
103
  # the mismatch made page_size None and crashed pagination with
@@ -48,6 +48,21 @@ class ActionParser(BaseTagParser):
48
48
  f'<q:action name="{name}"> {attr}= is not supported: it was accepted '
49
49
  f'and never enforced. {porque}')
50
50
 
51
+ # UI-10 (M17): the action's params come from a table's schema, read
52
+ # from the database when the action is used; a q:param written wins.
53
+ action_node.table = element.get('table')
54
+ action_node.table_datasource = element.get('datasource')
55
+ colunas = element.get('columns')
56
+ action_node.table_columns = [c.strip() for c in colunas.split(',') if c.strip()] if colunas else None
57
+ if (action_node.table_datasource or colunas) and not action_node.table:
58
+ from quantum.core.parser import QuantumParseError
59
+ raise QuantumParseError(
60
+ f'<q:action name="{name}"> datasource= and columns= describe table=, which is missing')
61
+ if action_node.table and not action_node.table_datasource:
62
+ from quantum.core.parser import QuantumParseError
63
+ raise QuantumParseError(
64
+ f'<q:action name="{name}" table="{action_node.table}"> needs datasource= (where the table is)')
65
+
51
66
  # Parse children (params, statements)
52
67
  for child in element:
53
68
  child_type = self._get_element_name(child)