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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- 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
|