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,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "set"
5
+
6
+ module Mxrb
7
+ module Semantic
8
+ # Plan produced by Extractor#plan. Describes what will be extracted and
9
+ # applies the change atomically when apply! is called.
10
+ class ExtractionPlan
11
+ attr_reader :source, :new_name, :selected_ids
12
+
13
+ def initialize(project:, source:, new_name:, selected_ids:,
14
+ inner_objects:, inner_flows:,
15
+ entry_flow:, exit_flow:,
16
+ source_doc:, source_raw:)
17
+ @project = project
18
+ @source = source
19
+ @new_name = new_name
20
+ @selected_ids = selected_ids
21
+ @inner_objects = inner_objects
22
+ @inner_flows = inner_flows
23
+ @entry_flow = entry_flow
24
+ @exit_flow = exit_flow
25
+ @source_doc = source_doc
26
+ @source_raw = source_raw
27
+ @applied = false
28
+ end
29
+
30
+ def applied? = @applied
31
+
32
+ def changes
33
+ [
34
+ "extract #{@inner_objects.size} activit#{@inner_objects.size == 1 ? 'y' : 'ies'} " \
35
+ "from #{@source.qualified_name} into #{@new_name}",
36
+ "replace selection in #{@source.qualified_name} with call to #{@new_name}"
37
+ ]
38
+ end
39
+
40
+ def apply!
41
+ raise ArgumentError, "extraction plan was already applied" if applied?
42
+
43
+ module_unit_id = find_module_unit_id
44
+ @project.mpr.transaction do
45
+ insert_extracted_microflow!(module_unit_id)
46
+ patch_source_microflow!
47
+ end
48
+ @project.refresh!
49
+ @applied = true
50
+ self
51
+ end
52
+
53
+ private
54
+
55
+ def find_module_unit_id
56
+ mod = @project.modules.find { _1.name == @source.module_name }
57
+ raise ArgumentError, "cannot find module #{@source.module_name.inspect}" unless mod
58
+
59
+ mod.id
60
+ end
61
+
62
+ def insert_extracted_microflow!(module_unit_id)
63
+ local_name = @new_name.split(".", 2).last
64
+ start_id = SecureRandom.uuid
65
+ end_id = SecureRandom.uuid
66
+ first_id = @entry_flow["DestinationPointer"]
67
+ last_id = @exit_flow["OriginPointer"]
68
+
69
+ objects = [
70
+ event_doc(start_id, "Microflows$StartEvent"),
71
+ *@inner_objects,
72
+ event_doc(end_id, "Microflows$EndEvent")
73
+ ]
74
+ flows = [
75
+ simple_flow_doc(start_id, first_id),
76
+ *@inner_flows,
77
+ simple_flow_doc(last_id, end_id)
78
+ ]
79
+
80
+ doc = {
81
+ "$ID" => SecureRandom.uuid,
82
+ "$Type" => "Microflows$Microflow",
83
+ "Name" => local_name,
84
+ "Documentation" => "",
85
+ "AllowConcurrentExecution" => true,
86
+ "MarkAsUsed" => false,
87
+ "Excluded" => false,
88
+ "MicroflowReturnType" => { "$ID" => SecureRandom.uuid, "$Type" => "DataTypes$VoidType" },
89
+ "AllowedModuleRoles" => IO::BsonCodec.build_array([]),
90
+ "ObjectCollection" => {
91
+ "$ID" => SecureRandom.uuid,
92
+ "$Type" => "Microflows$MicroflowObjectCollection",
93
+ "Objects" => IO::BsonCodec.build_array(objects)
94
+ },
95
+ "Flows" => IO::BsonCodec.build_array(flows)
96
+ }
97
+ @project.mpr.insert_unit(
98
+ container_uuid: module_unit_id,
99
+ containment_name: "Documents",
100
+ contents_doc: doc
101
+ )
102
+ end
103
+
104
+ def patch_source_microflow!
105
+ call_id = SecureRandom.uuid
106
+ call_activity = call_activity_doc(call_id)
107
+
108
+ removed_ids = @selected_ids
109
+ origin_id = @entry_flow["OriginPointer"]
110
+ dest_id = @exit_flow["DestinationPointer"]
111
+
112
+ # Rebuild objects: keep non-selected, add call activity
113
+ all_objects = normalize_all(parse_array(@source_doc["ObjectCollection"]["Objects"]))
114
+ kept_objects = all_objects.reject { removed_ids.include?(_1["$ID"]) }
115
+ kept_objects << call_activity
116
+
117
+ # Rebuild flows: drop entry/exit flows and inner flows, add two new ones
118
+ inner_flow_ids = @inner_flows.map { _1["$ID"] }.to_set
119
+ removed_flow_ids = inner_flow_ids + [@entry_flow["$ID"], @exit_flow["$ID"]].compact.to_set
120
+ all_flows = normalize_all(parse_array(@source_doc["Flows"]))
121
+ kept_flows = all_flows.reject { removed_flow_ids.include?(_1["$ID"]) }
122
+ kept_flows << simple_flow_doc(origin_id, call_id)
123
+ kept_flows << simple_flow_doc(call_id, dest_id)
124
+
125
+ patched = @source_doc.merge(
126
+ "ObjectCollection" => @source_doc["ObjectCollection"].merge(
127
+ "Objects" => IO::BsonCodec.build_array(kept_objects)
128
+ ),
129
+ "Flows" => IO::BsonCodec.build_array(kept_flows)
130
+ )
131
+ @project.mpr.update_unit(@source_raw.fetch("UnitID"), patched)
132
+ end
133
+
134
+ def event_doc(id, type)
135
+ { "$ID" => id, "$Type" => type, "RelativeMiddlePoint" => "0;0", "Size" => "20;20" }
136
+ end
137
+
138
+ def simple_flow_doc(from_id, to_id)
139
+ {
140
+ "$ID" => SecureRandom.uuid,
141
+ "$Type" => "Microflows$SequenceFlow",
142
+ "OriginPointer" => from_id,
143
+ "DestinationPointer" => to_id,
144
+ "IsErrorHandler" => false,
145
+ "OriginConnectionIndex" => 1,
146
+ "DestinationConnectionIndex" => 3
147
+ }
148
+ end
149
+
150
+ def call_activity_doc(id)
151
+ {
152
+ "$ID" => id,
153
+ "$Type" => "Microflows$ActionActivity",
154
+ "RelativeMiddlePoint" => "0;0",
155
+ "Size" => "120;60",
156
+ "Action" => {
157
+ "$ID" => SecureRandom.uuid,
158
+ "$Type" => "Microflows$MicroflowCallAction",
159
+ "ErrorHandlingType" => "Rollback",
160
+ "UseReturnVariable" => false,
161
+ "ResultVariableName" => "ReturnValue",
162
+ "MicroflowCall" => {
163
+ "$ID" => SecureRandom.uuid,
164
+ "$Type" => "Microflows$MicroflowCall",
165
+ "Microflow" => @new_name,
166
+ "ParameterMappings" => IO::BsonCodec.build_array([])
167
+ }
168
+ }
169
+ }
170
+ end
171
+
172
+ def parse_array(value)
173
+ IO::BsonCodec.parse_array(value)[:items]
174
+ end
175
+
176
+ POINTER_KEYS_EP = %w[$ID OriginPointer DestinationPointer].freeze
177
+
178
+ def normalize_ids(hash)
179
+ hash.each_with_object({}) do |(k, v), out|
180
+ out[k] = POINTER_KEYS_EP.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
181
+ end
182
+ end
183
+
184
+ def normalize_all(arr)
185
+ arr.map { normalize_ids(_1) }
186
+ end
187
+ end
188
+
189
+ # Extracts a contiguous subgraph of a microflow's activities into a new
190
+ # microflow and replaces the selection with a call to it.
191
+ class Extractor
192
+ FLOW_KINDS = %i[microflow nanoflow].freeze
193
+
194
+ def initialize(project)
195
+ @project = project
196
+ end
197
+
198
+ # Plan an extraction.
199
+ #
200
+ # @param source_name [String] qualified name of the source microflow
201
+ # @param as [String] qualified name for the new extracted microflow
202
+ # @param object_ids [Array] $ID values of the objects to extract
203
+ def plan(source_name, as:, object_ids:)
204
+ source = resolve(source_name)
205
+ raise ArgumentError, "#{source.qualified_name} is not a microflow or nanoflow" \
206
+ unless FLOW_KINDS.include?(source.kind)
207
+
208
+ source_module = source.qualified_name.split(".").first
209
+ new_module = as.split(".").first
210
+ raise ArgumentError, "extracted microflow must be in the same module (#{source_module})" \
211
+ unless new_module == source_module
212
+
213
+ raise ArgumentError, "#{as.inspect} already exists" if @project.find_artifact(as)
214
+ raise ArgumentError, "object_ids cannot be empty" if object_ids.empty?
215
+
216
+ raw = @project.raw_unit(source.unit_id)
217
+ doc = @project.parse_bson(raw)
218
+
219
+ all_objects = parse_array(doc["ObjectCollection"]["Objects"]).map { normalize_ids(_1) }
220
+ all_flows = parse_array(doc["Flows"]).map { normalize_ids(_1) }
221
+ by_id = all_objects.to_h { [_1["$ID"], _1] }
222
+
223
+ selected = Set.new(object_ids.map { IO::BsonCodec.extract_id(_1)&.to_s || _1.to_s })
224
+ missing = selected - by_id.keys.to_set
225
+ raise ArgumentError, "unknown object IDs: #{missing.to_a.join(', ')}" unless missing.empty?
226
+
227
+ # Reject selection of start/end events — those belong to the frame
228
+ all_objects.each do |obj|
229
+ if selected.include?(obj["$ID"]) && %w[
230
+ Microflows$StartEvent Microflows$EndEvent
231
+ ].include?(obj["$Type"])
232
+ raise ArgumentError, "cannot include start/end event in extraction selection"
233
+ end
234
+ end
235
+
236
+ inner_flows = all_flows.select do
237
+ selected.include?(_1["OriginPointer"]) && selected.include?(_1["DestinationPointer"])
238
+ end
239
+ entry_flows = all_flows.select do
240
+ !selected.include?(_1["OriginPointer"]) && selected.include?(_1["DestinationPointer"])
241
+ end
242
+ exit_flows = all_flows.select do
243
+ selected.include?(_1["OriginPointer"]) && !selected.include?(_1["DestinationPointer"])
244
+ end
245
+
246
+ if entry_flows.size != 1 || exit_flows.size != 1
247
+ raise ArgumentError,
248
+ "selection must have exactly one entry point (found #{entry_flows.size}) " \
249
+ "and one exit point (found #{exit_flows.size})"
250
+ end
251
+
252
+ inner_objects = all_objects.select { selected.include?(_1["$ID"]) }
253
+
254
+ ExtractionPlan.new(
255
+ project: @project,
256
+ source: source,
257
+ new_name: as,
258
+ selected_ids: selected,
259
+ inner_objects: inner_objects,
260
+ inner_flows: inner_flows,
261
+ entry_flow: entry_flows.first,
262
+ exit_flow: exit_flows.first,
263
+ source_doc: doc,
264
+ source_raw: raw
265
+ )
266
+ end
267
+
268
+ private
269
+
270
+ def resolve(name)
271
+ artifact = @project.find_artifact(name)
272
+ return artifact if artifact
273
+
274
+ raise KeyError, "unknown Mendix artifact #{name.inspect}"
275
+ end
276
+
277
+ def parse_array(value)
278
+ IO::BsonCodec.parse_array(value)[:items]
279
+ end
280
+
281
+ POINTER_KEYS = %w[$ID OriginPointer DestinationPointer].freeze
282
+
283
+ def normalize_ids(hash)
284
+ hash.each_with_object({}) do |(k, v), out|
285
+ out[k] = POINTER_KEYS.include?(k) ? (IO::BsonCodec.extract_id(v) || v) : v
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end