mxrb 0.1.0

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 (237) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.de-DE.md +108 -0
  4. data/README.md +172 -0
  5. data/README.pt-BR.md +109 -0
  6. data/bin/mxrb +1613 -0
  7. data/docs/README.md +10 -0
  8. data/docs/de-DE/README.md +24 -0
  9. data/docs/de-DE/architectural-patterns.md +26 -0
  10. data/docs/de-DE/architectural-standard.md +48 -0
  11. data/docs/de-DE/architecture.md +76 -0
  12. data/docs/de-DE/compiler.md +162 -0
  13. data/docs/de-DE/conventions.md +19 -0
  14. data/docs/de-DE/design-system.md +52 -0
  15. data/docs/de-DE/entity-dsl.md +53 -0
  16. data/docs/de-DE/native-runtime-quality-report.md +74 -0
  17. data/docs/de-DE/oql-sql.md +211 -0
  18. data/docs/de-DE/platform-api-integration.md +36 -0
  19. data/docs/de-DE/platform-operations.md +243 -0
  20. data/docs/de-DE/project-structure.md +32 -0
  21. data/docs/de-DE/ruby-first-roadmap.md +116 -0
  22. data/docs/de-DE/scaffolds.md +59 -0
  23. data/docs/de-DE/semantic-refactoring.md +56 -0
  24. data/docs/de-DE/team-server.md +33 -0
  25. data/docs/de-DE/validation-matrix.md +64 -0
  26. data/docs/de-DE/vetclinic-acceptance.md +24 -0
  27. data/docs/de-DE/writing.md +138 -0
  28. data/docs/en-US/README.md +24 -0
  29. data/docs/en-US/architectural-patterns.md +151 -0
  30. data/docs/en-US/architectural-standard.md +89 -0
  31. data/docs/en-US/architecture.md +170 -0
  32. data/docs/en-US/compiler.md +157 -0
  33. data/docs/en-US/conventions.md +82 -0
  34. data/docs/en-US/design-system.md +163 -0
  35. data/docs/en-US/entity-dsl.md +53 -0
  36. data/docs/en-US/native-runtime-quality-report.md +73 -0
  37. data/docs/en-US/oql-sql.md +201 -0
  38. data/docs/en-US/platform-api-integration.md +36 -0
  39. data/docs/en-US/platform-operations.md +244 -0
  40. data/docs/en-US/project-structure.md +49 -0
  41. data/docs/en-US/ruby-first-roadmap.md +186 -0
  42. data/docs/en-US/scaffolds.md +57 -0
  43. data/docs/en-US/semantic-refactoring.md +198 -0
  44. data/docs/en-US/team-server.md +32 -0
  45. data/docs/en-US/validation-matrix.md +257 -0
  46. data/docs/en-US/vetclinic-acceptance.md +23 -0
  47. data/docs/en-US/writing.md +655 -0
  48. data/docs/pt-BR/README.md +24 -0
  49. data/docs/pt-BR/architectural-patterns.md +25 -0
  50. data/docs/pt-BR/architectural-standard.md +533 -0
  51. data/docs/pt-BR/architecture.md +300 -0
  52. data/docs/pt-BR/compiler.md +162 -0
  53. data/docs/pt-BR/conventions.md +19 -0
  54. data/docs/pt-BR/design-system.md +65 -0
  55. data/docs/pt-BR/entity-dsl.md +116 -0
  56. data/docs/pt-BR/native-runtime-quality-report.md +73 -0
  57. data/docs/pt-BR/oql-sql.md +208 -0
  58. data/docs/pt-BR/platform-api-integration.md +36 -0
  59. data/docs/pt-BR/platform-operations.md +260 -0
  60. data/docs/pt-BR/project-structure.md +32 -0
  61. data/docs/pt-BR/ruby-first-roadmap.md +239 -0
  62. data/docs/pt-BR/scaffolds.md +104 -0
  63. data/docs/pt-BR/semantic-refactoring.md +55 -0
  64. data/docs/pt-BR/team-server.md +59 -0
  65. data/docs/pt-BR/validation-matrix.md +86 -0
  66. data/docs/pt-BR/vetclinic-acceptance.md +50 -0
  67. data/docs/pt-BR/writing.md +177 -0
  68. data/examples/sudoku_evaluation.rb +14 -0
  69. data/examples/sudoku_functional_test.rb +28 -0
  70. data/lib/mxrb/architecture/graph.rb +124 -0
  71. data/lib/mxrb/architecture/validator.rb +98 -0
  72. data/lib/mxrb/benchmark.rb +33 -0
  73. data/lib/mxrb/compare.rb +390 -0
  74. data/lib/mxrb/compiler/adapter.rb +96 -0
  75. data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
  76. data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
  77. data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
  78. data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
  79. data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
  80. data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
  81. data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
  82. data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
  83. data/lib/mxrb/compiler/constants_materializer.rb +111 -0
  84. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
  85. data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
  86. data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
  87. data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
  88. data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
  89. data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
  90. data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
  91. data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
  92. data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
  93. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
  94. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
  95. data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
  96. data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
  97. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
  98. data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
  99. data/lib/mxrb/compiler/mda.rb +99 -0
  100. data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
  101. data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
  102. data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
  103. data/lib/mxrb/compiler/model_package.rb +116 -0
  104. data/lib/mxrb/compiler/model_values.rb +67 -0
  105. data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
  106. data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
  107. data/lib/mxrb/compiler/packager.rb +100 -0
  108. data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
  109. data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
  110. data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
  111. data/lib/mxrb/compiler/portable_packager.rb +356 -0
  112. data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
  113. data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
  114. data/lib/mxrb/compiler/project_materializer.rb +139 -0
  115. data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
  116. data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
  117. data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
  118. data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
  119. data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
  120. data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
  121. data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
  122. data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
  123. data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
  124. data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
  125. data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
  126. data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
  127. data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
  128. data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
  129. data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
  130. data/lib/mxrb/compiler/security_materializer.rb +104 -0
  131. data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
  132. data/lib/mxrb/compiler/settings_materializer.rb +25 -0
  133. data/lib/mxrb/compiler/source_model.rb +128 -0
  134. data/lib/mxrb/compiler/system_model_seed.rb +88 -0
  135. data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
  136. data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
  137. data/lib/mxrb/compiler/translation_materializer.rb +95 -0
  138. data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
  139. data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
  140. data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
  141. data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
  142. data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
  143. data/lib/mxrb/doctor.rb +116 -0
  144. data/lib/mxrb/dsl/builder.rb +1636 -0
  145. data/lib/mxrb/errors.rb +18 -0
  146. data/lib/mxrb/evaluation.rb +131 -0
  147. data/lib/mxrb/exporter.rb +1716 -0
  148. data/lib/mxrb/frontend/migrator.rb +775 -0
  149. data/lib/mxrb/functional.rb +307 -0
  150. data/lib/mxrb/github/annotator.rb +161 -0
  151. data/lib/mxrb/initializer.rb +257 -0
  152. data/lib/mxrb/integrity/validator.rb +136 -0
  153. data/lib/mxrb/io/bson_codec.rb +145 -0
  154. data/lib/mxrb/io/mpr_file.rb +558 -0
  155. data/lib/mxrb/io/mxunit_codec.rb +42 -0
  156. data/lib/mxrb/marketplace.rb +380 -0
  157. data/lib/mxrb/model/association.rb +98 -0
  158. data/lib/mxrb/model/attribute.rb +100 -0
  159. data/lib/mxrb/model/connector.rb +17 -0
  160. data/lib/mxrb/model/design_materializer.rb +133 -0
  161. data/lib/mxrb/model/design_migration.rb +126 -0
  162. data/lib/mxrb/model/design_system.rb +111 -0
  163. data/lib/mxrb/model/domain_model.rb +51 -0
  164. data/lib/mxrb/model/entity.rb +180 -0
  165. data/lib/mxrb/model/menu.rb +38 -0
  166. data/lib/mxrb/model/microflow.rb +100 -0
  167. data/lib/mxrb/model/module.rb +142 -0
  168. data/lib/mxrb/model/navigation.rb +127 -0
  169. data/lib/mxrb/model/page.rb +410 -0
  170. data/lib/mxrb/model/project.rb +187 -0
  171. data/lib/mxrb/model/unit.rb +48 -0
  172. data/lib/mxrb/module_initializer.rb +75 -0
  173. data/lib/mxrb/official_marketplace/content_api.rb +292 -0
  174. data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
  175. data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
  176. data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
  177. data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
  178. data/lib/mxrb/official_marketplace.rb +720 -0
  179. data/lib/mxrb/oql/analyzer.rb +155 -0
  180. data/lib/mxrb/oql/index_advisor.rb +85 -0
  181. data/lib/mxrb/oql/plan_analyzer.rb +185 -0
  182. data/lib/mxrb/oql/server.rb +132 -0
  183. data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
  184. data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
  185. data/lib/mxrb/oql/workload_analyzer.rb +175 -0
  186. data/lib/mxrb/oql/workload_baseline.rb +60 -0
  187. data/lib/mxrb/oql.rb +424 -0
  188. data/lib/mxrb/project_lifecycle.rb +76 -0
  189. data/lib/mxrb/protocols/plan.rb +86 -0
  190. data/lib/mxrb/protocols.rb +123 -0
  191. data/lib/mxrb/runtime/database_workspace.rb +677 -0
  192. data/lib/mxrb/runtime/docker_executor.rb +41 -0
  193. data/lib/mxrb/runtime/docker_workspace.rb +29 -0
  194. data/lib/mxrb/runtime/executor.rb +169 -0
  195. data/lib/mxrb/runtime/java_locator.rb +26 -0
  196. data/lib/mxrb/runtime/native.rb +474 -0
  197. data/lib/mxrb/runtime/sql_server_database.rb +146 -0
  198. data/lib/mxrb/runtime/toolchain.rb +82 -0
  199. data/lib/mxrb/scaffold/cli.rb +113 -0
  200. data/lib/mxrb/scaffold/generator.rb +246 -0
  201. data/lib/mxrb/scaffold/help.rb +80 -0
  202. data/lib/mxrb/scaffold/page_templates.rb +54 -0
  203. data/lib/mxrb/scaffold/recipes.rb +198 -0
  204. data/lib/mxrb/scaffold/registry.rb +82 -0
  205. data/lib/mxrb/scaffold/templates.rb +723 -0
  206. data/lib/mxrb/scaffold/transaction.rb +84 -0
  207. data/lib/mxrb/schema/tables.rb +55 -0
  208. data/lib/mxrb/semantic/analyzer.rb +473 -0
  209. data/lib/mxrb/semantic/batch_plan.rb +68 -0
  210. data/lib/mxrb/semantic/domain_mutator.rb +419 -0
  211. data/lib/mxrb/semantic/embedder.rb +51 -0
  212. data/lib/mxrb/semantic/extractor.rb +290 -0
  213. data/lib/mxrb/semantic/index.rb +623 -0
  214. data/lib/mxrb/semantic/inliner.rb +216 -0
  215. data/lib/mxrb/semantic/mover.rb +153 -0
  216. data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
  217. data/lib/mxrb/semantic/remover.rb +70 -0
  218. data/lib/mxrb/semantic/renamer.rb +211 -0
  219. data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
  220. data/lib/mxrb/semantic/vec_store.rb +75 -0
  221. data/lib/mxrb/team_server.rb +390 -0
  222. data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
  223. data/lib/mxrb/templates/project/11.12.1.json +32 -0
  224. data/lib/mxrb/templates/project/6.10.8.json +50 -0
  225. data/lib/mxrb/templates/project/7.17.0.json +50 -0
  226. data/lib/mxrb/templates/project/7.5.0.json +50 -0
  227. data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
  228. data/lib/mxrb/version.rb +5 -0
  229. data/lib/mxrb/widget_package.rb +338 -0
  230. data/lib/mxrb/widget_synchronizer.rb +31 -0
  231. data/lib/mxrb/writer.rb +3775 -0
  232. data/lib/mxrb.rb +188 -0
  233. data/marketplace/catalog.json +10 -0
  234. data/marketplace/modules/shared-kernel/domain/README.md +4 -0
  235. data/marketplace/modules/shared-kernel/module.rb +5 -0
  236. data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
  237. metadata +447 -0
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+
6
+ module Mxrb
7
+ module Compiler
8
+ WebBundleResult = Data.define(:directory, :files, :bytes)
9
+
10
+ # Generates the web-client entrypoint and runs the version-owned Rspack toolchain directly.
11
+ # rubocop:disable Metrics
12
+ class WebBundleBuilder
13
+ include ModelValues
14
+
15
+ def initialize(mpr_path, deployment:, mendix_home:)
16
+ @mpr_path = File.expand_path(mpr_path)
17
+ @deployment = File.expand_path(deployment)
18
+ @source = SourceModel.read(@mpr_path)
19
+ @version_root = version_root(mendix_home)
20
+ end
21
+
22
+ def build
23
+ web = File.join(@deployment, 'web')
24
+ profiles = Adapter.for(@source.version, source: @source).web_profiles
25
+ return LegacyPageBuilder.new(@source, web, profiles:).build unless profiles.include?(:react)
26
+
27
+ prepare(web)
28
+ run_bundler(web)
29
+ WebShellMaterializer.new(web, version: @source.version).materialize
30
+ result(web)
31
+ end
32
+
33
+ private
34
+
35
+ def run_bundler(web)
36
+ output, status = Open3.capture2e(environment(web), node, *bundler_arguments, chdir: web)
37
+ raise CompilationError, "#{bundler_name} web build failed:\n#{output}" unless status.success?
38
+ end
39
+
40
+ def prepare(web)
41
+ WidgetPackageExtractor.new(File.dirname(@mpr_path), web).extract
42
+ materialize_react_dom_compatibility(web)
43
+ WebOperationCompiler.new(@source).write(File.join(@deployment, 'model', 'operations.json'))
44
+ PageBundleBuilder.new(@source, web).build
45
+ write_entry(File.join(web, 'index.js'))
46
+ end
47
+
48
+ def materialize_react_dom_compatibility(web)
49
+ shim = File.join(web, 'react-dom-compat.mjs')
50
+ react_dom = File.join(@version_root, 'modeler', 'tools', 'node', 'node_modules',
51
+ 'react-dom', 'index.js')
52
+ File.write(shim, <<~JS)
53
+ import * as ReactDOM from #{JSON.generate(react_dom)};
54
+ export * from #{JSON.generate(react_dom)};
55
+ export default ReactDOM;
56
+ export const findDOMNode = value => {
57
+ if (typeof Element !== "undefined" && value instanceof Element) return value;
58
+ const seen = new Set();
59
+ const pending = [value?._reactInternals, value?._reactInternalFiber].filter(Boolean);
60
+ while (pending.length) {
61
+ const fiber = pending.shift();
62
+ if (!fiber || seen.has(fiber)) continue;
63
+ seen.add(fiber);
64
+ if (typeof Element !== "undefined" && fiber.stateNode instanceof Element) {
65
+ return fiber.stateNode;
66
+ }
67
+ pending.push(fiber.child, fiber.sibling);
68
+ }
69
+ return null;
70
+ };
71
+ JS
72
+ config_path = File.join(web, 'rspack.config.mjs')
73
+ return unless File.file?(config_path)
74
+
75
+ config = File.read(config_path)
76
+ return if config.include?(JSON.generate(shim))
77
+
78
+ marker = 'alias: {'
79
+ replacement = "#{marker}\n \"react-dom\": #{JSON.generate(shim)},"
80
+ raise CompilationError, 'Rspack config has no resolve alias block' unless config.include?(marker)
81
+
82
+ File.write(config_path, config.sub(marker, replacement))
83
+ end
84
+
85
+ def version_root(path)
86
+ root = File.expand_path(path)
87
+ File.basename(root) == 'runtime' ? File.dirname(root) : root
88
+ end
89
+
90
+ def result(web)
91
+ directory = File.join(web, 'dist')
92
+ files = Dir.glob(File.join(directory, '**', '*')).select { File.file?(_1) }
93
+ WebBundleResult.new(directory:, files: files.length, bytes: files.sum { File.size(_1) })
94
+ end
95
+
96
+ def node = File.join(@version_root, 'modeler', 'tools', 'node', 'linux-x64', 'node')
97
+ def bundler_name = @source.version.to_i == 10 ? 'Rollup' : 'Rspack'
98
+
99
+ def bundler_arguments
100
+ return [File.join(node_modules, 'rollup', 'dist', 'bin', 'rollup'), '--config'] if @source.version.to_i == 10
101
+
102
+ [File.join(@version_root, 'modeler', 'tools', 'node', 'rspack-runner.mjs')]
103
+ end
104
+
105
+ def node_modules = File.join(@version_root, 'modeler', 'tools', 'node', 'node_modules')
106
+
107
+ def environment(web)
108
+ {
109
+ 'NODE_ENV' => 'production', 'SOURCE_MAP_GENERATION' => 'disabled',
110
+ 'SHOULD_GENERATE_EMBEDDED_INDEX' => 'false',
111
+ 'MX_DEPLOYMENT_WEB_DIRECTORY' => web,
112
+ 'MX_WEB_CLIENT_BUILD_LOG' => File.join(@deployment, 'log', 'mxrb-web-build.log'),
113
+ 'NODE_PATH' => node_modules
114
+ }
115
+ end
116
+
117
+ def write_entry(path)
118
+ payload = {
119
+ languages: languages, systemTexts: rendered_system_texts,
120
+ constants: { LAYOUT_SCOPE_ID_PREFIX: 'l' },
121
+ registerServiceWorker: false, enableServiceWorkerCaching: true
122
+ }
123
+ File.write(path, "import { startApp } from \"mendix\";\n\nstartApp(#{JSON.pretty_generate(payload)});\n")
124
+ end
125
+
126
+ def languages
127
+ values = translations.values.flat_map(&:keys).uniq.sort
128
+ values.empty? ? ['en_US'] : values
129
+ end
130
+
131
+ def rendered_system_texts
132
+ translations.transform_values do |values|
133
+ languages.map { values.fetch(_1, '') }
134
+ end
135
+ end
136
+
137
+ def translations
138
+ @translations ||= collect_translations
139
+ end
140
+
141
+ def collect_translations
142
+ collection = @source.documents('Texts$SystemTextCollection').first
143
+ return {} unless collection
144
+
145
+ array(collection['SystemTexts']).to_h do |text|
146
+ translations = array(text.dig('Text', 'Items')).to_h do |item|
147
+ [item['LanguageCode'].to_s, item['Text'].to_s]
148
+ end
149
+ [text['InternalKey'].to_s, translations]
150
+ end
151
+ end
152
+ end
153
+ # rubocop:enable Metrics
154
+ end
155
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Resolves React list data sources and safely lowers unusable simple connector reads to XPath.
6
+ # rubocop:disable Metrics
7
+ class WebListDataSource
8
+ include ModelValues
9
+
10
+ attr_reader :association_path, :entity, :microflow_name, :nanoflow_name, :xpath_constraint
11
+
12
+ def initialize(source, widget)
13
+ @source = source
14
+ @widget = widget
15
+ resolve_sources
16
+ resolve_flows
17
+ resolve_entity
18
+ @xpath_constraint = @xpath&.fetch('XPathConstraint', '').to_s
19
+ end
20
+
21
+ def resolve_sources
22
+ direct = widget['DataSource'] if widget.is_a?(Hash)
23
+ @xpath = direct if database_source?(direct)
24
+ @xpath ||= nested(widget, 'CustomWidgets$CustomWidgetXPathSource').first
25
+ @association_source = direct if direct&.fetch('$Type', nil) == 'Forms$AssociationSource'
26
+ @association_source ||= nested(widget, 'Forms$AssociationSource').first
27
+ @microflow_source = direct if direct&.fetch('$Type', nil) == 'Forms$MicroflowSource'
28
+ @microflow_source ||= nested(widget, 'Forms$MicroflowSource').first
29
+ @nanoflow_source = direct if direct&.fetch('$Type', nil) == 'Forms$NanoflowSource'
30
+ @nanoflow_source ||= nested(widget, 'Forms$NanoflowSource').first
31
+ @microflow_name = flow_name(@microflow_source, 'Microflow')
32
+ @nanoflow_name = @nanoflow_source&.fetch('Nanoflow', '').to_s
33
+ @association_path = entity_ref_path(@association_source&.fetch('EntityRef', nil))
34
+ end
35
+
36
+ def flow_name(source, key)
37
+ source&.dig("#{key}Settings", key).to_s.then do |name|
38
+ name.empty? ? source&.fetch(key, '').to_s : name
39
+ end
40
+ end
41
+
42
+ def resolve_flows
43
+ @flow = qualified_unit('Microflows$Microflow', microflow_name) unless microflow_name.empty?
44
+ @nanoflow = qualified_unit('Microflows$Nanoflow', nanoflow_name) unless nanoflow_name.empty?
45
+ end
46
+
47
+ def resolve_entity
48
+ @entity = [
49
+ entity_ref_destination(@xpath&.fetch('EntityRef', nil)),
50
+ entity_ref_destination(@association_source&.fetch('EntityRef', nil)),
51
+ @flow&.document&.dig('MicroflowReturnType', 'Entity'),
52
+ @nanoflow&.document&.dig('MicroflowReturnType', 'Entity')
53
+ ].map(&:to_s).find { !_1.empty? }.to_s
54
+ end
55
+
56
+ def supported? = xpath? || association? || microflow? || nanoflow?
57
+ def xpath? = !@xpath.nil? || connector_fallback?
58
+ def association? = !@association_source.nil? && !association_path.empty? && !entity.empty?
59
+ def microflow? = !@microflow_source.nil? && !microflow_name.empty? && !@flow.nil?
60
+ def nanoflow? = !@nanoflow_source.nil? && !nanoflow_name.empty? && !@nanoflow.nil?
61
+
62
+ private
63
+
64
+ attr_reader :widget
65
+
66
+ def database_source?(source)
67
+ %w[Forms$DatabaseSource Forms$NewListViewDatabaseSource Forms$ListViewXPathSource]
68
+ .include?(source&.fetch('$Type', nil))
69
+ end
70
+
71
+ def entity_ref_destination(reference)
72
+ steps = array(reference&.fetch('Steps', nil))
73
+ steps.last&.fetch('DestinationEntity', nil) || reference&.fetch('Entity', nil)
74
+ end
75
+
76
+ def entity_ref_path(reference)
77
+ array(reference&.fetch('Steps', nil)).flat_map do |step|
78
+ [step['Association'], step['DestinationEntity']]
79
+ end.map(&:to_s).reject(&:empty?).join('/')
80
+ end
81
+
82
+ def connector_fallback?
83
+ return @connector_fallback unless @connector_fallback.nil?
84
+
85
+ @connector_fallback = connector_action.then do |action|
86
+ action && simple_unconfigured_query?(action)
87
+ end
88
+ end
89
+
90
+ def connector_action
91
+ actions = nested(@flow&.document, 'DatabaseConnector$ExecuteDatabaseQueryAction')
92
+ actions.one? ? actions.first : nil
93
+ end
94
+
95
+ def simple_unconfigured_query?(action)
96
+ connection, query = connector_query(action['Query'].to_s)
97
+ return false unless connection && query && connection_unconfigured?(connection)
98
+ return false unless query['Query'].to_s.match?(/\A\s*select\s+.+\s+from\s+[A-Za-z_][A-Za-z0-9_]*\s*;?\s*\z/im)
99
+
100
+ mappings = array(query['TableMappings'])
101
+ mappings.one? && mappings.first['Entity'].to_s == entity
102
+ end
103
+
104
+ def connector_query(qualified)
105
+ connection_name, _, query_name = qualified.rpartition('.')
106
+ unit = @source.units.find do |candidate|
107
+ candidate.document['$Type'] == 'DatabaseConnector$DatabaseConnection' &&
108
+ "#{candidate.module_name}.#{candidate.document['Name']}" == connection_name
109
+ end
110
+ [unit&.document, array(unit&.document&.fetch('Queries', nil)).find { _1['Name'] == query_name }]
111
+ end
112
+
113
+ def connection_unconfigured?(connection)
114
+ %w[ConnectionString UserName Password].all? do |field|
115
+ name = connection[field].to_s
116
+ constant = qualified_unit('Constants$Constant', name) unless name.empty?
117
+ constant && constant.document.fetch('DefaultValue', nil).to_s.empty?
118
+ end
119
+ end
120
+
121
+ def qualified_unit(type, qualified)
122
+ @source.units_of(type).find do |unit|
123
+ "#{unit.module_name}.#{unit.document['Name']}" == qualified
124
+ end
125
+ end
126
+
127
+ def nested(value, type, result = [])
128
+ case value
129
+ when Hash
130
+ result << value if value['$Type'] == type
131
+ value.each_value { nested(_1, type, result) }
132
+ when Array then value.each { nested(_1, type, result) }
133
+ end
134
+ result
135
+ end
136
+ end
137
+ # rubocop:enable Metrics
138
+ end
139
+ end