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,335 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'digest'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'sqlite3'
|
|
7
|
+
|
|
8
|
+
module Mxrb
|
|
9
|
+
module Runtime
|
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/BlockLength, Metrics/ClassLength
|
|
11
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ParameterLists
|
|
12
|
+
SessionRecord = Data.define(:token, :identity, :expires_at)
|
|
13
|
+
|
|
14
|
+
# Injectable session persistence protocol.
|
|
15
|
+
module SessionStore
|
|
16
|
+
def write_session(token:, identity:, expires_at:) = raise NotImplementedError
|
|
17
|
+
def read_session(token, now: Time.now.utc) = raise NotImplementedError
|
|
18
|
+
def delete_session(token) = raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Injectable scheduled-event claim/lease protocol. Renewal is optional for
|
|
22
|
+
# local coordinators and required for distributed lease implementations.
|
|
23
|
+
module SchedulerCoordinator
|
|
24
|
+
def claim_scheduled_event(event:, slot:, owner:, now:, lease_until:, skip_overlap:)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def complete_scheduled_event(event:, slot:, owner:, now: Time.now.utc) = raise NotImplementedError
|
|
29
|
+
def renew_scheduled_event(event:, slot:, owner:, lease_until:) = raise NotImplementedError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Process-local implementation of the shared runtime contracts. It keeps
|
|
33
|
+
# the standalone runtime dependency-free while exposing the same API as
|
|
34
|
+
# SQLiteSharedStore for injection in SessionManager and Scheduler.
|
|
35
|
+
class MemorySharedStore
|
|
36
|
+
include SessionStore
|
|
37
|
+
include SchedulerCoordinator
|
|
38
|
+
|
|
39
|
+
def initialize
|
|
40
|
+
@sessions = {}
|
|
41
|
+
@claims = {}
|
|
42
|
+
@leases = {}
|
|
43
|
+
@mutex = Mutex.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def write_session(token:, identity:, expires_at:)
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
@sessions[token.to_s] = SessionRecord.new(
|
|
49
|
+
token.to_s, copy(identity), expires_at.utc
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
token
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def read_session(token, now: Time.now.utc)
|
|
56
|
+
@mutex.synchronize do
|
|
57
|
+
prune_sessions(now)
|
|
58
|
+
record = @sessions[token.to_s]
|
|
59
|
+
record && SessionRecord.new(record.token, copy(record.identity), record.expires_at)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def delete_session(token)
|
|
64
|
+
@mutex.synchronize { !!@sessions.delete(token.to_s) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def claim_scheduled_event(event:, slot:, owner:, now:, lease_until:, skip_overlap:)
|
|
68
|
+
key = [event.to_s, slot.to_s]
|
|
69
|
+
@mutex.synchronize do
|
|
70
|
+
claim = @claims[key]
|
|
71
|
+
return false if claim && (claim[:completed_at] || claim.fetch(:lease_expires_at) > now)
|
|
72
|
+
|
|
73
|
+
lease = @leases[event.to_s]
|
|
74
|
+
return false if skip_overlap && lease && lease.fetch(:expires_at) > now
|
|
75
|
+
|
|
76
|
+
@claims[key] = {
|
|
77
|
+
owner: owner.to_s, claimed_at: now, lease_expires_at: lease_until,
|
|
78
|
+
completed_at: nil
|
|
79
|
+
}
|
|
80
|
+
if skip_overlap
|
|
81
|
+
@leases[event.to_s] = {
|
|
82
|
+
slot: slot.to_s, owner: owner.to_s, expires_at: lease_until
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def complete_scheduled_event(event:, slot:, owner:, now: Time.now.utc)
|
|
90
|
+
key = [event.to_s, slot.to_s]
|
|
91
|
+
@mutex.synchronize do
|
|
92
|
+
claim = @claims[key]
|
|
93
|
+
claim[:completed_at] = now if claim && claim.fetch(:owner) == owner.to_s
|
|
94
|
+
lease = @leases[event.to_s]
|
|
95
|
+
@leases.delete(event.to_s) \
|
|
96
|
+
if lease && lease.fetch(:owner) == owner.to_s && lease.fetch(:slot) == slot.to_s
|
|
97
|
+
end
|
|
98
|
+
true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def renew_scheduled_event(event:, slot:, owner:, lease_until:)
|
|
102
|
+
key = [event.to_s, slot.to_s]
|
|
103
|
+
@mutex.synchronize do
|
|
104
|
+
claim = @claims[key]
|
|
105
|
+
return false unless claim && claim.fetch(:owner) == owner.to_s && !claim[:completed_at]
|
|
106
|
+
|
|
107
|
+
claim[:lease_expires_at] = lease_until
|
|
108
|
+
lease = @leases[event.to_s]
|
|
109
|
+
lease[:expires_at] = lease_until \
|
|
110
|
+
if lease && lease.fetch(:owner) == owner.to_s && lease.fetch(:slot) == slot.to_s
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def close = nil
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def prune_sessions(now)
|
|
120
|
+
@sessions.delete_if { |_token, record| record.expires_at <= now }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def copy(value) = JSON.parse(JSON.generate(value))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# SQLite implementation safe for multiple Ruby processes sharing a file.
|
|
127
|
+
# BEGIN IMMEDIATE serializes claim decisions; primary keys make a schedule
|
|
128
|
+
# slot idempotent even after a process restart.
|
|
129
|
+
class SQLiteSharedStore
|
|
130
|
+
include SessionStore
|
|
131
|
+
include SchedulerCoordinator
|
|
132
|
+
|
|
133
|
+
attr_reader :database
|
|
134
|
+
|
|
135
|
+
def initialize(path, busy_timeout: 5_000)
|
|
136
|
+
raw_path = path.to_s
|
|
137
|
+
raise ArgumentError, 'shared store path must not be empty' if raw_path.empty?
|
|
138
|
+
|
|
139
|
+
expanded = raw_path == ':memory:' ? raw_path : File.expand_path(raw_path)
|
|
140
|
+
|
|
141
|
+
FileUtils.mkdir_p(File.dirname(expanded)) unless expanded == ':memory:'
|
|
142
|
+
@database = SQLite3::Database.new(expanded)
|
|
143
|
+
@database.results_as_hash = true
|
|
144
|
+
@database.busy_timeout = Integer(busy_timeout)
|
|
145
|
+
@mutex = Mutex.new
|
|
146
|
+
configure!
|
|
147
|
+
migrate!
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def write_session(token:, identity:, expires_at:)
|
|
151
|
+
synchronize do
|
|
152
|
+
database.execute(
|
|
153
|
+
<<~SQL, [session_key(token), JSON.generate(identity), timestamp(expires_at)]
|
|
154
|
+
INSERT INTO mxrb_runtime_sessions (token, identity_json, expires_at)
|
|
155
|
+
VALUES (?, ?, ?)
|
|
156
|
+
ON CONFLICT(token) DO UPDATE SET
|
|
157
|
+
identity_json = excluded.identity_json,
|
|
158
|
+
expires_at = excluded.expires_at
|
|
159
|
+
SQL
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
token
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def read_session(token, now: Time.now.utc)
|
|
166
|
+
row = synchronize do
|
|
167
|
+
database.execute('DELETE FROM mxrb_runtime_sessions WHERE expires_at <= ?', [timestamp(now)])
|
|
168
|
+
database.get_first_row(
|
|
169
|
+
'SELECT token, identity_json, expires_at FROM mxrb_runtime_sessions WHERE token = ?',
|
|
170
|
+
[session_key(token)]
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
return unless row
|
|
174
|
+
|
|
175
|
+
SessionRecord.new(
|
|
176
|
+
token.to_s, JSON.parse(row.fetch('identity_json')),
|
|
177
|
+
Time.at(row.fetch('expires_at').to_f).utc
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def delete_session(token)
|
|
182
|
+
synchronize do
|
|
183
|
+
database.execute('DELETE FROM mxrb_runtime_sessions WHERE token = ?', [session_key(token)])
|
|
184
|
+
database.changes.positive?
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def claim_scheduled_event(event:, slot:, owner:, now:, lease_until:, skip_overlap:)
|
|
189
|
+
transaction do
|
|
190
|
+
claim = database.get_first_row(
|
|
191
|
+
<<~SQL, [event.to_s, slot.to_s]
|
|
192
|
+
SELECT completed_at, lease_expires_at
|
|
193
|
+
FROM mxrb_runtime_scheduled_claims
|
|
194
|
+
WHERE event_key = ? AND slot = ?
|
|
195
|
+
SQL
|
|
196
|
+
)
|
|
197
|
+
next false if claim && (claim['completed_at'] || claim.fetch('lease_expires_at').to_f > timestamp(now))
|
|
198
|
+
|
|
199
|
+
if skip_overlap
|
|
200
|
+
active = database.get_first_value(
|
|
201
|
+
'SELECT 1 FROM mxrb_runtime_scheduler_leases WHERE event_key = ? AND expires_at > ?',
|
|
202
|
+
[event.to_s, timestamp(now)]
|
|
203
|
+
)
|
|
204
|
+
next false if active
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
database.execute(
|
|
208
|
+
<<~SQL, [event.to_s, slot.to_s, owner.to_s, timestamp(now), timestamp(lease_until)]
|
|
209
|
+
INSERT INTO mxrb_runtime_scheduled_claims
|
|
210
|
+
(event_key, slot, owner, claimed_at, lease_expires_at, completed_at)
|
|
211
|
+
VALUES (?, ?, ?, ?, ?, NULL)
|
|
212
|
+
ON CONFLICT(event_key, slot) DO UPDATE SET
|
|
213
|
+
owner = excluded.owner,
|
|
214
|
+
claimed_at = excluded.claimed_at,
|
|
215
|
+
lease_expires_at = excluded.lease_expires_at,
|
|
216
|
+
completed_at = NULL
|
|
217
|
+
SQL
|
|
218
|
+
)
|
|
219
|
+
if skip_overlap
|
|
220
|
+
database.execute(
|
|
221
|
+
<<~SQL, [event.to_s, slot.to_s, owner.to_s, timestamp(lease_until)]
|
|
222
|
+
INSERT INTO mxrb_runtime_scheduler_leases (event_key, slot, owner, expires_at)
|
|
223
|
+
VALUES (?, ?, ?, ?)
|
|
224
|
+
ON CONFLICT(event_key) DO UPDATE SET
|
|
225
|
+
slot = excluded.slot,
|
|
226
|
+
owner = excluded.owner,
|
|
227
|
+
expires_at = excluded.expires_at
|
|
228
|
+
SQL
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def complete_scheduled_event(event:, slot:, owner:, now: Time.now.utc)
|
|
236
|
+
transaction do
|
|
237
|
+
database.execute(
|
|
238
|
+
<<~SQL, [timestamp(now), event.to_s, slot.to_s, owner.to_s]
|
|
239
|
+
UPDATE mxrb_runtime_scheduled_claims
|
|
240
|
+
SET completed_at = ?
|
|
241
|
+
WHERE event_key = ? AND slot = ? AND owner = ?
|
|
242
|
+
SQL
|
|
243
|
+
)
|
|
244
|
+
database.execute(
|
|
245
|
+
<<~SQL, [event.to_s, slot.to_s, owner.to_s]
|
|
246
|
+
DELETE FROM mxrb_runtime_scheduler_leases
|
|
247
|
+
WHERE event_key = ? AND slot = ? AND owner = ?
|
|
248
|
+
SQL
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
true
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def renew_scheduled_event(event:, slot:, owner:, lease_until:)
|
|
255
|
+
transaction do
|
|
256
|
+
database.execute(
|
|
257
|
+
<<~SQL, [timestamp(lease_until), event.to_s, slot.to_s, owner.to_s]
|
|
258
|
+
UPDATE mxrb_runtime_scheduled_claims
|
|
259
|
+
SET lease_expires_at = ?
|
|
260
|
+
WHERE event_key = ? AND slot = ? AND owner = ? AND completed_at IS NULL
|
|
261
|
+
SQL
|
|
262
|
+
)
|
|
263
|
+
next false unless database.changes.positive?
|
|
264
|
+
|
|
265
|
+
database.execute(
|
|
266
|
+
<<~SQL, [timestamp(lease_until), event.to_s, slot.to_s, owner.to_s]
|
|
267
|
+
UPDATE mxrb_runtime_scheduler_leases
|
|
268
|
+
SET expires_at = ?
|
|
269
|
+
WHERE event_key = ? AND slot = ? AND owner = ?
|
|
270
|
+
SQL
|
|
271
|
+
)
|
|
272
|
+
true
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def close
|
|
277
|
+
@mutex.synchronize { database.close unless database.closed? }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
def configure!
|
|
283
|
+
database.execute('PRAGMA journal_mode = WAL')
|
|
284
|
+
database.execute('PRAGMA synchronous = NORMAL')
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def migrate!
|
|
288
|
+
synchronize do
|
|
289
|
+
database.execute_batch(<<~SQL)
|
|
290
|
+
CREATE TABLE IF NOT EXISTS mxrb_runtime_sessions (
|
|
291
|
+
token TEXT PRIMARY KEY,
|
|
292
|
+
identity_json TEXT NOT NULL,
|
|
293
|
+
expires_at REAL NOT NULL
|
|
294
|
+
);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS mxrb_runtime_sessions_expiration
|
|
296
|
+
ON mxrb_runtime_sessions (expires_at);
|
|
297
|
+
CREATE TABLE IF NOT EXISTS mxrb_runtime_scheduled_claims (
|
|
298
|
+
event_key TEXT NOT NULL,
|
|
299
|
+
slot TEXT NOT NULL,
|
|
300
|
+
owner TEXT NOT NULL,
|
|
301
|
+
claimed_at REAL NOT NULL,
|
|
302
|
+
lease_expires_at REAL NOT NULL,
|
|
303
|
+
completed_at REAL,
|
|
304
|
+
PRIMARY KEY (event_key, slot)
|
|
305
|
+
);
|
|
306
|
+
CREATE TABLE IF NOT EXISTS mxrb_runtime_scheduler_leases (
|
|
307
|
+
event_key TEXT PRIMARY KEY,
|
|
308
|
+
slot TEXT NOT NULL,
|
|
309
|
+
owner TEXT NOT NULL,
|
|
310
|
+
expires_at REAL NOT NULL
|
|
311
|
+
);
|
|
312
|
+
SQL
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def transaction
|
|
317
|
+
synchronize do
|
|
318
|
+
database.execute('BEGIN IMMEDIATE')
|
|
319
|
+
result = yield
|
|
320
|
+
database.execute('COMMIT')
|
|
321
|
+
result
|
|
322
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
323
|
+
database.execute('ROLLBACK') if database.transaction_active?
|
|
324
|
+
raise
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def synchronize(&block) = @mutex.synchronize(&block)
|
|
329
|
+
def timestamp(value) = value.to_f
|
|
330
|
+
def session_key(token) = Digest::SHA256.hexdigest(token.to_s)
|
|
331
|
+
end
|
|
332
|
+
# rubocop:enable Metrics/AbcSize, Metrics/BlockLength, Metrics/ClassLength
|
|
333
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ParameterLists
|
|
334
|
+
end
|
|
335
|
+
end
|