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,419 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Semantic
|
|
7
|
+
# Plan that adds a new attribute to an existing entity.
|
|
8
|
+
class AddAttributePlan
|
|
9
|
+
attr_reader :changes
|
|
10
|
+
|
|
11
|
+
def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:, attribute_def:)
|
|
12
|
+
@project = project
|
|
13
|
+
@module_name = module_name
|
|
14
|
+
@entity_name = entity_name
|
|
15
|
+
@entity_id = entity_id
|
|
16
|
+
@domain_unit_id = domain_unit_id
|
|
17
|
+
@attribute_def = attribute_def
|
|
18
|
+
@applied = false
|
|
19
|
+
@changes = ["add #{attribute_def[:name]} (#{attribute_def[:type]}) to " \
|
|
20
|
+
"#{module_name}.#{entity_name}"].freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty? = false
|
|
24
|
+
def applied? = @applied
|
|
25
|
+
|
|
26
|
+
def apply!
|
|
27
|
+
raise ArgumentError, "plan was already applied" if @applied
|
|
28
|
+
|
|
29
|
+
@project.mpr.transaction do
|
|
30
|
+
raw = @project.raw_unit(@domain_unit_id)
|
|
31
|
+
raise ArgumentError, "domain model unit not found" unless raw
|
|
32
|
+
|
|
33
|
+
doc = @project.parse_bson(raw)
|
|
34
|
+
entities = DomainMutator.extract_entities(doc)
|
|
35
|
+
entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
|
|
36
|
+
raise ArgumentError, "entity #{@entity_name.inspect} not found in domain model" unless entity_doc
|
|
37
|
+
|
|
38
|
+
attrs = DomainMutator.extract_attributes(entity_doc)
|
|
39
|
+
existing = attrs.map { (_1["name"] || _1["Name"]).to_s }
|
|
40
|
+
raise ArgumentError, "attribute #{@attribute_def[:name]} already exists on #{@entity_name}" \
|
|
41
|
+
if existing.include?(@attribute_def[:name].to_s)
|
|
42
|
+
|
|
43
|
+
new_attr = Model::Attribute.new
|
|
44
|
+
new_attr.id = SecureRandom.uuid
|
|
45
|
+
new_attr.name = @attribute_def[:name].to_s
|
|
46
|
+
new_attr.documentation = @attribute_def.fetch(:documentation, "").to_s
|
|
47
|
+
new_attr.type = @attribute_def.fetch(:type, :string).to_sym
|
|
48
|
+
new_attr.default_value = @attribute_def[:default]&.to_s || ""
|
|
49
|
+
new_attr.data_storage_guid = SecureRandom.uuid
|
|
50
|
+
new_attr.export_level = "Hidden"
|
|
51
|
+
|
|
52
|
+
DomainMutator.put_attributes(entity_doc, attrs + [new_attr.to_bson])
|
|
53
|
+
DomainMutator.put_entity(doc, @entity_id, entity_doc)
|
|
54
|
+
@project.mpr.update_unit(@domain_unit_id, doc)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@project.refresh!
|
|
58
|
+
@applied = true
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Plan that removes an attribute from an entity, with incoming reference check.
|
|
64
|
+
class RemoveAttributePlan
|
|
65
|
+
attr_reader :attribute_name, :incoming, :changes
|
|
66
|
+
|
|
67
|
+
def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:,
|
|
68
|
+
attribute_name:, incoming:)
|
|
69
|
+
@project = project
|
|
70
|
+
@module_name = module_name
|
|
71
|
+
@entity_name = entity_name
|
|
72
|
+
@entity_id = entity_id
|
|
73
|
+
@domain_unit_id = domain_unit_id
|
|
74
|
+
@attribute_name = attribute_name
|
|
75
|
+
@incoming = incoming.freeze
|
|
76
|
+
@applied = false
|
|
77
|
+
@changes = ["remove #{module_name}.#{entity_name}/#{attribute_name}"].freeze
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def safe? = @incoming.empty?
|
|
81
|
+
def empty? = false
|
|
82
|
+
def applied? = @applied
|
|
83
|
+
|
|
84
|
+
def apply!
|
|
85
|
+
raise ArgumentError, "plan was already applied" if @applied
|
|
86
|
+
unless safe?
|
|
87
|
+
raise ArgumentError,
|
|
88
|
+
"cannot remove #{@module_name}.#{@entity_name}/#{@attribute_name}: " \
|
|
89
|
+
"#{@incoming.size} incoming reference(s)"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
@project.mpr.transaction do
|
|
93
|
+
raw = @project.raw_unit(@domain_unit_id)
|
|
94
|
+
raise ArgumentError, "domain model unit not found" unless raw
|
|
95
|
+
|
|
96
|
+
doc = @project.parse_bson(raw)
|
|
97
|
+
entities = DomainMutator.extract_entities(doc)
|
|
98
|
+
entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
|
|
99
|
+
raise ArgumentError, "entity not found" unless entity_doc
|
|
100
|
+
|
|
101
|
+
attrs = DomainMutator.extract_attributes(entity_doc)
|
|
102
|
+
before_size = attrs.size
|
|
103
|
+
attrs.reject! { (_1["name"] || _1["Name"]).to_s == @attribute_name }
|
|
104
|
+
raise ArgumentError, "attribute #{@attribute_name.inspect} not found on #{@entity_name}" \
|
|
105
|
+
if attrs.size == before_size
|
|
106
|
+
|
|
107
|
+
DomainMutator.put_attributes(entity_doc, attrs)
|
|
108
|
+
DomainMutator.put_entity(doc, @entity_id, entity_doc)
|
|
109
|
+
@project.mpr.update_unit(@domain_unit_id, doc)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
@project.refresh!
|
|
113
|
+
@applied = true
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Plan that changes an attribute's type, default value, or documentation.
|
|
119
|
+
class ChangeAttributePlan
|
|
120
|
+
attr_reader :attribute_name, :updates, :changes
|
|
121
|
+
|
|
122
|
+
def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:,
|
|
123
|
+
attribute_name:, updates:)
|
|
124
|
+
@project = project
|
|
125
|
+
@module_name = module_name
|
|
126
|
+
@entity_name = entity_name
|
|
127
|
+
@entity_id = entity_id
|
|
128
|
+
@domain_unit_id = domain_unit_id
|
|
129
|
+
@attribute_name = attribute_name
|
|
130
|
+
@updates = updates.freeze
|
|
131
|
+
@applied = false
|
|
132
|
+
desc = updates.map { |k, v| "#{k}: #{v}" }.join(", ")
|
|
133
|
+
@changes = ["change #{module_name}.#{entity_name}/#{attribute_name} (#{desc})"].freeze
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def empty? = false
|
|
137
|
+
def applied? = @applied
|
|
138
|
+
|
|
139
|
+
def apply!
|
|
140
|
+
raise ArgumentError, "plan was already applied" if @applied
|
|
141
|
+
|
|
142
|
+
@project.mpr.transaction do
|
|
143
|
+
raw = @project.raw_unit(@domain_unit_id)
|
|
144
|
+
raise ArgumentError, "domain model unit not found" unless raw
|
|
145
|
+
|
|
146
|
+
doc = @project.parse_bson(raw)
|
|
147
|
+
entities = DomainMutator.extract_entities(doc)
|
|
148
|
+
entity_doc = entities.find { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
|
|
149
|
+
raise ArgumentError, "entity not found" unless entity_doc
|
|
150
|
+
|
|
151
|
+
attrs = DomainMutator.extract_attributes(entity_doc)
|
|
152
|
+
attr_doc = attrs.find { (_1["name"] || _1["Name"]).to_s == @attribute_name }
|
|
153
|
+
raise ArgumentError, "attribute #{@attribute_name.inspect} not found on #{@entity_name}" unless attr_doc
|
|
154
|
+
|
|
155
|
+
attr_model = Model::Attribute.from_bson(attr_doc)
|
|
156
|
+
attr_model.type = @updates[:type].to_sym if @updates.key?(:type)
|
|
157
|
+
attr_model.default_value = @updates[:default].to_s if @updates.key?(:default)
|
|
158
|
+
attr_model.documentation = @updates[:documentation].to_s if @updates.key?(:documentation)
|
|
159
|
+
|
|
160
|
+
new_attrs = attrs.map { (_1["name"] || _1["Name"]).to_s == @attribute_name ? attr_model.to_bson : _1 }
|
|
161
|
+
DomainMutator.put_attributes(entity_doc, new_attrs)
|
|
162
|
+
DomainMutator.put_entity(doc, @entity_id, entity_doc)
|
|
163
|
+
@project.mpr.update_unit(@domain_unit_id, doc)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
@project.refresh!
|
|
167
|
+
@applied = true
|
|
168
|
+
self
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Plan that adds a new entity to a module's domain model.
|
|
173
|
+
class AddEntityPlan
|
|
174
|
+
attr_reader :entity_def, :module_name, :changes
|
|
175
|
+
|
|
176
|
+
def initialize(project:, module_name:, domain_unit_id:, entity_def:)
|
|
177
|
+
@project = project
|
|
178
|
+
@module_name = module_name
|
|
179
|
+
@domain_unit_id = domain_unit_id
|
|
180
|
+
@entity_def = entity_def
|
|
181
|
+
@applied = false
|
|
182
|
+
@changes = ["add entity #{module_name}.#{entity_def[:name]}"].freeze
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def empty? = false
|
|
186
|
+
def applied? = @applied
|
|
187
|
+
|
|
188
|
+
def apply!
|
|
189
|
+
raise ArgumentError, "plan was already applied" if @applied
|
|
190
|
+
|
|
191
|
+
@project.mpr.transaction do
|
|
192
|
+
raw = @project.raw_unit(@domain_unit_id)
|
|
193
|
+
raise ArgumentError, "domain model unit not found" unless raw
|
|
194
|
+
|
|
195
|
+
doc = @project.parse_bson(raw)
|
|
196
|
+
entities = DomainMutator.extract_entities(doc)
|
|
197
|
+
existing_names = entities.map { (_1["name"] || _1["Name"]).to_s }
|
|
198
|
+
raise ArgumentError, "entity #{@entity_def[:name]} already exists in #{@module_name}" \
|
|
199
|
+
if existing_names.include?(@entity_def[:name].to_s)
|
|
200
|
+
|
|
201
|
+
entity = Model::Entity.new
|
|
202
|
+
entity.id = SecureRandom.uuid
|
|
203
|
+
entity.name = @entity_def[:name].to_s
|
|
204
|
+
entity.qualified_name = "#{@module_name}.#{@entity_def[:name]}"
|
|
205
|
+
entity.documentation = @entity_def.fetch(:documentation, "").to_s
|
|
206
|
+
entity.data_storage_guid = SecureRandom.uuid
|
|
207
|
+
entity.export_level = "Hidden"
|
|
208
|
+
entity.persistable = !@entity_def[:non_persistent]
|
|
209
|
+
entity.location = { x: (entities.size % 4) * 220, y: (entities.size / 4) * 160 }
|
|
210
|
+
entity.access_rules = []
|
|
211
|
+
|
|
212
|
+
attrs = Array(@entity_def[:attributes]).map.with_index do |attr_def, i|
|
|
213
|
+
a = Model::Attribute.new
|
|
214
|
+
a.id = SecureRandom.uuid
|
|
215
|
+
a.name = attr_def[:name].to_s
|
|
216
|
+
a.documentation = attr_def.fetch(:documentation, "").to_s
|
|
217
|
+
a.type = attr_def.fetch(:type, :string).to_sym
|
|
218
|
+
a.default_value = attr_def[:default]&.to_s || ""
|
|
219
|
+
a.data_storage_guid = SecureRandom.uuid
|
|
220
|
+
a.export_level = "Hidden"
|
|
221
|
+
a
|
|
222
|
+
end
|
|
223
|
+
entity.instance_variable_set(:@attributes, attrs)
|
|
224
|
+
|
|
225
|
+
entities_key = DomainMutator.entities_key(doc)
|
|
226
|
+
doc[entities_key] = IO::BsonCodec.build_array(entities + [entity.to_bson])
|
|
227
|
+
@project.mpr.update_unit(@domain_unit_id, doc)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
@project.refresh!
|
|
231
|
+
@applied = true
|
|
232
|
+
self
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Plan that removes an entity (and all its embedded attributes) from the domain model.
|
|
237
|
+
class RemoveEntityPlan
|
|
238
|
+
attr_reader :entity_name, :incoming, :changes
|
|
239
|
+
|
|
240
|
+
def initialize(project:, module_name:, entity_name:, entity_id:, domain_unit_id:, incoming:)
|
|
241
|
+
@project = project
|
|
242
|
+
@module_name = module_name
|
|
243
|
+
@entity_name = entity_name
|
|
244
|
+
@entity_id = entity_id
|
|
245
|
+
@domain_unit_id = domain_unit_id
|
|
246
|
+
@incoming = incoming.freeze
|
|
247
|
+
@applied = false
|
|
248
|
+
@changes = ["remove entity #{module_name}.#{entity_name}"].freeze
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def safe? = @incoming.empty?
|
|
252
|
+
def empty? = false
|
|
253
|
+
def applied? = @applied
|
|
254
|
+
|
|
255
|
+
def apply!
|
|
256
|
+
raise ArgumentError, "plan was already applied" if @applied
|
|
257
|
+
unless safe?
|
|
258
|
+
raise ArgumentError,
|
|
259
|
+
"cannot remove #{@module_name}.#{@entity_name}: " \
|
|
260
|
+
"#{@incoming.size} incoming reference(s)"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
@project.mpr.transaction do
|
|
264
|
+
raw = @project.raw_unit(@domain_unit_id)
|
|
265
|
+
raise ArgumentError, "domain model unit not found" unless raw
|
|
266
|
+
|
|
267
|
+
doc = @project.parse_bson(raw)
|
|
268
|
+
entities = DomainMutator.extract_entities(doc)
|
|
269
|
+
before_size = entities.size
|
|
270
|
+
entities.reject! { IO::BsonCodec.extract_id(_1["$ID"] || _1["\$ID"]) == @entity_id }
|
|
271
|
+
raise ArgumentError, "entity #{@entity_name.inspect} not found" if entities.size == before_size
|
|
272
|
+
|
|
273
|
+
entities_key = DomainMutator.entities_key(doc)
|
|
274
|
+
doc[entities_key] = IO::BsonCodec.build_array(entities)
|
|
275
|
+
@project.mpr.update_unit(@domain_unit_id, doc)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
@project.refresh!
|
|
279
|
+
@applied = true
|
|
280
|
+
self
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Factory for domain model mutation plans.
|
|
285
|
+
#
|
|
286
|
+
# Usage:
|
|
287
|
+
# project.plan_add_attribute("M.Product", name: :Price, type: :decimal)
|
|
288
|
+
# project.plan_remove_attribute("M.Product/Price")
|
|
289
|
+
# project.plan_change_attribute("M.Product/Price", type: :integer, default: "0")
|
|
290
|
+
# project.plan_add_entity("M", name: :Tag, attributes: [{name: :Label, type: :string}])
|
|
291
|
+
# project.plan_remove_entity("M.Product")
|
|
292
|
+
class DomainMutator
|
|
293
|
+
def initialize(project)
|
|
294
|
+
@project = project
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def plan_add_attribute(entity_qname, name:, type: :string, default: nil, documentation: nil)
|
|
298
|
+
mod_name, entity_name, dm_id, entity_id = resolve_entity(entity_qname)
|
|
299
|
+
AddAttributePlan.new(
|
|
300
|
+
project: @project, module_name: mod_name, entity_name: entity_name,
|
|
301
|
+
entity_id: entity_id, domain_unit_id: dm_id,
|
|
302
|
+
attribute_def: { name: name.to_s, type: type, default: default, documentation: documentation.to_s }
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def plan_remove_attribute(attribute_qname)
|
|
307
|
+
mod_name, entity_name, dm_id, entity_id, attr_name = resolve_attribute(attribute_qname)
|
|
308
|
+
# Attribute qualified names in the semantic index use dots: M.Entity.Attr
|
|
309
|
+
artifact = @project.find_artifact("#{mod_name}.#{entity_name}.#{attr_name}", kind: :attribute)
|
|
310
|
+
incoming = artifact ? @project.references_to(artifact) : []
|
|
311
|
+
RemoveAttributePlan.new(
|
|
312
|
+
project: @project, module_name: mod_name, entity_name: entity_name,
|
|
313
|
+
entity_id: entity_id, domain_unit_id: dm_id,
|
|
314
|
+
attribute_name: attr_name, incoming: incoming
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def plan_change_attribute(attribute_qname, type: nil, default: nil, documentation: nil)
|
|
319
|
+
mod_name, entity_name, dm_id, entity_id, attr_name = resolve_attribute(attribute_qname)
|
|
320
|
+
updates = {}
|
|
321
|
+
updates[:type] = type if type
|
|
322
|
+
updates[:default] = default unless default.nil?
|
|
323
|
+
updates[:documentation] = documentation if documentation
|
|
324
|
+
raise ArgumentError, "plan_change_attribute: no changes specified" if updates.empty?
|
|
325
|
+
|
|
326
|
+
ChangeAttributePlan.new(
|
|
327
|
+
project: @project, module_name: mod_name, entity_name: entity_name,
|
|
328
|
+
entity_id: entity_id, domain_unit_id: dm_id,
|
|
329
|
+
attribute_name: attr_name, updates: updates
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def plan_add_entity(module_qname, name:, attributes: [], documentation: nil, non_persistent: false)
|
|
334
|
+
mod_name, dm_id = resolve_module(module_qname)
|
|
335
|
+
AddEntityPlan.new(
|
|
336
|
+
project: @project, module_name: mod_name, domain_unit_id: dm_id,
|
|
337
|
+
entity_def: {
|
|
338
|
+
name: name.to_s, attributes: attributes,
|
|
339
|
+
documentation: documentation.to_s, non_persistent: non_persistent
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def plan_remove_entity(entity_qname)
|
|
345
|
+
mod_name, entity_name, dm_id, entity_id = resolve_entity(entity_qname)
|
|
346
|
+
artifact = @project.find_artifact(entity_qname, kind: :entity)
|
|
347
|
+
incoming = artifact ? @project.references_to(artifact).reject { _1.source.qualified_name == entity_qname } : []
|
|
348
|
+
RemoveEntityPlan.new(
|
|
349
|
+
project: @project, module_name: mod_name, entity_name: entity_name,
|
|
350
|
+
entity_id: entity_id, domain_unit_id: dm_id, incoming: incoming
|
|
351
|
+
)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# ── BSON helpers used by plans ──────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
def self.extract_entities(doc)
|
|
357
|
+
IO::BsonCodec.parse_array(doc["entities"] || doc["Entities"])[:items]
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def self.entities_key(doc)
|
|
361
|
+
doc.key?("entities") ? "entities" : "Entities"
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.extract_attributes(entity_doc)
|
|
365
|
+
IO::BsonCodec.parse_array(entity_doc["attributes"] || entity_doc["Attributes"])[:items]
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def self.put_attributes(entity_doc, attrs)
|
|
369
|
+
key = entity_doc.key?("attributes") ? "attributes" : "Attributes"
|
|
370
|
+
entity_doc[key] = IO::BsonCodec.build_array(attrs)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def self.put_entity(domain_doc, entity_id, updated_entity_doc)
|
|
374
|
+
key = entities_key(domain_doc)
|
|
375
|
+
entities = IO::BsonCodec.parse_array(domain_doc[key])[:items]
|
|
376
|
+
updated = entities.map do |e|
|
|
377
|
+
IO::BsonCodec.extract_id(e["$ID"] || e["\$ID"]) == entity_id ? updated_entity_doc : e
|
|
378
|
+
end
|
|
379
|
+
domain_doc[key] = IO::BsonCodec.build_array(updated)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
private
|
|
383
|
+
|
|
384
|
+
def resolve_module(name)
|
|
385
|
+
mod = @project.modules.find { _1.name == name.to_s }
|
|
386
|
+
raise KeyError, "module #{name.inspect} not found" unless mod
|
|
387
|
+
|
|
388
|
+
dm = mod.domain_model
|
|
389
|
+
raise ArgumentError, "module #{name} has no domain model" unless dm
|
|
390
|
+
|
|
391
|
+
[mod.name, dm.id]
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def resolve_entity(qname)
|
|
395
|
+
parts = qname.to_s.split(".", 2)
|
|
396
|
+
raise ArgumentError, "expected ModuleName.EntityName, got #{qname.inspect}" unless parts.size == 2
|
|
397
|
+
|
|
398
|
+
mod_name, entity_name = parts
|
|
399
|
+
mod = @project.modules.find { _1.name == mod_name }
|
|
400
|
+
raise KeyError, "module #{mod_name.inspect} not found" unless mod
|
|
401
|
+
|
|
402
|
+
dm = mod.domain_model
|
|
403
|
+
raise ArgumentError, "module #{mod_name} has no domain model" unless dm
|
|
404
|
+
|
|
405
|
+
entity = mod.entities.find { _1.name == entity_name }
|
|
406
|
+
raise KeyError, "entity #{qname.inspect} not found" unless entity
|
|
407
|
+
|
|
408
|
+
[mod_name, entity_name, dm.id, entity.id]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def resolve_attribute(qname)
|
|
412
|
+
entity_part, attr_name = qname.to_s.split("/", 2)
|
|
413
|
+
raise ArgumentError, "expected M.Entity/AttributeName, got #{qname.inspect}" unless attr_name
|
|
414
|
+
|
|
415
|
+
[*resolve_entity(entity_part), attr_name]
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'tfidf_embedder'
|
|
4
|
+
require_relative 'onnx_embedder'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Semantic
|
|
8
|
+
RankedArtifact = Data.define(:artifact, :similarity, :distance)
|
|
9
|
+
|
|
10
|
+
# Builds optional embedding backends and ranks artifacts in pure Ruby.
|
|
11
|
+
module Embedder
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def build(backend: :auto)
|
|
15
|
+
case backend
|
|
16
|
+
when :tfidf then TfidfEmbedder.new
|
|
17
|
+
when :onnx then OnnxEmbedder.new
|
|
18
|
+
when :auto then OnnxEmbedder.available? ? OnnxEmbedder.new : TfidfEmbedder.new
|
|
19
|
+
else raise ArgumentError, "unknown embedding backend: #{backend.inspect}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def artifact_text(artifact)
|
|
24
|
+
metadata = artifact.metadata || {}
|
|
25
|
+
[
|
|
26
|
+
artifact.qualified_name, artifact.name, artifact.kind,
|
|
27
|
+
artifact.module_name, metadata[:documentation]
|
|
28
|
+
].compact.join(' ')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def rank(artifacts, query, embedder:, limit:)
|
|
32
|
+
rank_with_distance(artifacts, query, embedder:, limit:).map(&:artifact).freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rank_with_distance(artifacts, query, embedder:, limit:)
|
|
36
|
+
query_vector = embedder.embed(query.to_s)
|
|
37
|
+
scored = artifacts.map do |artifact|
|
|
38
|
+
similarity = dot(query_vector, embedder.embed(artifact_text(artifact)))
|
|
39
|
+
RankedArtifact.new(artifact, similarity, 1.0 - similarity)
|
|
40
|
+
end
|
|
41
|
+
scored.sort_by { [-_1.similarity, _1.artifact.qualified_name] }.first(limit).freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def dot(left, right)
|
|
45
|
+
raise ArgumentError, 'embedding dimensions do not match' unless left.size == right.size
|
|
46
|
+
|
|
47
|
+
left.each_index.sum { left[_1] * right[_1] }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|