mxrb 0.1.2 → 0.1.4
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 +4 -4
- data/bin/mxrb +458 -10
- data/docs/de-DE/README.md +1 -0
- data/docs/de-DE/oql-sql.md +42 -0
- data/docs/de-DE/project-structure.md +201 -2
- data/docs/de-DE/runtime-ruby.md +104 -0
- data/docs/de-DE/runtime-schema-migrations.md +44 -0
- data/docs/de-DE/validation-matrix.md +42 -4
- data/docs/en-US/README.md +1 -0
- data/docs/en-US/oql-sql.md +41 -0
- data/docs/en-US/project-structure.md +192 -2
- data/docs/en-US/runtime-ruby.md +100 -0
- data/docs/en-US/runtime-schema-migrations.md +41 -0
- data/docs/en-US/validation-matrix.md +39 -4
- data/docs/pt-BR/README.md +1 -0
- data/docs/pt-BR/entity-dsl.md +8 -0
- data/docs/pt-BR/oql-sql.md +41 -0
- data/docs/pt-BR/project-structure.md +197 -2
- data/docs/pt-BR/runtime-ruby.md +108 -0
- data/docs/pt-BR/runtime-schema-migrations.md +40 -0
- data/docs/pt-BR/validation-matrix.md +42 -5
- data/docs/pt-BR/writing.md +28 -0
- data/lib/mxrb/cli/help.rb +426 -0
- data/lib/mxrb/cli/release.rb +137 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
- data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
- data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
- data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
- data/lib/mxrb/compiler/packager.rb +27 -13
- data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
- data/lib/mxrb/compiler/portable_packager.rb +16 -11
- data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
- data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
- data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
- data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
- data/lib/mxrb/domain_diagram.rb +409 -0
- data/lib/mxrb/dsl/builder.rb +62 -11
- data/lib/mxrb/environment.rb +226 -0
- data/lib/mxrb/exporter.rb +265 -45
- data/lib/mxrb/http/server.rb +107 -0
- data/lib/mxrb/initializer.rb +80 -9
- data/lib/mxrb/integrity/validator.rb +32 -7
- data/lib/mxrb/io/bson_codec.rb +42 -1
- data/lib/mxrb/io/mpr_file.rb +146 -3
- data/lib/mxrb/model/attribute.rb +3 -0
- data/lib/mxrb/model/entity.rb +44 -2
- data/lib/mxrb/model/microflow.rb +3 -1
- data/lib/mxrb/model/module.rb +71 -0
- data/lib/mxrb/model/page.rb +126 -15
- data/lib/mxrb/model/project.rb +3 -0
- data/lib/mxrb/modeler/catalog.rb +178 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
- data/lib/mxrb/official_marketplace.rb +9 -3
- data/lib/mxrb/oql/reverse_translator.rb +394 -0
- data/lib/mxrb/oql/server.rb +4 -8
- data/lib/mxrb/oql.rb +32 -4
- data/lib/mxrb/progress.rb +242 -0
- data/lib/mxrb/project_lifecycle.rb +5 -2
- data/lib/mxrb/ruby_app/exporter.rb +1749 -0
- data/lib/mxrb/ruby_app/preset.rb +506 -0
- data/lib/mxrb/ruby_app/session_manager.rb +122 -0
- data/lib/mxrb/ruby_app.rb +1361 -0
- data/lib/mxrb/runtime/access_control.rb +408 -0
- data/lib/mxrb/runtime/native.rb +1015 -93
- data/lib/mxrb/runtime/scheduler.rb +396 -0
- data/lib/mxrb/runtime/schema_migrator.rb +598 -0
- data/lib/mxrb/runtime/shared_store.rb +335 -0
- data/lib/mxrb/runtime/sqlite_store.rb +729 -0
- data/lib/mxrb/scaffold/generator.rb +11 -3
- data/lib/mxrb/team_server.rb +10 -7
- data/lib/mxrb/uml/activity_diagram.rb +118 -0
- data/lib/mxrb/uml/class_diagram.rb +118 -0
- data/lib/mxrb/uml/sequence_diagram.rb +112 -0
- data/lib/mxrb/uml/server.rb +146 -0
- data/lib/mxrb/uml/support.rb +35 -0
- data/lib/mxrb/version.rb +1 -1
- data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
- data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
- data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
- data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
- data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
- data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
- data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
- data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
- data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
- data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
- data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
- data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
- data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
- data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
- data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
- data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
- data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
- data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
- data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
- data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
- data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
- data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
- data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
- data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
- data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
- data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
- data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
- data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
- data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
- data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
- data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
- data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
- data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
- data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
- data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
- data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
- data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
- data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
- data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
- data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
- data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
- data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
- data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
- data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
- data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
- data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
- data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
- data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
- data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
- data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
- data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
- data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
- data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
- data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
- data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
- data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
- data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
- data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
- data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
- data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
- data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
- data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
- data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
- data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
- data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
- data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
- data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
- data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
- data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
- data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
- data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
- data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
- data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
- data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
- data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
- data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
- data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
- data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
- data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
- data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
- data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
- data/lib/mxrb/web_ui/domain.html +15 -0
- data/lib/mxrb/web_ui/modeler.html +16 -0
- data/lib/mxrb/web_ui/uml.html +31 -0
- data/lib/mxrb/web_ui.rb +89 -0
- data/lib/mxrb/widget_certification.rb +208 -0
- data/lib/mxrb/writer.rb +278 -41
- data/lib/mxrb.rb +38 -8
- metadata +157 -5
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
require_relative 'web_ui'
|
|
9
|
+
require_relative 'http/server'
|
|
10
|
+
|
|
11
|
+
module Mxrb
|
|
12
|
+
# Browser ER diagram for Mendix domain models. It edits visual layout only:
|
|
13
|
+
# entity locations plus local and cross-module connection anchors.
|
|
14
|
+
# rubocop:disable Metrics
|
|
15
|
+
module DomainDiagram
|
|
16
|
+
ANCHORS = %w[north east south west].freeze
|
|
17
|
+
ANCHOR_VALUES = {
|
|
18
|
+
'north' => '50;0', 'east' => '100;50',
|
|
19
|
+
'south' => '50;100', 'west' => '0;50'
|
|
20
|
+
}.freeze
|
|
21
|
+
VALUE_ANCHORS = ANCHOR_VALUES.invert.freeze
|
|
22
|
+
|
|
23
|
+
# Builds a framework-neutral JSON projection from arbitrary Mendix modules.
|
|
24
|
+
class Document
|
|
25
|
+
def initialize(path, modules: [])
|
|
26
|
+
@path = File.expand_path(path)
|
|
27
|
+
@selected_modules = Array(modules).map(&:to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
Model::Project.open(@path).then do |project|
|
|
32
|
+
selected = project.modules
|
|
33
|
+
unless @selected_modules.empty?
|
|
34
|
+
unknown = @selected_modules.uniq - selected.map(&:name)
|
|
35
|
+
raise ArgumentError, "unknown modules: #{unknown.join(', ')}" unless unknown.empty?
|
|
36
|
+
|
|
37
|
+
selected = selected.select { @selected_modules.include?(_1.name) }
|
|
38
|
+
end
|
|
39
|
+
anchor_overrides = project.mpr.domain_diagram_anchors
|
|
40
|
+
known = project.modules.each_with_object({}) do |mod, names|
|
|
41
|
+
mod.entities.each do |entity|
|
|
42
|
+
qualified = entity.qualified_name || "#{mod.name}.#{entity.name}"
|
|
43
|
+
names[entity.id] = qualified
|
|
44
|
+
names[qualified] = qualified
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
{
|
|
48
|
+
project: { name: project.name, mendix_version: project.mendix_version },
|
|
49
|
+
module_filter_applied: !@selected_modules.empty?,
|
|
50
|
+
modules: selected.filter_map { module_payload(_1, known, anchor_overrides) }
|
|
51
|
+
}
|
|
52
|
+
ensure
|
|
53
|
+
project&.close
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def module_payload(mod, known, anchor_overrides = {})
|
|
60
|
+
domain = mod.domain_model
|
|
61
|
+
return unless domain
|
|
62
|
+
|
|
63
|
+
raw = mod.mpr.parse_contents(mod.mpr.unit(domain.id))
|
|
64
|
+
association_docs = array(raw['associations'] || raw['Associations']) +
|
|
65
|
+
array(raw['crossAssociations'] || raw['CrossAssociations'])
|
|
66
|
+
association_by_id = association_docs.to_h { [IO::BsonCodec.extract_id(_1['$ID']), _1] }
|
|
67
|
+
{
|
|
68
|
+
id: mod.id, name: mod.name,
|
|
69
|
+
entities: domain.entities.map { entity_payload(_1, mod.name) },
|
|
70
|
+
associations: domain.associations.filter_map do |association|
|
|
71
|
+
association_payload(
|
|
72
|
+
association, association_by_id[association.id] || {}, known, anchor_overrides
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def entity_payload(entity, module_name)
|
|
79
|
+
kind = if entity.respond_to?(:oql_view?) && entity.oql_view?
|
|
80
|
+
'oql_view'
|
|
81
|
+
elsif entity.persistable == true
|
|
82
|
+
'entity'
|
|
83
|
+
else
|
|
84
|
+
'dto'
|
|
85
|
+
end
|
|
86
|
+
{
|
|
87
|
+
id: entity.id, name: entity.name,
|
|
88
|
+
qualified_name: entity.qualified_name || "#{module_name}.#{entity.name}",
|
|
89
|
+
module: module_name, kind:,
|
|
90
|
+
persistent: entity.persistable == true,
|
|
91
|
+
x: Integer(entity.location&.fetch(:x, 0) || 0),
|
|
92
|
+
y: Integer(entity.location&.fetch(:y, 0) || 0),
|
|
93
|
+
attributes: entity.attributes.map do |attribute|
|
|
94
|
+
{
|
|
95
|
+
name: attribute.name, type: attribute.type.to_s,
|
|
96
|
+
required: attribute.required == true, key: attribute.unique == true
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def association_payload(association, raw, known, anchor_overrides = {})
|
|
103
|
+
from = known[association.from_entity_id]
|
|
104
|
+
to = known[association.to_entity_id]
|
|
105
|
+
return unless from && to
|
|
106
|
+
|
|
107
|
+
cross_module = raw['$Type'] == 'DomainModels$CrossAssociation'
|
|
108
|
+
override = anchor_overrides.fetch(association.id.to_s, {})
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
id: association.id, name: association.name, from:, to:,
|
|
112
|
+
type: association.association_type.to_s,
|
|
113
|
+
owner: association.owner.to_s,
|
|
114
|
+
source_anchor: override.fetch(
|
|
115
|
+
:source_anchor, anchor(raw['ParentConnection'], fallback: 'east')
|
|
116
|
+
),
|
|
117
|
+
target_anchor: override.fetch(
|
|
118
|
+
:target_anchor, anchor(raw['ChildConnection'], fallback: 'west')
|
|
119
|
+
),
|
|
120
|
+
cross_module:,
|
|
121
|
+
editable_anchors: true,
|
|
122
|
+
anchor_storage: cross_module ? 'mxrb' : 'native'
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def anchor(value, fallback:)
|
|
127
|
+
VALUE_ANCHORS.fetch(value.to_s, fallback)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Applies only audited visual fields to a copied MPR.
|
|
134
|
+
class LayoutWriter
|
|
135
|
+
def initialize(path)
|
|
136
|
+
@path = File.expand_path(path)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def apply!(payload)
|
|
140
|
+
modules = Array(payload.fetch('modules'))
|
|
141
|
+
mpr = IO::MprFile.open(@path, readonly: false)
|
|
142
|
+
changed = 0
|
|
143
|
+
metadata_anchors = []
|
|
144
|
+
mpr.transaction do
|
|
145
|
+
module_units = mpr.units_by_containment('Modules').to_h do |unit|
|
|
146
|
+
[mpr.parse_contents(unit)['Name'].to_s, unit]
|
|
147
|
+
end
|
|
148
|
+
modules.each do |layout|
|
|
149
|
+
unit = module_units[layout.fetch('name').to_s]
|
|
150
|
+
raise ValidationError, "domain module #{layout['name']} not found" unless unit
|
|
151
|
+
|
|
152
|
+
module_changed, module_metadata = apply_module(mpr, unit, layout)
|
|
153
|
+
changed += module_changed
|
|
154
|
+
metadata_anchors.concat(module_metadata)
|
|
155
|
+
end
|
|
156
|
+
changed += mpr.write_domain_diagram_anchors(metadata_anchors)
|
|
157
|
+
end
|
|
158
|
+
changed
|
|
159
|
+
ensure
|
|
160
|
+
mpr&.close
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def apply_module(mpr, module_unit, layout)
|
|
166
|
+
domain_unit = mpr.children_of(module_unit.fetch('UnitID')).find do |unit|
|
|
167
|
+
unit['ContainmentName'] == 'DomainModel'
|
|
168
|
+
end
|
|
169
|
+
raise ValidationError, "domain model #{layout['name']} not found" unless domain_unit
|
|
170
|
+
|
|
171
|
+
doc = mpr.parse_contents(domain_unit)
|
|
172
|
+
changed = update_entities(doc, Array(layout['entities']))
|
|
173
|
+
association_changes, metadata_anchors = update_associations(
|
|
174
|
+
doc, Array(layout['associations'])
|
|
175
|
+
)
|
|
176
|
+
changed += association_changes
|
|
177
|
+
mpr.update_unit(domain_unit.fetch('UnitID'), doc) if changed.positive?
|
|
178
|
+
[changed, metadata_anchors]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def update_entities(doc, layouts)
|
|
182
|
+
by_id = layouts.to_h { [_1.fetch('id').to_s, _1] }
|
|
183
|
+
entities = array(doc['entities'] || doc['Entities'])
|
|
184
|
+
native_ids = entities.map { IO::BsonCodec.extract_id(_1['$ID']).to_s }
|
|
185
|
+
unknown = by_id.keys - native_ids
|
|
186
|
+
raise ValidationError, "unknown domain entities: #{unknown.join(', ')}" unless unknown.empty?
|
|
187
|
+
|
|
188
|
+
changed = entities.count do |entity|
|
|
189
|
+
layout = by_id[IO::BsonCodec.extract_id(entity['$ID']).to_s]
|
|
190
|
+
next false unless layout
|
|
191
|
+
|
|
192
|
+
x = bounded_integer(layout.fetch('x'), 'x')
|
|
193
|
+
y = bounded_integer(layout.fetch('y'), 'y')
|
|
194
|
+
key = entity.key?('Location') ? 'Location' : 'location'
|
|
195
|
+
value = entity[key].is_a?(Hash) ? { 'x' => x, 'y' => y } : "#{x};#{y}"
|
|
196
|
+
changed = entity[key] != value
|
|
197
|
+
entity[key] = value
|
|
198
|
+
changed
|
|
199
|
+
end
|
|
200
|
+
key = doc.key?('Entities') ? 'Entities' : 'entities'
|
|
201
|
+
doc[key] = IO::BsonCodec.build_array(entities)
|
|
202
|
+
changed
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def update_associations(doc, layouts)
|
|
206
|
+
by_id = layouts.to_h { [_1.fetch('id').to_s, _1] }
|
|
207
|
+
key = %w[associations Associations].find { doc.key?(_1) } || 'associations'
|
|
208
|
+
cross_key = %w[crossAssociations CrossAssociations].find { doc.key?(_1) } ||
|
|
209
|
+
'crossAssociations'
|
|
210
|
+
associations = array(doc[key])
|
|
211
|
+
cross_associations = array(doc[cross_key])
|
|
212
|
+
native_ids = associations.map { IO::BsonCodec.extract_id(_1['$ID']).to_s }
|
|
213
|
+
cross_ids = cross_associations.map { IO::BsonCodec.extract_id(_1['$ID']).to_s }
|
|
214
|
+
unknown = by_id.keys - native_ids - cross_ids
|
|
215
|
+
raise ValidationError, "unknown domain associations: #{unknown.join(', ')}" unless unknown.empty?
|
|
216
|
+
|
|
217
|
+
count = associations.count do |association|
|
|
218
|
+
layout = by_id[IO::BsonCodec.extract_id(association['$ID']).to_s]
|
|
219
|
+
next false unless layout
|
|
220
|
+
|
|
221
|
+
source = connection(layout.fetch('source_anchor'))
|
|
222
|
+
target = connection(layout.fetch('target_anchor'))
|
|
223
|
+
changed = association['ParentConnection'] != source || association['ChildConnection'] != target
|
|
224
|
+
association['ParentConnection'] = source
|
|
225
|
+
association['ChildConnection'] = target
|
|
226
|
+
changed
|
|
227
|
+
end
|
|
228
|
+
doc[key] = IO::BsonCodec.build_array(associations)
|
|
229
|
+
metadata = cross_ids.filter_map do |id|
|
|
230
|
+
layout = by_id[id]
|
|
231
|
+
next unless layout
|
|
232
|
+
|
|
233
|
+
{
|
|
234
|
+
id:, source_anchor: anchor_name(layout.fetch('source_anchor')),
|
|
235
|
+
target_anchor: anchor_name(layout.fetch('target_anchor'))
|
|
236
|
+
}
|
|
237
|
+
end
|
|
238
|
+
[count, metadata]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def bounded_integer(value, name)
|
|
242
|
+
number = Integer(value)
|
|
243
|
+
raise ValidationError, "#{name} is outside the diagram canvas" unless number.between?(-100_000, 100_000)
|
|
244
|
+
|
|
245
|
+
number
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def connection(anchor)
|
|
249
|
+
ANCHOR_VALUES.fetch(anchor_name(anchor))
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def anchor_name(anchor)
|
|
253
|
+
ANCHOR_VALUES.fetch(anchor.to_s) do
|
|
254
|
+
raise ValidationError, "unknown association anchor #{anchor.inspect}"
|
|
255
|
+
end
|
|
256
|
+
anchor.to_s
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def array(value) = IO::BsonCodec.parse_array(value)[:items]
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Loopback-only server for the standalone editor.
|
|
263
|
+
class Server
|
|
264
|
+
MAX_BODY_BYTES = 2_097_152
|
|
265
|
+
LOOPBACK_HOSTS = %w[127.0.0.1 ::1 localhost].freeze
|
|
266
|
+
|
|
267
|
+
attr_reader :source, :output, :host, :port, :managed_paths
|
|
268
|
+
|
|
269
|
+
def initialize(source, output: nil, modules: [], host: '127.0.0.1', port: 4568, force: false,
|
|
270
|
+
reuse: false, lifecycle_token: nil)
|
|
271
|
+
@source = File.expand_path(source)
|
|
272
|
+
raise ArgumentError, "MPR not found: #{@source}" unless File.file?(@source)
|
|
273
|
+
|
|
274
|
+
@output = File.expand_path(output || default_output)
|
|
275
|
+
raise ArgumentError, 'output must be a safe copy, not the source MPR' if same_file_as_source?
|
|
276
|
+
raise ArgumentError, "output cannot be a directory: #{@output}" if File.directory?(@output)
|
|
277
|
+
raise ArgumentError, "output cannot be a symbolic link: #{@output}" if File.symlink?(@output)
|
|
278
|
+
|
|
279
|
+
@host = host.to_s
|
|
280
|
+
raise ArgumentError, 'domain model server must bind to loopback' unless LOOPBACK_HOSTS.include?(@host)
|
|
281
|
+
|
|
282
|
+
@port = Integer(port)
|
|
283
|
+
@modules = Array(modules)
|
|
284
|
+
@token = SecureRandom.hex(24)
|
|
285
|
+
@lifecycle_token = lifecycle_token
|
|
286
|
+
@managed_paths = [@output]
|
|
287
|
+
prepare_output(force:, reuse:)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def start
|
|
291
|
+
@server = Http::Server.new(host:, port:) { dispatch(_1, _2) }
|
|
292
|
+
@server.start { yield(_1) if block_given? }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def shutdown = @server&.shutdown
|
|
296
|
+
|
|
297
|
+
private
|
|
298
|
+
|
|
299
|
+
def default_output
|
|
300
|
+
File.join(File.dirname(source), "#{File.basename(source, '.mpr')}.domain-layout.mpr")
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def prepare_output(force:, reuse:)
|
|
304
|
+
if reuse
|
|
305
|
+
raise ArgumentError, "output not found: #{output}" unless File.file?(output)
|
|
306
|
+
|
|
307
|
+
return
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
raise ArgumentError, "output already exists: #{output}; use --force" if File.exist?(output) && !force
|
|
311
|
+
|
|
312
|
+
validate_external_contents_destination!
|
|
313
|
+
FileUtils.mkdir_p(File.dirname(output))
|
|
314
|
+
FileUtils.cp(source, output)
|
|
315
|
+
copy_external_contents
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def same_file_as_source?
|
|
319
|
+
source == output || (File.exist?(output) && File.identical?(source, output))
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def validate_external_contents_destination!
|
|
323
|
+
source_contents = File.join(File.dirname(source), 'mprcontents')
|
|
324
|
+
return unless File.directory?(source_contents)
|
|
325
|
+
|
|
326
|
+
output_directory = File.dirname(output)
|
|
327
|
+
return if File.expand_path(output_directory) == File.expand_path(File.dirname(source))
|
|
328
|
+
|
|
329
|
+
target = File.join(output_directory, 'mprcontents')
|
|
330
|
+
return unless File.exist?(target) || File.symlink?(target)
|
|
331
|
+
|
|
332
|
+
raise ArgumentError, "external contents destination already exists: #{target}"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def copy_external_contents
|
|
336
|
+
source_contents = File.join(File.dirname(source), 'mprcontents')
|
|
337
|
+
return unless File.directory?(source_contents)
|
|
338
|
+
|
|
339
|
+
output_directory = File.dirname(output)
|
|
340
|
+
return if File.expand_path(output_directory) == File.expand_path(File.dirname(source))
|
|
341
|
+
|
|
342
|
+
target = File.join(output_directory, 'mprcontents')
|
|
343
|
+
FileUtils.cp_r(source_contents, output_directory)
|
|
344
|
+
@managed_paths << target
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def dispatch(request, response)
|
|
348
|
+
if request.request_method == 'GET' && request.path == '/'
|
|
349
|
+
return render(response, 200, WebUi.page('domain'), 'text/html; charset=utf-8')
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
if request.request_method == 'GET' && request.path.start_with?('/assets/')
|
|
353
|
+
asset = WebUi.asset(request.path)
|
|
354
|
+
return render(response, 200, *asset) if asset
|
|
355
|
+
|
|
356
|
+
return json(response, 404, ok: false, error: 'not_found')
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
if request.request_method == 'GET' && request.path == '/api/diagram'
|
|
360
|
+
payload = Document.new(output, modules: @modules).to_h.merge(
|
|
361
|
+
output: output, csrf_token: @token
|
|
362
|
+
)
|
|
363
|
+
return json(response, 200, payload)
|
|
364
|
+
end
|
|
365
|
+
if request.path.start_with?('/api/admin/')
|
|
366
|
+
return json(response, 403, ok: false, error: 'forbidden') unless lifecycle_authorized?(request)
|
|
367
|
+
return json(response, 200, ok: true) if request.request_method == 'GET' && request.path == '/api/admin/status'
|
|
368
|
+
|
|
369
|
+
if request.request_method == 'POST' && request.path == '/api/admin/shutdown'
|
|
370
|
+
Thread.new { shutdown }
|
|
371
|
+
return json(response, 200, ok: true)
|
|
372
|
+
end
|
|
373
|
+
return json(response, 404, ok: false, error: 'not_found')
|
|
374
|
+
end
|
|
375
|
+
if request.request_method == 'POST' && request.path == '/api/layout'
|
|
376
|
+
raise ValidationError, 'invalid editor token' unless request['X-MXRB-Token'].to_s == @token
|
|
377
|
+
|
|
378
|
+
body = request.body.to_s
|
|
379
|
+
raise ValidationError, 'layout exceeds 2 MiB' if body.bytesize > MAX_BODY_BYTES
|
|
380
|
+
|
|
381
|
+
changed = LayoutWriter.new(output).apply!(JSON.parse(body))
|
|
382
|
+
return json(response, 200, ok: true, changed:, output:)
|
|
383
|
+
end
|
|
384
|
+
return json(response, 404, ok: false, error: 'not_found') if request.path.start_with?('/api/')
|
|
385
|
+
|
|
386
|
+
json(response, 404, ok: false, error: 'not_found')
|
|
387
|
+
rescue JSON::ParserError, ValidationError, KeyError, ArgumentError => e
|
|
388
|
+
json(response, 422, ok: false, error: e.message)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def lifecycle_authorized?(request)
|
|
392
|
+
@lifecycle_token && request['X-MXRB-Lifecycle-Token'].to_s == @lifecycle_token
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def json(response, status, payload)
|
|
396
|
+
render(response, status, JSON.generate(payload), 'application/json; charset=utf-8')
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def render(response, status, body, type)
|
|
400
|
+
response.status = status
|
|
401
|
+
response['Content-Type'] = type
|
|
402
|
+
response['Cache-Control'] = 'no-store'
|
|
403
|
+
response['X-Content-Type-Options'] = 'nosniff'
|
|
404
|
+
response.body = body
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
# rubocop:enable Metrics
|
|
409
|
+
end
|
data/lib/mxrb/dsl/builder.rb
CHANGED
|
@@ -85,8 +85,16 @@ module Mxrb
|
|
|
85
85
|
_add_widget(:text, name, caption: caption || name.to_s, &block)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def data_grid(name, entity: nil, &block)
|
|
89
|
-
_add_widget(:data_grid, name, entity: entity, &block)
|
|
88
|
+
def data_grid(name, entity: nil, selection: nil, &block)
|
|
89
|
+
_add_widget(:data_grid, name, entity: entity, selection: selection, &block)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Gallery projections remain present beside an authoritative page
|
|
93
|
+
# deep_structure in exported projects. Accept the concise projection at
|
|
94
|
+
# every widget nesting level so arbitrary native pages can be evaluated
|
|
95
|
+
# and reconstructed from their lossless payload.
|
|
96
|
+
def gallery(name, entity: nil, &block)
|
|
97
|
+
_add_widget(:gallery, name, entity: entity, &block)
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
def tab_control(name, &block)
|
|
@@ -157,6 +165,8 @@ module Mxrb
|
|
|
157
165
|
|
|
158
166
|
# Builds sub-widgets inside a data_grid column sub-items or inside a container.
|
|
159
167
|
class WidgetBuilder
|
|
168
|
+
include WidgetDsl
|
|
169
|
+
|
|
160
170
|
def initialize(type, name, **options)
|
|
161
171
|
@type = type
|
|
162
172
|
@name = name.to_s
|
|
@@ -166,12 +176,18 @@ module Mxrb
|
|
|
166
176
|
@tabs = []
|
|
167
177
|
@search_bar = nil
|
|
168
178
|
@toolbar = nil
|
|
179
|
+
@children = []
|
|
180
|
+
@filters = []
|
|
169
181
|
end
|
|
170
182
|
|
|
171
|
-
def column(name, attribute: nil, caption: nil)
|
|
172
|
-
@columns << {
|
|
183
|
+
def column(name, attribute: nil, caption: nil, filter: nil)
|
|
184
|
+
@columns << {
|
|
185
|
+
name: name.to_s, attribute: attribute&.to_s, caption:, filter:
|
|
186
|
+
}.compact
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
def filter(widget) = @filters << widget
|
|
190
|
+
|
|
175
191
|
def tab_page(name, caption: nil, &block)
|
|
176
192
|
tab = TabPageBuilder.new(name, caption: caption)
|
|
177
193
|
tab.instance_eval(&block) if block
|
|
@@ -191,8 +207,8 @@ module Mxrb
|
|
|
191
207
|
end
|
|
192
208
|
|
|
193
209
|
%i[on_change on_click on_enter on_leave].each do |event|
|
|
194
|
-
define_method(event) do |microflow: nil, nanoflow: nil, action: nil|
|
|
195
|
-
choices = { microflow: microflow, nanoflow: nanoflow, action: action }.compact
|
|
210
|
+
define_method(event) do |microflow: nil, nanoflow: nil, page: nil, action: nil|
|
|
211
|
+
choices = { microflow: microflow, nanoflow: nanoflow, page: page, action: action }.compact
|
|
196
212
|
raise ArgumentError, "#{event} requires exactly one handler" unless choices.size == 1
|
|
197
213
|
@events << { event: event, kind: choices.keys.first, handler: choices.values.first.to_s }
|
|
198
214
|
end
|
|
@@ -204,8 +220,15 @@ module Mxrb
|
|
|
204
220
|
options[:tabs] = @tabs unless @tabs.empty?
|
|
205
221
|
options[:search_bar] = @search_bar if @search_bar
|
|
206
222
|
options[:toolbar] = @toolbar if @toolbar
|
|
207
|
-
|
|
223
|
+
options[:filters] = @filters unless @filters.empty?
|
|
224
|
+
value = { type: @type, name: @name, options: options, events: @events }
|
|
225
|
+
value[:children] = @children unless @children.empty?
|
|
226
|
+
value
|
|
208
227
|
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
def _widget_list = @children
|
|
209
232
|
end
|
|
210
233
|
|
|
211
234
|
class TabPageBuilder
|
|
@@ -315,6 +338,8 @@ module Mxrb
|
|
|
315
338
|
class Builder
|
|
316
339
|
include ConnectorDeclarations
|
|
317
340
|
|
|
341
|
+
attr_reader :path
|
|
342
|
+
|
|
318
343
|
def initialize(path)
|
|
319
344
|
@path = path
|
|
320
345
|
@mendix_version = "10.18.0"
|
|
@@ -325,6 +350,7 @@ module Mxrb
|
|
|
325
350
|
@native_units_path = nil
|
|
326
351
|
@native_unit_overrides = []
|
|
327
352
|
@project_assets = nil
|
|
353
|
+
@ruby_app_sources_path = nil
|
|
328
354
|
end
|
|
329
355
|
|
|
330
356
|
def mendix_version(v)
|
|
@@ -366,6 +392,10 @@ module Mxrb
|
|
|
366
392
|
}
|
|
367
393
|
end
|
|
368
394
|
|
|
395
|
+
def ruby_app_sources(path)
|
|
396
|
+
@ruby_app_sources_path = File.expand_path(path)
|
|
397
|
+
end
|
|
398
|
+
|
|
369
399
|
def native_unit(unit_id, container_id:, containment:, deep_structure:, module_name: nil)
|
|
370
400
|
raise ArgumentError, "deep_structure requires a Hash" unless deep_structure.is_a?(Hash)
|
|
371
401
|
|
|
@@ -421,6 +451,7 @@ module Mxrb
|
|
|
421
451
|
design_system: @design_system,
|
|
422
452
|
connectors: connector_requests.map(&:to_h),
|
|
423
453
|
project_assets: @project_assets,
|
|
454
|
+
ruby_app_sources_path: @ruby_app_sources_path,
|
|
424
455
|
native_units_path: @native_units_path,
|
|
425
456
|
native_unit_overrides: @native_unit_overrides
|
|
426
457
|
}
|
|
@@ -872,15 +903,21 @@ module Mxrb
|
|
|
872
903
|
@scheduled_events << sb.to_h
|
|
873
904
|
end
|
|
874
905
|
|
|
875
|
-
def native_document(name, type:, deep_structure:, containment: 'Documents'
|
|
906
|
+
def native_document(name, type:, deep_structure:, containment: 'Documents',
|
|
907
|
+
unit_id: nil, container_id: nil)
|
|
876
908
|
raise ArgumentError, 'deep_structure requires a Hash' unless deep_structure.is_a?(Hash)
|
|
877
909
|
|
|
878
910
|
@native_documents << {
|
|
879
911
|
name: name.to_s, type: type.to_s, containment: containment.to_s,
|
|
912
|
+
unit_id: unit_id&.to_s, container_id: container_id&.to_s,
|
|
880
913
|
doc: { '$Type' => type.to_s, 'Name' => name.to_s }.merge(deep_structure)
|
|
881
914
|
}
|
|
882
915
|
end
|
|
883
916
|
|
|
917
|
+
def bson_binary(base64, subtype: :generic)
|
|
918
|
+
BSON::Binary.new(Base64.strict_decode64(base64), subtype.to_sym)
|
|
919
|
+
end
|
|
920
|
+
|
|
884
921
|
# Native responsive application shell. It deliberately uses only Mendix
|
|
885
922
|
# core widgets, so generated projects do not depend on Atlas Core merely
|
|
886
923
|
# to display their navigation.
|
|
@@ -1070,6 +1107,7 @@ module Mxrb
|
|
|
1070
1107
|
@generalization = nil
|
|
1071
1108
|
@system_members = nil
|
|
1072
1109
|
@indexes = nil
|
|
1110
|
+
@oql_view = nil
|
|
1073
1111
|
end
|
|
1074
1112
|
|
|
1075
1113
|
ATTR_TYPES.each do |type|
|
|
@@ -1081,6 +1119,16 @@ module Mxrb
|
|
|
1081
1119
|
def non_persistent! = (@persistable = false)
|
|
1082
1120
|
def documentation(d) = (@doc = d)
|
|
1083
1121
|
|
|
1122
|
+
# Marks an entity as an OQL-backed view. Modern Mendix projects keep the
|
|
1123
|
+
# query in a separate ViewEntitySourceDocument; older projects may keep
|
|
1124
|
+
# it directly on the embedded entity.
|
|
1125
|
+
def oql_view(source: nil, query: nil)
|
|
1126
|
+
raise ArgumentError, 'oql_view requires source or query' if source.nil? && query.nil?
|
|
1127
|
+
|
|
1128
|
+
@oql_view = { source: source&.to_s, query: query&.to_s }.compact
|
|
1129
|
+
@persistable = false
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1084
1132
|
def generalizes(entity)
|
|
1085
1133
|
@generalization = entity.to_s
|
|
1086
1134
|
end
|
|
@@ -1164,7 +1212,7 @@ module Mxrb
|
|
|
1164
1212
|
name: @name, persistable: @persistable, documentation: @doc,
|
|
1165
1213
|
attributes: @attributes, associations: @associations, lifecycle: @lifecycle,
|
|
1166
1214
|
access_rules: @access_rules, generalization: @generalization,
|
|
1167
|
-
system_members: @system_members, indexes: @indexes
|
|
1215
|
+
system_members: @system_members, indexes: @indexes, oql_view: @oql_view
|
|
1168
1216
|
}
|
|
1169
1217
|
end
|
|
1170
1218
|
|
|
@@ -1230,8 +1278,8 @@ module Mxrb
|
|
|
1230
1278
|
end
|
|
1231
1279
|
|
|
1232
1280
|
%i[on_change on_click on_submit on_load].each do |event|
|
|
1233
|
-
define_method(event) do |target: nil, microflow: nil, nanoflow: nil, action: nil|
|
|
1234
|
-
choices = { microflow: microflow, nanoflow: nanoflow, action: action }.compact
|
|
1281
|
+
define_method(event) do |target: nil, microflow: nil, nanoflow: nil, page: nil, action: nil|
|
|
1282
|
+
choices = { microflow: microflow, nanoflow: nanoflow, page: page, action: action }.compact
|
|
1235
1283
|
raise ArgumentError, "#{event} requires exactly one handler" unless choices.size == 1
|
|
1236
1284
|
@events << {
|
|
1237
1285
|
event: event, target: target&.to_s,
|
|
@@ -1506,6 +1554,7 @@ module Mxrb
|
|
|
1506
1554
|
@return_expression = nil
|
|
1507
1555
|
@expected_body_fingerprint = nil
|
|
1508
1556
|
@allow_concurrent_execution = nil
|
|
1557
|
+
@apply_entity_access = nil
|
|
1509
1558
|
@mark_as_used = nil
|
|
1510
1559
|
@excluded = nil
|
|
1511
1560
|
end
|
|
@@ -1521,6 +1570,7 @@ module Mxrb
|
|
|
1521
1570
|
def return_type(t) = (@return_type = t.to_s)
|
|
1522
1571
|
def documentation(d) = (@doc = d)
|
|
1523
1572
|
def allow_concurrent_execution(value = true) = (@allow_concurrent_execution = !!value)
|
|
1573
|
+
def apply_entity_access(value = true) = (@apply_entity_access = !!value)
|
|
1524
1574
|
def mark_as_used(value = true) = (@mark_as_used = !!value)
|
|
1525
1575
|
def excluded(value = true) = (@excluded = !!value)
|
|
1526
1576
|
|
|
@@ -1586,6 +1636,7 @@ module Mxrb
|
|
|
1586
1636
|
body: @body, return_variable_name: @return_variable_name,
|
|
1587
1637
|
return_expression: @return_expression,
|
|
1588
1638
|
allow_concurrent_execution: @allow_concurrent_execution,
|
|
1639
|
+
apply_entity_access: @apply_entity_access,
|
|
1589
1640
|
mark_as_used: @mark_as_used, excluded: @excluded,
|
|
1590
1641
|
preserve_native_body: !@expected_body_fingerprint.nil? &&
|
|
1591
1642
|
@expected_body_fingerprint == current_fingerprint
|