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,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "set"
5
+
6
+ module Mxrb
7
+ module Semantic
8
+ class InlinePlan
9
+ attr_reader :source, :called_name, :call_id
10
+
11
+ def initialize(project:, source:, called_name:, call_id:,
12
+ inner_objects:, inner_flows:,
13
+ entry_flow:, exit_flow:,
14
+ source_doc:, source_raw:)
15
+ @project = project
16
+ @source = source
17
+ @called_name = called_name
18
+ @call_id = call_id
19
+ @inner_objects = inner_objects
20
+ @inner_flows = inner_flows
21
+ @entry_flow = entry_flow
22
+ @exit_flow = exit_flow
23
+ @source_doc = source_doc
24
+ @source_raw = source_raw
25
+ @applied = false
26
+ end
27
+
28
+ def applied? = @applied
29
+
30
+ def changes
31
+ count = @inner_objects.size
32
+ suffix = count == 1 ? "y" : "ies"
33
+ [
34
+ "inline #{@called_name} into #{@source.qualified_name}",
35
+ "replace call_microflow activity with #{count} inlined activit#{suffix}"
36
+ ]
37
+ end
38
+
39
+ def apply!
40
+ raise ArgumentError, "inline plan was already applied" if applied?
41
+
42
+ @project.mpr.transaction { patch_source_microflow! }
43
+ @project.refresh!
44
+ @applied = true
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ def patch_source_microflow!
51
+ # Remap every inlined object's UUID to avoid collisions with existing caller objects
52
+ id_map = @inner_objects.to_h { [_1["$ID"], SecureRandom.uuid] }
53
+
54
+ remapped_objects = @inner_objects.map { |obj| obj.merge("$ID" => id_map.fetch(obj["$ID"])) }
55
+ remapped_flows = @inner_flows.map do |flow|
56
+ flow.merge(
57
+ "$ID" => SecureRandom.uuid,
58
+ "OriginPointer" => id_map.fetch(flow["OriginPointer"], flow["OriginPointer"]),
59
+ "DestinationPointer" => id_map.fetch(flow["DestinationPointer"], flow["DestinationPointer"])
60
+ )
61
+ end
62
+
63
+ first_id = id_map.fetch(@entry_flow["DestinationPointer"])
64
+ last_id = id_map.fetch(@exit_flow["OriginPointer"])
65
+ origin_id = @entry_flow["OriginPointer"]
66
+ dest_id = @exit_flow["DestinationPointer"]
67
+
68
+ all_objects = normalize_all(parse_array(@source_doc["ObjectCollection"]["Objects"]))
69
+ kept_objects = all_objects.reject { _1["$ID"] == @call_id }
70
+ kept_objects.concat(remapped_objects)
71
+
72
+ removed_flow_ids = Set.new([@entry_flow["$ID"], @exit_flow["$ID"]].compact)
73
+ all_flows = normalize_all(parse_array(@source_doc["Flows"]))
74
+ kept_flows = all_flows.reject { removed_flow_ids.include?(_1["$ID"]) }
75
+ kept_flows << simple_flow_doc(origin_id, first_id)
76
+ kept_flows << simple_flow_doc(last_id, dest_id)
77
+ kept_flows.concat(remapped_flows)
78
+
79
+ patched = @source_doc.merge(
80
+ "ObjectCollection" => @source_doc["ObjectCollection"].merge(
81
+ "Objects" => IO::BsonCodec.build_array(kept_objects)
82
+ ),
83
+ "Flows" => IO::BsonCodec.build_array(kept_flows)
84
+ )
85
+ @project.mpr.update_unit(@source_raw.fetch("UnitID"), patched)
86
+ end
87
+
88
+ def simple_flow_doc(from_id, to_id)
89
+ {
90
+ "$ID" => SecureRandom.uuid,
91
+ "$Type" => "Microflows$SequenceFlow",
92
+ "OriginPointer" => from_id,
93
+ "DestinationPointer" => to_id,
94
+ "IsErrorHandler" => false,
95
+ "OriginConnectionIndex" => 1,
96
+ "DestinationConnectionIndex" => 3
97
+ }
98
+ end
99
+
100
+ POINTER_KEYS = %w[$ID OriginPointer DestinationPointer].freeze
101
+
102
+ def normalize_ids(hash)
103
+ hash.each_with_object({}) do |(k, v), out|
104
+ out[k] = POINTER_KEYS.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
105
+ end
106
+ end
107
+
108
+ def normalize_all(arr) = arr.map { normalize_ids(_1) }
109
+ def parse_array(value) = IO::BsonCodec.parse_array(value)[:items]
110
+ end
111
+
112
+ # Inlines a called microflow back into its caller, replacing the
113
+ # call_microflow activity with the callee's activities.
114
+ class Inliner
115
+ FLOW_KINDS = %i[microflow nanoflow].freeze
116
+
117
+ def initialize(project)
118
+ @project = project
119
+ end
120
+
121
+ # @param source_name [String] qualified name of the caller microflow
122
+ # @param calling [String] qualified name of the microflow to inline
123
+ def plan(source_name, calling:)
124
+ source = resolve(source_name)
125
+ raise ArgumentError, "#{source.qualified_name} is not a microflow or nanoflow" \
126
+ unless FLOW_KINDS.include?(source.kind)
127
+
128
+ called = resolve(calling)
129
+ raise ArgumentError, "#{called.qualified_name} is not a microflow or nanoflow" \
130
+ unless FLOW_KINDS.include?(called.kind)
131
+
132
+ source_raw = @project.raw_unit(source.unit_id)
133
+ source_doc = @project.parse_bson(source_raw)
134
+
135
+ all_objects = normalize_all(parse_array(source_doc["ObjectCollection"]["Objects"]))
136
+ all_flows = normalize_all(parse_array(source_doc["Flows"]))
137
+
138
+ call_activity = all_objects.find do |obj|
139
+ obj["$Type"] == "Microflows$ActionActivity" &&
140
+ obj.dig("Action", "MicroflowCall", "Microflow") == calling
141
+ end
142
+ raise ArgumentError, "no call to #{calling.inspect} found in #{source_name.inspect}" \
143
+ unless call_activity
144
+
145
+ call_id = call_activity["$ID"]
146
+ entry_flow = all_flows.find { _1["DestinationPointer"] == call_id }
147
+ exit_flow = all_flows.find { _1["OriginPointer"] == call_id }
148
+ raise ArgumentError, "cannot find entry flow for call activity" unless entry_flow
149
+ raise ArgumentError, "cannot find exit flow for call activity" unless exit_flow
150
+
151
+ called_raw = @project.raw_unit(called.unit_id)
152
+ called_doc = @project.parse_bson(called_raw)
153
+
154
+ called_objects = normalize_all(parse_array(called_doc["ObjectCollection"]["Objects"]))
155
+ called_flows = normalize_all(parse_array(called_doc["Flows"]))
156
+
157
+ start_event = called_objects.find { _1["$Type"] == "Microflows$StartEvent" }
158
+ end_event = called_objects.find { _1["$Type"] == "Microflows$EndEvent" }
159
+ raise ArgumentError, "#{calling.inspect} has no start event" unless start_event
160
+ raise ArgumentError, "#{calling.inspect} has no end event" unless end_event
161
+
162
+ start_id = start_event["$ID"]
163
+ end_id = end_event["$ID"]
164
+
165
+ inner_objects = called_objects.reject { [start_id, end_id].include?(_1["$ID"]) }
166
+ raise ArgumentError, "#{calling.inspect} has no activities to inline" if inner_objects.empty?
167
+
168
+ inner_flows = called_flows.reject do |f|
169
+ [start_id, end_id].include?(f["OriginPointer"]) ||
170
+ [start_id, end_id].include?(f["DestinationPointer"])
171
+ end
172
+
173
+ start_to_first = called_flows.find { _1["OriginPointer"] == start_id }
174
+ last_to_end = called_flows.find { _1["DestinationPointer"] == end_id }
175
+ raise ArgumentError, "cannot resolve first activity in #{calling.inspect}" unless start_to_first
176
+ raise ArgumentError, "cannot resolve last activity in #{calling.inspect}" unless last_to_end
177
+
178
+ # Synthesise entry/exit flows that point at the actual first/last inlined objects
179
+ synthetic_entry = entry_flow.merge("DestinationPointer" => start_to_first["DestinationPointer"])
180
+ synthetic_exit = exit_flow.merge("OriginPointer" => last_to_end["OriginPointer"])
181
+
182
+ InlinePlan.new(
183
+ project: @project,
184
+ source: source,
185
+ called_name: calling,
186
+ call_id: call_id,
187
+ inner_objects: inner_objects,
188
+ inner_flows: inner_flows,
189
+ entry_flow: synthetic_entry,
190
+ exit_flow: synthetic_exit,
191
+ source_doc: source_doc,
192
+ source_raw: source_raw
193
+ )
194
+ end
195
+
196
+ private
197
+
198
+ def resolve(name)
199
+ artifact = @project.find_artifact(name)
200
+ raise KeyError, "unknown Mendix artifact #{name.inspect}" unless artifact
201
+ artifact
202
+ end
203
+
204
+ POINTER_KEYS = %w[$ID OriginPointer DestinationPointer].freeze
205
+
206
+ def normalize_ids(hash)
207
+ hash.each_with_object({}) do |(k, v), out|
208
+ out[k] = POINTER_KEYS.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
209
+ end
210
+ end
211
+
212
+ def normalize_all(arr) = arr.map { normalize_ids(_1) }
213
+ def parse_array(value) = IO::BsonCodec.parse_array(value)[:items]
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Semantic
5
+ class MovePlan
6
+ attr_reader :source, :target, :before_container, :containment_name
7
+
8
+ def initialize(project:, source:, target:, before_container:, containment_name:)
9
+ @project = project
10
+ @source = source
11
+ @target = target
12
+ @before_container = before_container
13
+ @containment_name = containment_name
14
+ @applied = false
15
+ end
16
+
17
+ def after_container = target.unit_id
18
+ def empty? = before_container == after_container
19
+ def applied? = @applied
20
+
21
+ def apply!
22
+ raise ArgumentError, "move plan was already applied" if applied?
23
+
24
+ unless empty?
25
+ @project.mpr.transaction do
26
+ @project.mpr.relocate_unit(
27
+ source.unit_id,
28
+ container_uuid: after_container,
29
+ containment_name:
30
+ )
31
+ end
32
+ end
33
+ @project.refresh!
34
+ @applied = true
35
+ self
36
+ end
37
+ end
38
+
39
+ # Cross-module move: relocates a unit AND renames its qualified name across
40
+ # the project so that all references stay consistent.
41
+ class CrossModuleMovePlan
42
+ attr_reader :source, :target, :before_container, :rename_changes
43
+
44
+ def initialize(project:, source:, target:, rename_plan:, before_container:, containment_name:)
45
+ @project = project
46
+ @source = source
47
+ @target = target
48
+ @rename_plan = rename_plan
49
+ @before_container = before_container
50
+ @containment_name = containment_name
51
+ @applied = false
52
+ end
53
+
54
+ def after_container = target.unit_id
55
+ def empty? = false
56
+ def rename_changes = @rename_plan.changes
57
+ def applied? = @applied
58
+
59
+ def apply!
60
+ raise ArgumentError, "cross-module move plan was already applied" if applied?
61
+
62
+ @project.mpr.transaction do
63
+ @rename_plan.documents.each do |unit_id, doc|
64
+ @project.mpr.update_unit(unit_id, doc)
65
+ end
66
+ @project.mpr.relocate_unit(
67
+ @source.unit_id,
68
+ container_uuid: @target.unit_id,
69
+ containment_name: @containment_name
70
+ )
71
+ end
72
+ @project.refresh!
73
+ @applied = true
74
+ self
75
+ end
76
+ end
77
+
78
+ class Mover
79
+ EMBEDDED_KINDS = %i[module entity attribute association].freeze
80
+ CONTAINER_KINDS = %i[module folder].freeze
81
+
82
+ def initialize(project)
83
+ @project = project
84
+ end
85
+
86
+ def plan(name, to:)
87
+ source = resolve(name)
88
+ target = resolve(to)
89
+ validate_kinds(source, target)
90
+
91
+ raw = @project.raw_unit(source.unit_id)
92
+ raise ArgumentError, "#{source.qualified_name} is not a movable unit" unless raw
93
+ if descendant_ids(source.unit_id).include?(target.unit_id)
94
+ raise ArgumentError, "cannot move #{source.qualified_name} into its descendant"
95
+ end
96
+
97
+ if source.module_name != target.module_name
98
+ plan_cross_module(source, target, raw)
99
+ else
100
+ MovePlan.new(
101
+ project: @project,
102
+ source:,
103
+ target:,
104
+ before_container: raw.fetch("ContainerID"),
105
+ containment_name: raw.fetch("ContainmentName")
106
+ )
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def plan_cross_module(source, target, raw)
113
+ new_name = "#{target.module_name}.#{source.name}"
114
+ rename_plan = Renamer.new(@project).plan(source.qualified_name, to: new_name, cross_module: true)
115
+ CrossModuleMovePlan.new(
116
+ project: @project,
117
+ source:,
118
+ target:,
119
+ rename_plan:,
120
+ before_container: raw.fetch("ContainerID"),
121
+ containment_name: raw.fetch("ContainmentName")
122
+ )
123
+ end
124
+
125
+ def resolve(name)
126
+ artifact = @project.find_artifact(name)
127
+ return artifact if artifact
128
+
129
+ matches = @project.semantic_index.find_all(name)
130
+ raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
131
+ raise ArgumentError, "ambiguous Mendix artifact #{name.inspect}" if matches.size > 1
132
+
133
+ matches.first
134
+ end
135
+
136
+ def validate_kinds(source, target)
137
+ if EMBEDDED_KINDS.include?(source.kind)
138
+ raise ArgumentError, "#{source.kind} cannot be moved as a standalone unit"
139
+ end
140
+ return if CONTAINER_KINDS.include?(target.kind)
141
+
142
+ raise ArgumentError, "#{target.qualified_name} is not a module or folder"
143
+ end
144
+
145
+ def descendant_ids(unit_id)
146
+ direct = @project.children_of(unit_id)
147
+ direct.flat_map do |child|
148
+ [child.fetch("UnitID"), *descendant_ids(child.fetch("UnitID"))]
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Semantic
5
+ # Optional sentence-transformer backend powered by the informers gem.
6
+ class OnnxEmbedder
7
+ MODEL = 'sentence-transformers/all-MiniLM-L6-v2'
8
+ MODEL_REVISION = '1110a243fdf4706b3f48f1d95db1a4f5529b4d41'
9
+
10
+ def self.available?
11
+ require 'informers'
12
+ true
13
+ rescue LoadError
14
+ false
15
+ end
16
+
17
+ def initialize(pipeline: nil)
18
+ require 'informers' unless pipeline
19
+ @pipeline = pipeline || Informers.pipeline(
20
+ 'embedding', MODEL, revision: MODEL_REVISION
21
+ )
22
+ @dimension = nil
23
+ end
24
+
25
+ def backend = :onnx
26
+ def dimension = (@dimension ||= embed('warmup').size)
27
+
28
+ def embed(text)
29
+ vector = @pipeline.call(text)
30
+ vector.first.is_a?(Array) ? vector.first : vector
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Semantic
5
+ class RemovalPlan
6
+ attr_reader :artifact, :incoming, :children
7
+
8
+ def initialize(project:, artifact:, incoming:, children:)
9
+ @project = project
10
+ @artifact = artifact
11
+ @incoming = incoming.freeze
12
+ @children = children.freeze
13
+ @applied = false
14
+ end
15
+
16
+ def safe? = incoming.empty? && children.empty?
17
+ def applied? = @applied
18
+
19
+ def apply!
20
+ raise ArgumentError, "removal plan was already applied" if applied?
21
+ unless safe?
22
+ raise ArgumentError,
23
+ "cannot remove #{artifact.qualified_name}: " \
24
+ "#{incoming.size} incoming reference(s), #{children.size} child unit(s)"
25
+ end
26
+
27
+ @project.mpr.transaction { @project.mpr.delete_unit(artifact.unit_id) }
28
+ @project.refresh!
29
+ @applied = true
30
+ self
31
+ end
32
+ end
33
+
34
+ class Remover
35
+ EMBEDDED_KINDS = %i[module entity attribute association].freeze
36
+
37
+ def initialize(project)
38
+ @project = project
39
+ end
40
+
41
+ def plan(name)
42
+ artifact = resolve(name)
43
+ if EMBEDDED_KINDS.include?(artifact.kind)
44
+ raise ArgumentError,
45
+ "#{artifact.kind} removal requires a typed domain-model mutation"
46
+ end
47
+ raw = @project.raw_unit(artifact.unit_id)
48
+ raise ArgumentError, "#{artifact.qualified_name} is not a removable unit" unless raw
49
+
50
+ incoming = @project.references_to(artifact)
51
+ .reject { _1.source.id == artifact.id }
52
+ children = @project.children_of(artifact.unit_id)
53
+ RemovalPlan.new(project: @project, artifact:, incoming:, children:)
54
+ end
55
+
56
+ private
57
+
58
+ def resolve(name)
59
+ artifact = @project.find_artifact(name)
60
+ return artifact if artifact
61
+
62
+ matches = @project.semantic_index.find_all(name)
63
+ raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
64
+ raise ArgumentError, "ambiguous Mendix artifact #{name.inspect}" if matches.size > 1
65
+
66
+ matches.first
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mxrb
4
+ module Semantic
5
+ RenameChange = Data.define(:unit_id, :artifact, :path, :before, :after)
6
+
7
+ # Immutable preview plus the prepared BSON documents needed for an explicit
8
+ # apply. Construct plans through Project#plan_rename.
9
+ class RenamePlan
10
+ attr_reader :source, :target, :changes, :documents, :architecture
11
+
12
+ def initialize(project:, source:, target:, changes:, documents:, architecture: nil)
13
+ @project = project
14
+ @source = source
15
+ @target = target
16
+ @changes = changes.freeze
17
+ @documents = documents.freeze
18
+ @architecture = architecture
19
+ @applied = false
20
+ end
21
+
22
+ def empty? = @changes.empty?
23
+ def applied? = @applied
24
+ def affected_units = @documents.keys.freeze
25
+
26
+ def apply!
27
+ raise ArgumentError, "rename plan was already applied" if @applied
28
+
29
+ @project.mpr.transaction do
30
+ @documents.each { |unit_id, document| @project.mpr.update_unit(unit_id, document) }
31
+ @project.mpr.write_architecture_definition(@architecture) if @architecture
32
+ end
33
+ @project.refresh!
34
+ @applied = true
35
+ self
36
+ end
37
+ end
38
+
39
+ # Plans and applies model-wide renames without introducing a query
40
+ # language. Every changed BSON string is visible in the Ruby preview.
41
+ class Renamer
42
+ METADATA_FIELDS = %w[Documentation documentation Caption caption Text text].freeze
43
+
44
+ def initialize(project)
45
+ @project = project
46
+ end
47
+
48
+ def plan(source_name, to:, cross_module: false)
49
+ source = resolve(source_name)
50
+ target = qualified_target(source, to, cross_module: cross_module)
51
+ reject_collision(source, target)
52
+
53
+ changes = []
54
+ documents = {}
55
+ @project.all_units.each do |raw|
56
+ original = @project.parse_bson(raw)
57
+ transformed = transform(
58
+ original, source:, target:, unit_id: raw.fetch("UnitID"),
59
+ target_unit: source.unit_id == raw.fetch("UnitID"), changes:
60
+ )
61
+ documents[raw.fetch("UnitID")] = transformed unless transformed.equal?(original)
62
+ end
63
+ original_architecture = @project.architecture_definition
64
+ architecture = if original_architecture
65
+ transform(
66
+ original_architecture, source:, target:, unit_id: "architecture",
67
+ target_unit: false, changes:
68
+ )
69
+ end
70
+
71
+ RenamePlan.new(
72
+ project: @project, source:, target:, changes:, documents:, architecture:
73
+ )
74
+ end
75
+
76
+ private
77
+
78
+ def resolve(name)
79
+ artifact = @project.find_artifact(name)
80
+ return artifact if artifact
81
+
82
+ matches = @project.semantic_index.find_all(name)
83
+ raise KeyError, "unknown Mendix artifact #{name.inspect}" if matches.empty?
84
+
85
+ kinds = matches.map(&:kind).uniq.join(", ")
86
+ raise ArgumentError, "ambiguous Mendix artifact #{name.inspect} (#{kinds})"
87
+ end
88
+
89
+ def qualified_target(source, value, cross_module: false)
90
+ requested = value.to_s
91
+ raise ArgumentError, "new name cannot be empty" if requested.empty?
92
+ unless requested.match?(/\A[A-Za-z_][A-Za-z0-9_.]*\z/)
93
+ raise ArgumentError, "invalid Mendix name #{requested.inspect}"
94
+ end
95
+
96
+ old_parts = source.qualified_name.split(".")
97
+ new_parts = requested.split(".")
98
+ target = if new_parts.one?
99
+ (old_parts[0...-1] + new_parts).join(".")
100
+ else
101
+ requested
102
+ end
103
+ unless target.split(".").length == old_parts.length
104
+ raise ArgumentError, "#{source.kind} rename must keep its qualification depth"
105
+ end
106
+ if !cross_module && old_parts.length > 1 && target.split(".")[0...-1] != old_parts[0...-1]
107
+ raise ArgumentError, "#{source.kind} rename cannot move the artifact to another container"
108
+ end
109
+ target
110
+ end
111
+
112
+ def reject_collision(source, target)
113
+ collisions = @project.semantic_index.find_all(target)
114
+ .reject { _1.id == source.id }
115
+ return if collisions.empty?
116
+
117
+ raise ArgumentError, "Mendix artifact #{target.inspect} already exists"
118
+ end
119
+
120
+ def transform(
121
+ value, source:, target:, unit_id:, target_unit:, changes:, path: [],
122
+ target_object: false
123
+ )
124
+ case value
125
+ when Hash
126
+ transformed_hash(
127
+ value, source:, target:, unit_id:, target_unit:, changes:, path:,
128
+ target_object:
129
+ )
130
+ when Array
131
+ transformed_array(
132
+ value, source:, target:, unit_id:, target_unit:, changes:, path:,
133
+ target_object:
134
+ )
135
+ when String
136
+ transformed_string(
137
+ value, source:, target:, target_object:, field: path.last
138
+ ).tap do |replacement|
139
+ next if replacement == value
140
+
141
+ changes << RenameChange.new(
142
+ unit_id, source, path.map(&:to_s).freeze, value, replacement
143
+ )
144
+ end
145
+ else
146
+ value
147
+ end
148
+ end
149
+
150
+ def transformed_hash(value, **context)
151
+ object_id = IO::BsonCodec.extract_id(value["$ID"])
152
+ target_object = context.fetch(:target_unit) &&
153
+ object_id == artifact_object_id(context.fetch(:source))
154
+ changed = false
155
+ result = value.each_with_object({}) do |(key, child), hash|
156
+ transformed = transform(
157
+ child, **context, target_object:, path: context.fetch(:path) + [key]
158
+ )
159
+ changed ||= !transformed.equal?(child)
160
+ hash[key] = transformed
161
+ end
162
+ changed ? result : value
163
+ end
164
+
165
+ def transformed_array(value, **context)
166
+ changed = false
167
+ result = value.each_with_index.map do |child, index|
168
+ transformed = transform(
169
+ child, **context, target_object: false,
170
+ path: context.fetch(:path) + [index]
171
+ )
172
+ changed ||= !transformed.equal?(child)
173
+ transformed
174
+ end
175
+ changed ? result : value
176
+ end
177
+
178
+ def transformed_string(value, source:, target:, target_object:, field:)
179
+ return value if METADATA_FIELDS.include?(field.to_s)
180
+
181
+ replacement = replace_qualified(value, source.qualified_name, target)
182
+ return replacement unless replacement == value
183
+ return value unless target_object && declaration_field?(field)
184
+
185
+ value == source.name ? target.split(".").last : value
186
+ end
187
+
188
+ def artifact_object_id(artifact)
189
+ artifact.id.sub(/\A(?:module|entity|attribute|association|unit):/, "")
190
+ end
191
+
192
+ def replace_qualified(value, source, target)
193
+ pairs = [[source, target]]
194
+ if source.count(".") == 2
195
+ pairs << [source.sub(/\.[^.]+\z/, "/\\0").sub("/.", "/"),
196
+ target.sub(/\.[^.]+\z/, "/\\0").sub("/.", "/")]
197
+ end
198
+ pairs.reduce(value) do |current, (old_name, new_name)|
199
+ current.gsub(
200
+ /(?<![A-Za-z0-9_])#{Regexp.escape(old_name)}(?![A-Za-z0-9_])/,
201
+ new_name
202
+ )
203
+ end
204
+ end
205
+
206
+ def declaration_field?(field)
207
+ %w[Name name $QualifiedName].include?(field.to_s)
208
+ end
209
+ end
210
+ end
211
+ end