interfacets 0.1.0 → 0.9.9
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/.rubocop.yml +173 -1
- data/LICENSE +21 -0
- data/Rakefile +4 -6
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +209 -0
- data/lib/interfacets/client/bus.rb +69 -0
- data/lib/interfacets/client/channels/api.rb +95 -0
- data/lib/interfacets/client/channels/audio.rb +101 -0
- data/lib/interfacets/client/channels/base.rb +28 -0
- data/lib/interfacets/client/channels/page_visibility.rb +21 -0
- data/lib/interfacets/client/channels/react/builder.rb +203 -0
- data/lib/interfacets/client/channels/react/channel.rb +61 -0
- data/lib/interfacets/client/channels/react/dom.rb +91 -0
- data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
- data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
- data/lib/interfacets/client/channels/timer.rb +51 -0
- data/lib/interfacets/client/channels/url.rb +52 -0
- data/lib/interfacets/client/config.rb +22 -0
- data/lib/interfacets/client/delegator.rb +37 -0
- data/lib/interfacets/client/facet.rb +26 -0
- data/lib/interfacets/client/facet2.rb +15 -0
- data/lib/interfacets/client/facets/attributes/accessor.rb +28 -0
- data/lib/interfacets/client/facets/attributes/association.rb +50 -0
- data/lib/interfacets/client/facets/attributes/bind.rb +25 -0
- data/lib/interfacets/client/facets/attributes/collection.rb +47 -0
- data/lib/interfacets/client/facets/attributes/readonly.rb +19 -0
- data/lib/interfacets/client/facets/deserializer.rb +30 -0
- data/lib/interfacets/client/facets/schema/deserializer.rb +63 -0
- data/lib/interfacets/client/facets/schema.rb +63 -0
- data/lib/interfacets/client/facets/serializer.rb +18 -0
- data/lib/interfacets/client/registry.rb +84 -0
- data/lib/interfacets/client/system.rb +88 -0
- data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
- data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
- data/lib/interfacets/client/utils/open_struct.rb +102 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client/view.rb +47 -0
- data/lib/interfacets/client.rb +13 -0
- data/lib/interfacets/mruby/build.dockerfile +66 -0
- data/lib/interfacets/mruby/build_config.rb +20 -0
- data/lib/interfacets/mruby/entrypoint.rb +23 -0
- data/lib/interfacets/mruby/init.c +66 -0
- data/lib/interfacets/server/api.rb +64 -0
- data/lib/interfacets/server/assets/facet.rb +61 -0
- data/lib/interfacets/server/assets.rb +210 -0
- data/lib/interfacets/server/basic_routable.rb +40 -0
- data/lib/interfacets/server/basic_router.rb +74 -0
- data/lib/interfacets/server/bus.rb +39 -0
- data/lib/interfacets/server/config.rb +87 -0
- data/lib/interfacets/server/facet.rb +51 -0
- data/lib/interfacets/server/facets/deserializer.rb +25 -0
- data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
- data/lib/interfacets/server/facets/serializer.rb +50 -0
- data/lib/interfacets/server/registry.rb +212 -0
- data/lib/interfacets/shared/entities/bus.rb +218 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +117 -0
- data/lib/interfacets/shared/entities/specs.rb +124 -0
- data/lib/interfacets/shared/entity.rb +178 -0
- data/lib/interfacets/shared/entity_collection.rb +88 -0
- data/lib/interfacets/shared/generated_store.rb +145 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +71 -0
- data/lib/interfacets/test/browser.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +91 -0
- data/lib/interfacets/test/js/nodo_bus.rb +81 -0
- data/lib/interfacets/test/js/receivers/api.rb +37 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +167 -0
- data/lib/interfacets/test/js/receivers/react.rb +31 -0
- data/lib/interfacets/test/js/receivers/url.rb +55 -0
- data/lib/interfacets/test.rb +17 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +26 -2
- metadata +103 -6
- data/README.md +0 -35
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
class Api
|
|
6
|
+
class Channel
|
|
7
|
+
attr_reader :klass, :store
|
|
8
|
+
|
|
9
|
+
def rendered?
|
|
10
|
+
@rendered
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def render(klass, store)
|
|
14
|
+
@rendered = true
|
|
15
|
+
@klass = klass
|
|
16
|
+
@store = store
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :entity, :name, :registry
|
|
21
|
+
def initialize(entity:, name:, registry:)
|
|
22
|
+
@entity = entity
|
|
23
|
+
@name = name
|
|
24
|
+
@registry = registry
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def handle(event)
|
|
28
|
+
channel = Channel.new
|
|
29
|
+
|
|
30
|
+
entity.channel = channel
|
|
31
|
+
Shared::Entities::Bus
|
|
32
|
+
.new(entity:)
|
|
33
|
+
.handle(event:)
|
|
34
|
+
|
|
35
|
+
if entity.channel.rendered?
|
|
36
|
+
registry.build(
|
|
37
|
+
channel.klass,
|
|
38
|
+
channel.store,
|
|
39
|
+
).render
|
|
40
|
+
else
|
|
41
|
+
emit("after_#{event.fetch("action")}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render
|
|
46
|
+
emit("after_load")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def emit(action)
|
|
52
|
+
{
|
|
53
|
+
facet: name,
|
|
54
|
+
payload: (
|
|
55
|
+
Shared::Entities::Bus
|
|
56
|
+
.new(entity:)
|
|
57
|
+
.serialize(to: "client", action:, nesting: ["root"])
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
module Assets
|
|
6
|
+
class Facet
|
|
7
|
+
def self.register(klass:, assets:)
|
|
8
|
+
assets[klass.name] = new(klass).code
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :klass
|
|
12
|
+
def initialize(klass)
|
|
13
|
+
@klass = klass
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def code
|
|
17
|
+
# nest it all so names resolve
|
|
18
|
+
*mods, klass_name = klass.name.split("::")
|
|
19
|
+
|
|
20
|
+
headers = []
|
|
21
|
+
footers = []
|
|
22
|
+
|
|
23
|
+
current_mod = ""
|
|
24
|
+
mods.each do |mod_name|
|
|
25
|
+
current_mod += "::#{mod_name}"
|
|
26
|
+
type = current_mod.constantize.is_a?(Module) ? :module : :class
|
|
27
|
+
headers << "#{type} #{mod_name}"
|
|
28
|
+
footers << "end"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
headers << "class #{klass_name} < Interfacets::Client::Facet"
|
|
32
|
+
footers << "end"
|
|
33
|
+
|
|
34
|
+
<<~TXT
|
|
35
|
+
#{headers.join("\n")}
|
|
36
|
+
|
|
37
|
+
include Interfacets::Client::Facets::Schema
|
|
38
|
+
|
|
39
|
+
view_spec #{write_source(klass.client_config.view.block)}
|
|
40
|
+
|
|
41
|
+
entity_spec #{write_source(klass.client_config.api.block)}
|
|
42
|
+
|
|
43
|
+
client_spec #{write_source(klass.client_config.entity.block)}
|
|
44
|
+
#{footers.join("\n")}
|
|
45
|
+
TXT
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def write_source(block)
|
|
51
|
+
return unless block
|
|
52
|
+
|
|
53
|
+
RubyVM::AbstractSyntaxTree
|
|
54
|
+
.of(block, keep_script_lines: true)
|
|
55
|
+
.source
|
|
56
|
+
.strip
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Interfacets
|
|
6
|
+
module Server
|
|
7
|
+
module Assets
|
|
8
|
+
Const =
|
|
9
|
+
Data.define(:nesting, :type, :name, :parent) do
|
|
10
|
+
def full_name
|
|
11
|
+
[nesting.last&.full_name, name].compact.join("::")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Classifier
|
|
16
|
+
class Visitor < Prism::Visitor
|
|
17
|
+
# This class just gathers up all modules and classes defined
|
|
18
|
+
attr_reader :mods
|
|
19
|
+
def initialize
|
|
20
|
+
@stack = []
|
|
21
|
+
@mods = []
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def visit_module_node(node)
|
|
26
|
+
push_mod(node)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def visit_class_node(node)
|
|
30
|
+
push_mod(node)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def push_mod(node)
|
|
36
|
+
type = node.type == :module_node ? :module : :class
|
|
37
|
+
|
|
38
|
+
parent = (
|
|
39
|
+
if type == :class && node.child_nodes[1]
|
|
40
|
+
node.child_nodes[1].full_name
|
|
41
|
+
end
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const = Const.new(
|
|
45
|
+
nesting: @stack.dup,
|
|
46
|
+
name: node.constant_path.full_name,
|
|
47
|
+
type:,
|
|
48
|
+
parent:
|
|
49
|
+
)
|
|
50
|
+
@stack << const
|
|
51
|
+
mods << @stack.dup
|
|
52
|
+
visit_child_nodes(node)
|
|
53
|
+
@stack.pop
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
attr_reader :code
|
|
58
|
+
def initialize(code)
|
|
59
|
+
@code = code
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def consts
|
|
63
|
+
@consts ||= (
|
|
64
|
+
Prism.parse(code).value.accept(visitor)
|
|
65
|
+
visitor.mods.map(&:last)
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def visitor
|
|
70
|
+
@visitor ||= Visitor.new
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Serializer
|
|
75
|
+
attr_reader :paths, :file
|
|
76
|
+
def initialize(paths:, file: File)
|
|
77
|
+
@paths = paths
|
|
78
|
+
@file = file
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def as_json
|
|
82
|
+
{
|
|
83
|
+
fs_map:,
|
|
84
|
+
const_map:,
|
|
85
|
+
scaffolding:,
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def const_map
|
|
92
|
+
@const_map ||= (
|
|
93
|
+
full_map = Hash.new { |h, k| h[k] = [] }
|
|
94
|
+
|
|
95
|
+
paths
|
|
96
|
+
.each { |path|
|
|
97
|
+
consts_by_path.fetch(path).each { |c| full_map[c.full_name] << path }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
full_map.transform_values(&:uniq!)
|
|
101
|
+
full_map
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def scaffolding
|
|
106
|
+
serialized = {}
|
|
107
|
+
results = []
|
|
108
|
+
|
|
109
|
+
consts.each { |const|
|
|
110
|
+
push_bootstrapper(const, results, serialized)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
results
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def push_bootstrapper(const, results, serialized)
|
|
117
|
+
return if serialized.key?(const.full_name)
|
|
118
|
+
|
|
119
|
+
serialized[const.full_name] = true
|
|
120
|
+
|
|
121
|
+
const.nesting.each do |_nesting_const|
|
|
122
|
+
push_bootstrapper(const, results, serialized)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
parent = (
|
|
126
|
+
if const.parent
|
|
127
|
+
resolve_const_name(const.nesting.last&.full_name, const.parent)
|
|
128
|
+
end
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if parent.is_a?(Const)
|
|
132
|
+
push_bootstrapper(parent, results, serialized)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
parent_name = parent.is_a?(Const) ? parent.full_name : parent
|
|
136
|
+
|
|
137
|
+
parent_str = "< #{parent_name}" if parent
|
|
138
|
+
results << <<~TXT
|
|
139
|
+
#{const.type} #{const.full_name} #{parent_str}
|
|
140
|
+
class << self
|
|
141
|
+
include Interfacets::Client::Assets::AutoloadHook
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
TXT
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def resolve_const_name(nesting, name)
|
|
148
|
+
full_name = [nesting, name].compact.join("::")
|
|
149
|
+
return consts_by_name[full_name] if consts_by_name.key?(full_name)
|
|
150
|
+
|
|
151
|
+
# maybe a constant that we don't handle loading for (eg, StandardError)
|
|
152
|
+
return name if nesting.nil? || nesting.empty?
|
|
153
|
+
|
|
154
|
+
resolve_const_name(nesting.split("::")[0...-1].join("::"), name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def consts_by_name
|
|
158
|
+
@consts_by_name ||= consts.group_by(&:full_name).transform_values(&:first)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def fs_map
|
|
162
|
+
paths.map { [_1, file.read(_1)] }.to_h
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def consts
|
|
166
|
+
@consts ||= consts_by_path.values.flatten
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def consts_by_path
|
|
170
|
+
@consts_by_path ||= (
|
|
171
|
+
fs_map
|
|
172
|
+
.transform_values { |code| Classifier.new(code).consts }
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
GEM_DIRS = [
|
|
178
|
+
File.expand_path(File.join(__dir__, "../shared")),
|
|
179
|
+
File.expand_path(File.join(__dir__, "../client")),
|
|
180
|
+
File.expand_path(File.join(__dir__, "../client.rb")),
|
|
181
|
+
].freeze
|
|
182
|
+
|
|
183
|
+
class << self
|
|
184
|
+
def bundle(dirs:, registry:)
|
|
185
|
+
|
|
186
|
+
registry.serialize
|
|
187
|
+
|
|
188
|
+
(dirs + GEM_DIRS + [registry.build_dir])
|
|
189
|
+
.flat_map { paths(_1) }
|
|
190
|
+
.flatten
|
|
191
|
+
.map(&:to_s)
|
|
192
|
+
.then { Serializer.new(paths: _1).as_json }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def paths(path)
|
|
198
|
+
if File.file?(path)
|
|
199
|
+
[path]
|
|
200
|
+
else
|
|
201
|
+
[
|
|
202
|
+
Dir.glob(File.join(path, "*.rb")),
|
|
203
|
+
Dir.glob(File.join(path, "**/*.rb")),
|
|
204
|
+
].flatten
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
module BasicRoutable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
attach(self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def attach(mod)
|
|
15
|
+
mod.shared do
|
|
16
|
+
accessor(:api_path, accepted_by: :client)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
mod.server do
|
|
20
|
+
attr_accessor :api_path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inherited(mod)
|
|
25
|
+
attach(mod)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_accessor :bus
|
|
29
|
+
def find(&block)
|
|
30
|
+
@find = block if block_given?
|
|
31
|
+
@find
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build(klass, store)
|
|
35
|
+
bus.registry.build(klass, store)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
class BasicRouter
|
|
6
|
+
class Evaluator
|
|
7
|
+
attr_reader :bus
|
|
8
|
+
def initialize(bus:)
|
|
9
|
+
@bus = bus
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(klass:, id:, query:, resolved_path:)
|
|
13
|
+
klass.bus = bus
|
|
14
|
+
klass
|
|
15
|
+
.find
|
|
16
|
+
.call(id, query:)
|
|
17
|
+
.tap { _1.entity.api_path = resolved_path }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :paths, :bus, :default
|
|
22
|
+
def initialize(bus:, paths:, default: nil)
|
|
23
|
+
@bus = bus
|
|
24
|
+
@paths = paths
|
|
25
|
+
@default = default
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(url, query: {})
|
|
29
|
+
if url.nil? && default
|
|
30
|
+
klass = klass_for(default)
|
|
31
|
+
evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
path = url.sub(/^#{bus.root_url}/, "/").sub(/^/, "/").sub(%r{^/*}, "/")
|
|
35
|
+
|
|
36
|
+
if normalized_paths.key?(path)
|
|
37
|
+
klass = klass_for(path)
|
|
38
|
+
|
|
39
|
+
evaluator.call(klass:, id: nil, query:, resolved_path: path)
|
|
40
|
+
else
|
|
41
|
+
*parts, id = path.split("/")
|
|
42
|
+
resolved_base_path = parts.join("/")
|
|
43
|
+
|
|
44
|
+
if normalized_paths.key?(resolved_base_path)
|
|
45
|
+
klass = klass_for(resolved_base_path)
|
|
46
|
+
evaluator.call(klass:, id:, query:, resolved_path: path)
|
|
47
|
+
elsif default
|
|
48
|
+
klass = klass_for(default)
|
|
49
|
+
evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
50
|
+
else
|
|
51
|
+
raise "unhandled route: #{path}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def evaluator
|
|
57
|
+
@evaluator ||= Evaluator.new(bus:)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def klass_for(path)
|
|
61
|
+
val = normalized_paths.fetch(path)
|
|
62
|
+
|
|
63
|
+
(val.is_a?(String) ? Object.const_get(val) : val)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def normalized_paths
|
|
67
|
+
@normalized_paths ||= (
|
|
68
|
+
paths
|
|
69
|
+
.transform_keys { _1.start_with?("/") ? _1 : "/#{_1}" }
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
class Bus
|
|
6
|
+
attr_reader(
|
|
7
|
+
:root_url,
|
|
8
|
+
:asset_paths,
|
|
9
|
+
:registry,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def initialize(
|
|
13
|
+
root_url:,
|
|
14
|
+
asset_paths:,
|
|
15
|
+
facets:,
|
|
16
|
+
build_dir:
|
|
17
|
+
)
|
|
18
|
+
@registry = Registry.new(facets:, build_dir:)
|
|
19
|
+
@root_url = root_url
|
|
20
|
+
@asset_paths = asset_paths
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def client_system_json
|
|
24
|
+
{
|
|
25
|
+
assets: Assets.bundle(dirs: asset_paths, registry:),
|
|
26
|
+
root_url:
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build(name:, store:)
|
|
31
|
+
registry.build(name, store)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle(name:, id:, event:)
|
|
35
|
+
registry.load(name, id).handle(event)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
class Config
|
|
6
|
+
attr_reader :registry
|
|
7
|
+
attr_accessor(
|
|
8
|
+
:mount_point,
|
|
9
|
+
:host,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def initialize(
|
|
13
|
+
mount_point:,
|
|
14
|
+
registry:,
|
|
15
|
+
assets:
|
|
16
|
+
)
|
|
17
|
+
@mount_point = mount_point
|
|
18
|
+
@registry = registry
|
|
19
|
+
@assets = assets
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def bundled_assets
|
|
23
|
+
dirs = @assets.map(&:to_s)
|
|
24
|
+
|
|
25
|
+
facets = (
|
|
26
|
+
@registry
|
|
27
|
+
.values
|
|
28
|
+
.map { Object.const_get(_1) }
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
Interfacets::Server::Assets.bundle(dirs:, facets:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def schema
|
|
35
|
+
@facet_registry
|
|
36
|
+
.values
|
|
37
|
+
.map { Object.const_get(_1) }
|
|
38
|
+
.then {
|
|
39
|
+
Interfacets::Server::Facets::Schema::Serializer
|
|
40
|
+
.call(facets: _1)
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def register_facet(name, class_or_class_name)
|
|
45
|
+
# if klass.name is nil, then just use the class reference
|
|
46
|
+
@facet_registry[name] = class_or_class_name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def all_facets
|
|
50
|
+
@facet_registry.keys.map { facet(_1) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def default_facet
|
|
54
|
+
build_facet(facet_uid: @default_facet)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def facet(name)
|
|
58
|
+
klass_name = (
|
|
59
|
+
if @facet_registry.key?(name)
|
|
60
|
+
@facet_registry.fetch(name)
|
|
61
|
+
elsif @facet_registry.values.include?(name)
|
|
62
|
+
name
|
|
63
|
+
else
|
|
64
|
+
raise("unknown facet_class: #{name}")
|
|
65
|
+
end
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
klass_name.constantize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ingest_facet(data)
|
|
72
|
+
Facets::Deserializer.call(data:, config: self)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_facet(facet_uid: nil, name: nil, id: nil, query: {})
|
|
76
|
+
if name
|
|
77
|
+
facet(name).build(id, query:)
|
|
78
|
+
elsif @facet_registry.key?(facet_uid)
|
|
79
|
+
facet(facet_uid).build(nil, query:)
|
|
80
|
+
else
|
|
81
|
+
*name, id = facet_uid.split("/")
|
|
82
|
+
facet(name.join("/")).build(id, query:)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "active_support/concern"
|
|
3
|
+
|
|
4
|
+
module Interfacets
|
|
5
|
+
module Server
|
|
6
|
+
module Facet
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
class Actor
|
|
10
|
+
def initialize(entity:, facet:)
|
|
11
|
+
@entity = entity
|
|
12
|
+
@facet = facet
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class_methods do
|
|
17
|
+
def view(&block)
|
|
18
|
+
views << block
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def views
|
|
22
|
+
@views ||= []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def client(&block)
|
|
26
|
+
clients << block
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clients
|
|
30
|
+
@clients ||= []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def shared(&block)
|
|
34
|
+
shareds << block
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shareds
|
|
38
|
+
@shareds ||= []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def server(&block)
|
|
42
|
+
servers << block
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def servers
|
|
46
|
+
@servers ||= []
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
module Facets
|
|
6
|
+
class Deserializer
|
|
7
|
+
class << self
|
|
8
|
+
def call(data:, config:)
|
|
9
|
+
facet_class_name = data.fetch("facet_class")
|
|
10
|
+
attributes = data.fetch("attributes")
|
|
11
|
+
|
|
12
|
+
facet = (
|
|
13
|
+
config
|
|
14
|
+
.facet(facet_class_name)
|
|
15
|
+
.build(attributes.fetch("id"))
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
facet.entity.ingest(attributes)
|
|
19
|
+
facet
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
module Facets
|
|
6
|
+
module Schema
|
|
7
|
+
class Serializer
|
|
8
|
+
def self.call(...)
|
|
9
|
+
new(...).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private attr_reader(:facets, :schema)
|
|
13
|
+
def initialize(facets:)
|
|
14
|
+
@facets = facets
|
|
15
|
+
@schema = { views: {}, entities: {}, apis: {} }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
facets.each do |facet|
|
|
20
|
+
config = facet.client_config
|
|
21
|
+
|
|
22
|
+
schema[:views][config.view.name] = {
|
|
23
|
+
body: write_source(config.view.block),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
schema[:entities][config.entity.name] = {
|
|
27
|
+
api_type: config.api.name,
|
|
28
|
+
body: write_source(config.entity.block),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
schema[:apis][config.api.name] = {
|
|
32
|
+
body: write_source(config.api.block),
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
schema
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def write_source(block)
|
|
42
|
+
return unless block
|
|
43
|
+
|
|
44
|
+
RubyVM::AbstractSyntaxTree
|
|
45
|
+
.of(block, keep_script_lines: true)
|
|
46
|
+
.source
|
|
47
|
+
.then { block.is_a?(UnboundMethod) ? _1 : "lambda #{_1}" }
|
|
48
|
+
.strip
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|