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,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ # Compiles microflow-family roots and delegates ordered graph nodes.
8
+ class MicroflowDocumentCompiler
9
+ include ModelValues
10
+ include RuntimeDataTypes
11
+
12
+ ROOT_TYPES = %w[Microflows$Microflow Microflows$Nanoflow Microflows$Rule].freeze
13
+
14
+ def initialize(source, schema)
15
+ @schema = schema
16
+ @nodes = MicroflowNodeCompiler.new(schema, source:)
17
+ @role_map = project_role_map(source)
18
+ end
19
+
20
+ def compile(unit)
21
+ source = unit.document
22
+ raise CompilationError, "unsupported flow root #{source['$Type']}" unless ROOT_TYPES.include?(source['$Type'])
23
+
24
+ @nodes.prepare(source)
25
+ @schema.fields_for(source).to_h do |field|
26
+ [field, root_value(source, field, unit.module_name)]
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def root_value(source, field, module_name) # rubocop:disable Metrics/CyclomaticComplexity
33
+ case field
34
+ when '$ID', '$Type', 'Name' then source[field]
35
+ when 'ObjectCollection' then @nodes.compile(source['ObjectCollection'])
36
+ when 'SequenceFlows' then @nodes.compile(source['Flows'])
37
+ when 'QualifiedName' then "#{module_name}.#{source['Name']}"
38
+ when 'ReturnType' then data_type(source['MicroflowReturnType'])
39
+ when 'ModelerAllowedUserRoles' then allowed_user_roles(source)
40
+ when 'UrlSegments' then []
41
+ else generic_field(source, field)
42
+ end
43
+ end
44
+
45
+ def generic_field(source, field)
46
+ return @nodes.compile(source[field]) if source.key?(field)
47
+
48
+ existing = @schema.counterpart(source)
49
+ return existing[field] if existing&.key?(field)
50
+
51
+ root_default(source, field)
52
+ end
53
+
54
+ def root_default(source, field)
55
+ case field
56
+ when 'ConcurrenyErrorMessage'
57
+ { '$ID' => derived_id(source, field), '$Type' => 'Texts$Text' }
58
+ when 'ApplyEntityAccess', 'AllowConcurrentExecution' then false
59
+ when 'ConcurrencyErrorMicroflow', 'Url' then ''
60
+ when 'UrlSearchParameters' then []
61
+ else raise CompilationError, "cannot derive Runtime root field #{source['$Type']}.#{field}"
62
+ end
63
+ end
64
+
65
+ def allowed_user_roles(flow)
66
+ array(flow['AllowedModuleRoles']).flat_map { @role_map.fetch(_1.to_s, []) }.uniq
67
+ end
68
+
69
+ def project_role_map(source)
70
+ security = source.documents('Security$ProjectSecurity').first
71
+ return {} unless security
72
+
73
+ array(security['UserRoles']).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |role, map|
74
+ array(role['ModuleRoles']).each { |module_role| map[module_role.to_s] << role['Name'].to_s }
75
+ end
76
+ end
77
+
78
+ def derived_id(source, label)
79
+ seed = "#{IO::BsonCodec.extract_id(source['$ID'])}:#{label}"
80
+ hex = Digest::SHA256.hexdigest(seed)[0, 32]
81
+ "#{hex[0, 8]}-#{hex[8, 4]}-4#{hex[13, 3]}-8#{hex[17, 3]}-#{hex[20, 12]}"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ MicroflowMaterialization = Data.define(:model_path, :microflows, :nanoflows, :rules)
6
+
7
+ # Materializes microflows, nanoflows, and rules using the version-matched Runtime schema.
8
+ class MicroflowMaterializer
9
+ TYPES = %w[Microflows$Microflow Microflows$Nanoflow Microflows$Rule].freeze
10
+
11
+ def initialize(mpr_path, deployment:)
12
+ @mpr_path = File.expand_path(mpr_path)
13
+ @deployment = File.expand_path(deployment)
14
+ end
15
+
16
+ def materialize
17
+ source = SourceModel.read(@mpr_path)
18
+ Adapter.for(source.version)
19
+ path = File.join(@deployment, 'model', 'model.mdp')
20
+ package = ModelPackage.read(path)
21
+ documents = compile_documents(source, package)
22
+ write_model(path, package, documents)
23
+ result(path, documents)
24
+ end
25
+
26
+ private
27
+
28
+ def compile_documents(source, package)
29
+ compiler = MicroflowDocumentCompiler.new(
30
+ source, RuntimeModelSchema.new(package, version: source.version)
31
+ )
32
+ source.units.select { TYPES.include?(_1.document['$Type']) }.map { compiler.compile(_1) }
33
+ end
34
+
35
+ def write_model(path, package, documents)
36
+ documents.reduce(package) { |result, document| result.upsert(document) }.write(path)
37
+ end
38
+
39
+ def result(path, documents)
40
+ counts = documents.map { _1['$Type'] }.tally
41
+ MicroflowMaterialization.new(
42
+ model_path: path, microflows: counts.fetch('Microflows$Microflow', 0),
43
+ nanoflows: counts.fetch('Microflows$Nanoflow', 0), rules: counts.fetch('Microflows$Rule', 0)
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity
8
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
9
+ # Recursively compiles ordered objects nested inside microflow graphs.
10
+ class MicroflowNodeCompiler
11
+ include ModelValues
12
+ include RuntimeDataTypes
13
+
14
+ FIELD_SOURCES = {
15
+ ['Microflows$BasicCodeActionParameterValue', 'ValueExpression'] => 'Argument',
16
+ ['Microflows$CreateVariableAction', 'Type'] => 'VariableType',
17
+ ['Microflows$MicroflowParameter', 'Type'] => 'VariableType',
18
+ ['Microflows$MappingRequestHandling', 'ContentTypeRuntime'] => 'ContentType',
19
+ ['Microflows$RetrieveSorting', 'AttributePath'] => 'AttributeRef',
20
+ ['Microflows$RestCallAction', 'RequestTimeOutExpression'] => 'TimeOutExpression',
21
+ ['Microflows$ResultHandling', 'VariableDataType'] => 'VariableType'
22
+ }.freeze
23
+ TYPE_DEFAULTS = {
24
+ 'Microflows$NoCase' => { 'StringRepresentation' => '' }
25
+ }.freeze
26
+ RUNTIME_DERIVED_FIELDS = {
27
+ 'Microflows$ActionActivity' => %w[Caption]
28
+ }.freeze
29
+ EDITORIAL_TYPES = %w[Microflows$AnnotationFlow].freeze
30
+
31
+ def initialize(schema, source: nil)
32
+ @schema = schema
33
+ @database_connector = DatabaseConnectorActionCompiler.new(source) if source.respond_to?(:units)
34
+ @associations = association_index(source)
35
+ @variable_types = {}
36
+ end
37
+
38
+ def prepare(flow)
39
+ @variable_types = {}
40
+ visit(flow) do |node|
41
+ next unless node['$Type'] == 'Microflows$MicroflowParameter'
42
+
43
+ entity = node.dig('VariableType', 'Entity').to_s
44
+ @variable_types[node['Name'].to_s] = entity unless entity.empty?
45
+ end
46
+ end
47
+
48
+ def compile(value)
49
+ case value
50
+ when Hash then compile_hash(value)
51
+ when Array then array(value).filter_map { compile(_1) }
52
+ else value
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def compile_hash(value)
59
+ return value.to_h { |key, child| [key, compile(child)] } unless value['$Type']
60
+ return nil if EDITORIAL_TYPES.include?(value['$Type'])
61
+ return compile_custom_range(value) if value['$Type'] == 'Microflows$CustomRange'
62
+
63
+ if value['$Type'] == 'DatabaseConnector$ExecuteDatabaseQueryAction'
64
+ return compile(noop_database_action(value)) if @database_connector.unconfigured_write?(value)
65
+
66
+ return compile(@database_connector.compile(value))
67
+ end
68
+
69
+ compile_node(value)
70
+ end
71
+
72
+ def compile_custom_range(source)
73
+ {
74
+ '$ID' => derived_id(source, 'constant-range'), '$Type' => 'Microflows$ConstantRange',
75
+ 'SingleObject' => source['SingleObject'] == true
76
+ }
77
+ end
78
+
79
+ def noop_database_action(source)
80
+ {
81
+ '$ID' => source['$ID'], '$Type' => 'Microflows$LogMessageAction',
82
+ 'MessageTemplate' => {
83
+ '$ID' => derived_id(source, 'local-fallback-message'),
84
+ '$Type' => 'Microflows$StringTemplate', 'Parameters' => [], 'Text' => ''
85
+ },
86
+ 'ErrorHandlingType' => source.fetch('ErrorHandlingType', 'Rollback'),
87
+ 'Level' => 'Trace', 'Node' => "'Mxrb'", 'IncludeLatestStackTrace' => false
88
+ }
89
+ end
90
+
91
+ def compile_node(source)
92
+ @schema.fields_for(source).to_h { |field| [field, node_value(source, field)] }
93
+ end
94
+
95
+ def node_value(source, field)
96
+ return source[field] if %w[$ID $Type].include?(field)
97
+ if field == 'Type' && %w[
98
+ Microflows$AssociationRetrieveSource Microflows$DatabaseRetrieveSource
99
+ ].include?(source['$Type'])
100
+ return retrieve_type(source)
101
+ end
102
+ if field == 'StringRepresentation' && source['$Type'] == 'Microflows$InheritanceCase'
103
+ return derived_default(source, field)
104
+ end
105
+ if field == 'ValueExpression' && source['$Type'] == 'Microflows$MicroflowParameterValue'
106
+ return "'#{source.fetch('Microflow')}'"
107
+ end
108
+
109
+ existing = @schema.counterpart(source)
110
+ if RUNTIME_DERIVED_FIELDS.fetch(source['$Type'], []).include?(field) && existing&.key?(field)
111
+ return existing[field]
112
+ end
113
+
114
+ source_field = if source.key?(field)
115
+ field
116
+ else
117
+ FIELD_SOURCES.fetch([source['$Type'], field], field)
118
+ end
119
+ return converted_field(source, source_field, field) if source.key?(source_field)
120
+
121
+ default_or_existing(source, field)
122
+ end
123
+
124
+ def converted_field(source, source_field, runtime_field)
125
+ value = source[source_field]
126
+ return runtime_attribute_path(value) if runtime_field == 'AttributePath'
127
+ return data_type(value) if %w[Type VariableDataType].include?(runtime_field) && data_type_document?(value)
128
+
129
+ compile(value)
130
+ end
131
+
132
+ def runtime_attribute_path(reference)
133
+ attribute = if reference.is_a?(Hash)
134
+ reference.fetch('Attribute', '').to_s
135
+ else
136
+ reference.to_s
137
+ end
138
+ entity = attribute.rpartition('.').first
139
+ entity.empty? ? attribute : "#{entity}/#{attribute}"
140
+ end
141
+
142
+ def default_or_existing(source, field)
143
+ defaults = TYPE_DEFAULTS.fetch(source['$Type'], {})
144
+ return defaults[field] if defaults.key?(field)
145
+
146
+ existing = @schema.counterpart(source)
147
+ return existing[field] if existing&.key?(field)
148
+
149
+ derived_default(source, field)
150
+ end
151
+
152
+ def derived_default(source, field)
153
+ case field
154
+ when 'Argument' then source.dig('Value', 'Argument').to_s
155
+ when 'QueueSettings', 'NewCaseValue' then nil
156
+ when 'Location' then 'Content'
157
+ when 'ParameterMappings' then []
158
+ when 'StringRepresentation'
159
+ return '(empty)' if source['$Type'] == 'Microflows$InheritanceCase' && source['Value'].to_s.empty?
160
+
161
+ source['Value'].to_s
162
+ when 'Type' then derived_runtime_type(source)
163
+ when 'IsRequired', 'Disabled' then source.fetch(field, false) == true
164
+ when 'DefaultValue', 'Caption', 'ResultVariableName', 'OutputVariableName',
165
+ 'FormObjectVariable', 'Queue' then ''
166
+ else raise CompilationError, "cannot derive Runtime field #{source['$Type']}.#{field}"
167
+ end
168
+ end
169
+
170
+ def derived_runtime_type(source)
171
+ if source['$Type'] == 'Microflows$AggregateAction'
172
+ return 'Integer' if source['AggregateFunction'] == 'Count'
173
+
174
+ raise CompilationError,
175
+ "cannot derive #{source['AggregateFunction']} aggregate type without an audited attribute type"
176
+ end
177
+
178
+ retrieve_type(source)
179
+ end
180
+
181
+ def retrieve_type(source)
182
+ return association_retrieve_type(source) if source['$Type'] == 'Microflows$AssociationRetrieveSource'
183
+ unless source['$Type'] == 'Microflows$DatabaseRetrieveSource'
184
+ raise CompilationError, "cannot derive Runtime retrieve type for #{source['$Type']}"
185
+ end
186
+
187
+ single = source.dig('Range', 'SingleObject') == true
188
+ single ? source['Entity'].to_s : "[#{source['Entity']}]"
189
+ end
190
+
191
+ def association_retrieve_type(source)
192
+ association = @associations[source['AssociationId'].to_s] || runtime_association(source['AssociationId'].to_s)
193
+ raise CompilationError, "unknown association #{source['AssociationId'].inspect}" unless association
194
+
195
+ start = @variable_types[source['StartVariableName'].to_s]
196
+ target = start == association[:child] ? association[:parent] : association[:child]
197
+ return if target.to_s.empty?
198
+
199
+ list = association[:reference_set] || start == association[:child]
200
+ list ? "[#{target}]" : target
201
+ end
202
+
203
+ def runtime_association(name)
204
+ association = @schema.respond_to?(:named) && @schema.named(name)
205
+ return unless association
206
+
207
+ parent = @schema.counterpart_id(association['ParentPointer'])
208
+ child = @schema.counterpart_id(association['ChildPointer'])
209
+ {
210
+ parent: parent&.fetch('QualifiedName', nil), child: child&.fetch('QualifiedName', nil),
211
+ reference_set: association['Type'] == 'ReferenceSet'
212
+ }
213
+ end
214
+
215
+ def association_index(source)
216
+ return {} unless source.respond_to?(:units_of)
217
+
218
+ source.units_of('DomainModels$DomainModel').each_with_object({}) do |unit, result|
219
+ entities = array(unit.document['Entities']).to_h do |entity|
220
+ [IO::BsonCodec.extract_id(entity['$ID']), "#{unit.module_name}.#{entity['Name']}"]
221
+ end
222
+ array(unit.document['Associations']).each do |association|
223
+ result["#{unit.module_name}.#{association['Name']}"] = {
224
+ parent: entities[IO::BsonCodec.extract_id(association['ParentPointer'])],
225
+ child: entities[IO::BsonCodec.extract_id(association['ChildPointer'])],
226
+ reference_set: association['Type'] == 'ReferenceSet'
227
+ }
228
+ end
229
+ end
230
+ end
231
+
232
+ def visit(value, &block)
233
+ case value
234
+ when Hash
235
+ block.call(value) if value['$Type']
236
+ value.each_value { visit(_1, &block) }
237
+ when Array then value.each { visit(_1, &block) }
238
+ end
239
+ end
240
+
241
+ def derived_id(source, label)
242
+ seed = "#{IO::BsonCodec.extract_id(source['$ID'])}:#{label}"
243
+ hex = Digest::SHA256.hexdigest(seed)[0, 32]
244
+ "#{hex[0, 8]}-#{hex[8, 4]}-4#{hex[13, 3]}-8#{hex[17, 3]}-#{hex[20, 12]}"
245
+ end
246
+ end
247
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity
248
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
249
+ end
250
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'tmpdir'
5
+
6
+ module Mxrb
7
+ module Compiler
8
+ # Mendix model.mdp is an ordered stream of BSON documents without a
9
+ # container header. Each document carries its own int32 BSON length.
10
+ class ModelPackage
11
+ Entry = Data.define(:offset, :size, :document) do
12
+ def id = IO::BsonCodec.extract_id(document['$ID'])
13
+ def type = document['$Type'].to_s
14
+ end
15
+
16
+ attr_reader :entries
17
+
18
+ def self.read(path)
19
+ bytes = File.binread(path)
20
+ new(parse(bytes, path))
21
+ end
22
+
23
+ def self.empty = new([].freeze)
24
+ def self.decode(bytes, label = 'model.mdp') = new(parse(bytes, label))
25
+
26
+ def self.from_documents(documents)
27
+ new([].freeze).then do |package|
28
+ documents.reduce(package) { |result, document| result.with_appended(document) }
29
+ end
30
+ end
31
+
32
+ def self.parse(bytes, label = 'model.mdp')
33
+ entries = []
34
+ offset = 0
35
+ while offset < bytes.bytesize
36
+ entry = parse_entry(bytes, offset, label)
37
+ entries << entry
38
+ offset += entry.size
39
+ end
40
+ entries.freeze
41
+ rescue SerializationError => e
42
+ raise CompilationError, "#{label}: #{e.message}"
43
+ end
44
+ private_class_method :parse
45
+
46
+ def self.parse_entry(bytes, offset, label)
47
+ size = document_size(bytes, offset, label)
48
+ document = IO::BsonCodec.parse(bytes.byteslice(offset, size))
49
+ Entry.new(offset:, size:, document:)
50
+ end
51
+ private_class_method :parse_entry
52
+
53
+ def self.document_size(bytes, offset, label)
54
+ remaining = bytes.bytesize - offset
55
+ raise CompilationError, "#{label}: truncated BSON length at byte #{offset}" if remaining < 4
56
+
57
+ size = bytes.byteslice(offset, 4).unpack1('l<')
58
+ return size if size.between?(5, remaining)
59
+
60
+ raise CompilationError, "#{label}: invalid BSON size #{size} at byte #{offset}"
61
+ end
62
+ private_class_method :document_size
63
+
64
+ def initialize(entries)
65
+ @entries = entries
66
+ end
67
+
68
+ def documents = entries.map(&:document)
69
+ def types = entries.map(&:type).tally.freeze
70
+ def find(id) = entries.find { _1.id == id }
71
+
72
+ def replace(id, document)
73
+ index = entries.index { _1.id == id }
74
+ raise CompilationError, "model document #{id} not found" unless index
75
+
76
+ updated = entries.map.with_index do |entry, position|
77
+ position == index ? Entry.new(offset: 0, size: 0, document:) : entry
78
+ end
79
+ self.class.new(reindex(updated))
80
+ end
81
+
82
+ def upsert(document)
83
+ id = IO::BsonCodec.extract_id(document['$ID'])
84
+ raise CompilationError, 'model document has no $ID' unless id
85
+ return replace(id, document) if find(id)
86
+
87
+ self.class.new(reindex(entries + [Entry.new(offset: 0, size: 0, document:)]))
88
+ end
89
+
90
+ def with_appended(document)
91
+ self.class.new(reindex(entries + [Entry.new(offset: 0, size: 0, document:)]))
92
+ end
93
+
94
+ def write(path)
95
+ output = File.expand_path(path)
96
+ FileUtils.mkdir_p(File.dirname(output))
97
+ Dir.mktmpdir('mxrb-model-', File.dirname(output)) do |directory|
98
+ temporary = File.join(directory, 'model.mdp')
99
+ File.binwrite(temporary, entries.map { IO::BsonCodec.serialize(_1.document) }.join)
100
+ FileUtils.mv(temporary, output, force: true)
101
+ end
102
+ output
103
+ end
104
+
105
+ private
106
+
107
+ def reindex(source)
108
+ offset = 0
109
+ source.map do |entry|
110
+ size = IO::BsonCodec.serialize(entry.document).bytesize
111
+ Entry.new(offset:, size:, document: entry.document).tap { offset += size }
112
+ end.freeze
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Compiler
5
+ # Normalizes MPR's BSON array convention into Runtime-native values.
6
+ module ModelValues
7
+ private
8
+
9
+ def array(value) = IO::BsonCodec.parse_array(value)[:items]
10
+ def plain_array(value) = value ? array(value).map { plain_value(_1) } : []
11
+ def plain_document(value) = value ? plain_value(value) : nil
12
+
13
+ def xpath_variables(constraint)
14
+ constraint.to_s.scan(/\$([A-Za-z_]\w*)/).flatten.uniq
15
+ end
16
+
17
+ def object_page_parameters(document, names)
18
+ indexed = page_parameter_types(document)
19
+ entities = names.to_h { |name| [name, object_parameter_entity(indexed[name])] }
20
+ entities if entities.values.all?
21
+ end
22
+
23
+ def page_parameter_types(document)
24
+ array(document&.fetch('Parameters', nil)).filter_map do |parameter|
25
+ next unless parameter.is_a?(Hash)
26
+
27
+ [parameter['Name'].to_s, parameter['ParameterType'] || {}]
28
+ end.to_h
29
+ end
30
+
31
+ def object_parameter_entity(type)
32
+ return unless type&.fetch('$Type', nil) == 'DataTypes$ObjectType'
33
+
34
+ entity = type['Entity'].to_s
35
+ entity unless entity.empty?
36
+ end
37
+
38
+ def image_bytes(value)
39
+ value.respond_to?(:data) ? value.data : value.to_s.b
40
+ end
41
+
42
+ def image_format(source) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
43
+ explicit = source['ImageFormat'].to_s.downcase
44
+ return explicit unless explicit.empty?
45
+
46
+ bytes = image_bytes(source['Image'])
47
+ return 'png' if bytes.start_with?("\x89PNG\r\n\x1A\n".b)
48
+ return 'gif' if bytes.start_with?('GIF87a', 'GIF89a')
49
+ return 'jpg' if bytes.start_with?("\xFF\xD8\xFF".b)
50
+ return 'bmp' if bytes.start_with?('BM')
51
+ return 'ico' if bytes.start_with?("\x00\x00\x01\x00".b)
52
+ return 'webp' if bytes.start_with?('RIFF') && bytes.byteslice(8, 4) == 'WEBP'
53
+ return 'svg' if bytes.byteslice(0, 1024).to_s.match?(/<svg\b/i)
54
+
55
+ raise CompilationError, "cannot determine format for image #{source['Name'].inspect}"
56
+ end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
57
+
58
+ def plain_value(value)
59
+ case value
60
+ when Hash then value.to_h { |key, child| [key, plain_value(child)] }
61
+ when Array then array(value).map { plain_value(_1) }
62
+ else value
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end