interfacets 0.1.0 → 0.9.99
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/.tmp +5 -0
- data/LICENSE +21 -0
- data/Rakefile +9 -7
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +210 -0
- data/lib/interfacets/client/bus.rb +73 -0
- data/lib/interfacets/client/channels/api.rb +102 -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/registry.rb +23 -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 +115 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client.rb +13 -0
- data/lib/interfacets/component_registry.rb +115 -0
- data/lib/interfacets/component_schema_parser.rb +84 -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 +44 -0
- data/lib/interfacets/server/assets/facet.rb +63 -0
- data/lib/interfacets/server/assets.rb +216 -0
- data/lib/interfacets/server/basic_router.rb +79 -0
- data/lib/interfacets/server/bus.rb +34 -0
- data/lib/interfacets/server/config.rb +87 -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 +51 -0
- data/lib/interfacets/shared/basic_routable.rb +45 -0
- data/lib/interfacets/shared/entities/bus.rb +230 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
- data/lib/interfacets/shared/entities/specs.rb +161 -0
- data/lib/interfacets/shared/entity.rb +102 -0
- data/lib/interfacets/shared/entity_dsl.rb +154 -0
- data/lib/interfacets/shared/facet.rb +200 -0
- data/lib/interfacets/shared/generated_store.rb +149 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +75 -0
- data/lib/interfacets/shared/view.rb +74 -0
- data/lib/interfacets/test/component_registry.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +100 -0
- data/lib/interfacets/test/js/nodo_bus.rb +98 -0
- data/lib/interfacets/test/js/receivers/api.rb +48 -0
- data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
- data/lib/interfacets/test/js/receivers/react.rb +32 -0
- data/lib/interfacets/test/js/receivers/timer.rb +77 -0
- data/lib/interfacets/test/js/receivers/url.rb +60 -0
- data/lib/interfacets/test/standard_elements.yml +173 -0
- data/lib/interfacets/test/ui_simulator.rb +75 -0
- data/lib/interfacets/test/validation_engine.rb +151 -0
- data/lib/interfacets/test.rb +13 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +29 -2
- metadata +114 -6
- data/README.md +0 -35
|
@@ -0,0 +1,216 @@
|
|
|
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(nesting_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.reject(&:empty?).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
|
+
@fs_map ||= 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:, only_facets: false)
|
|
185
|
+
|
|
186
|
+
target_dirs = (
|
|
187
|
+
if only_facets
|
|
188
|
+
dirs
|
|
189
|
+
else
|
|
190
|
+
GEM_DIRS + dirs
|
|
191
|
+
end
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
target_dirs
|
|
195
|
+
.flat_map { paths(_1) }
|
|
196
|
+
.flatten
|
|
197
|
+
.map(&:to_s)
|
|
198
|
+
.then { Serializer.new(paths: _1).as_json }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def paths(path)
|
|
204
|
+
if File.file?(path)
|
|
205
|
+
[path]
|
|
206
|
+
else
|
|
207
|
+
[
|
|
208
|
+
Dir.glob(File.join(path, "*.rb")),
|
|
209
|
+
Dir.glob(File.join(path, "**/*.rb")),
|
|
210
|
+
].flatten
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
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.server_entity_class
|
|
14
|
+
.find_for_basic_routing(id, query:, bus: bus)
|
|
15
|
+
.tap { _1.entity.api_path = resolved_path }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :paths, :bus, :default
|
|
20
|
+
def initialize(bus:, paths:, default: nil)
|
|
21
|
+
@bus = bus
|
|
22
|
+
@paths = paths
|
|
23
|
+
@default = default
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call(url, query: {})
|
|
27
|
+
path = normalize(url)
|
|
28
|
+
|
|
29
|
+
if path.nil? && default
|
|
30
|
+
klass = klass_for(default)
|
|
31
|
+
return evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if normalized_paths.key?(path)
|
|
35
|
+
klass = klass_for(path)
|
|
36
|
+
|
|
37
|
+
evaluator.call(klass:, id: nil, query:, resolved_path: path)
|
|
38
|
+
else
|
|
39
|
+
*parts, id = path.split("/")
|
|
40
|
+
resolved_base_path = parts.join("/")
|
|
41
|
+
|
|
42
|
+
if normalized_paths.key?(resolved_base_path)
|
|
43
|
+
klass = klass_for(resolved_base_path)
|
|
44
|
+
evaluator.call(klass:, id:, query:, resolved_path: path)
|
|
45
|
+
elsif default
|
|
46
|
+
klass = klass_for(default)
|
|
47
|
+
evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
48
|
+
else
|
|
49
|
+
raise "unhandled route: #{path}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def evaluator
|
|
55
|
+
@evaluator ||= Evaluator.new(bus:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def klass_for(path)
|
|
59
|
+
val = normalized_paths.fetch(normalize(path))
|
|
60
|
+
|
|
61
|
+
(val.is_a?(String) ? Object.const_get(val) : val)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def normalized_paths
|
|
65
|
+
@normalized_paths ||= (
|
|
66
|
+
paths
|
|
67
|
+
.transform_keys { normalize(_1) }
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalize(path)
|
|
72
|
+
path
|
|
73
|
+
&.sub(/^#{bus.root_url}/, "/")
|
|
74
|
+
&.sub(/^/, "/")
|
|
75
|
+
&.sub(%r{^/*}, "/")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
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(root_url:, asset_paths:)
|
|
13
|
+
@registry = Registry.new
|
|
14
|
+
@root_url = root_url
|
|
15
|
+
@asset_paths = asset_paths
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def client_system_json(only_facets: false)
|
|
19
|
+
{
|
|
20
|
+
assets: Assets.bundle(dirs: asset_paths, registry:, only_facets:),
|
|
21
|
+
root_url:
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build(name:, store:)
|
|
26
|
+
registry.build(name, store)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle(name:, id:, event:)
|
|
30
|
+
registry.load(name, id).handle(event)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
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::Shared::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,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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Server
|
|
5
|
+
module Facets
|
|
6
|
+
class Serializer
|
|
7
|
+
def self.call(...)
|
|
8
|
+
new(...).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :facet, :mount_point
|
|
12
|
+
def initialize(facet, mount_point)
|
|
13
|
+
@facet = facet
|
|
14
|
+
@mount_point = mount_point
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
{
|
|
19
|
+
id: facet.entity.id,
|
|
20
|
+
facet_class: facet.class.name,
|
|
21
|
+
attributes: facet.entity.serialize,
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def attributes(entity)
|
|
26
|
+
entity
|
|
27
|
+
.attribute_specs
|
|
28
|
+
.each_with_object({}) { |(name, _attribute), json|
|
|
29
|
+
# refactor this, shouldn't be attributes...
|
|
30
|
+
# next if attribute.is_a?(Facets::Attributes::ClientEvent)
|
|
31
|
+
# next if attribute.is_a?(Facets::Attributes::ServerEvent)
|
|
32
|
+
# next if attribute.is_a?(Facets::Attributes::Inherit)
|
|
33
|
+
|
|
34
|
+
value = entity.public_send(name)
|
|
35
|
+
|
|
36
|
+
json[name] = (
|
|
37
|
+
if value.is_a?(Array)
|
|
38
|
+
value.map { _1.is_a?(Entity) || _1.is_a?(Facet::Handler) ? attributes(_1) : _1 }
|
|
39
|
+
elsif value.is_a?(Entity) || value.is_a?(Facet::Handler)
|
|
40
|
+
attributes(value)
|
|
41
|
+
else
|
|
42
|
+
value
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/all"
|
|
4
|
+
require "ostruct"
|
|
5
|
+
|
|
6
|
+
module Interfacets
|
|
7
|
+
module Server
|
|
8
|
+
class Registry
|
|
9
|
+
class Channel
|
|
10
|
+
def initialize(registry:)
|
|
11
|
+
@registry = registry
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build(...)
|
|
15
|
+
@registry.build(...)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render_facet
|
|
19
|
+
@facet.render
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def rendered?
|
|
23
|
+
@facet
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render(klass, store)
|
|
27
|
+
@facet = @registry.build(klass, store)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build(name, store)
|
|
32
|
+
facet = name.is_a?(Module) ? name : Object.const_get(name)
|
|
33
|
+
|
|
34
|
+
Api.new(
|
|
35
|
+
registry: self,
|
|
36
|
+
name:,
|
|
37
|
+
entity: (
|
|
38
|
+
facet
|
|
39
|
+
.server_entity_class
|
|
40
|
+
.new(
|
|
41
|
+
store: store.is_a?(Hash) ? OpenStruct.new(store) : store,
|
|
42
|
+
nesting: "root",
|
|
43
|
+
parent: nil,
|
|
44
|
+
channel: Channel.new(registry: self)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module BasicRoutable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :bus
|
|
11
|
+
|
|
12
|
+
def build(klass, store)
|
|
13
|
+
bus.registry.build(klass, store)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# This is unfortunate, but in order to be able to do:
|
|
18
|
+
|
|
19
|
+
# build(self, ...)
|
|
20
|
+
# we need the self to be the facet.
|
|
21
|
+
# We could also update the registry to handle being passed a server
|
|
22
|
+
# entity, but I'm currently tweaking the registry. so this is a TODO.
|
|
23
|
+
facet = self
|
|
24
|
+
|
|
25
|
+
entity_base do
|
|
26
|
+
accessor(:api_path, accepted_by: :client)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
server_entity(unshift: true) do
|
|
30
|
+
attr_accessor :api_path
|
|
31
|
+
|
|
32
|
+
define_singleton_method(:find) do |&block|
|
|
33
|
+
@find = block if block
|
|
34
|
+
@find
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
define_singleton_method(:find_for_basic_routing) do |id, query:, bus:|
|
|
38
|
+
facet.bus = bus
|
|
39
|
+
facet.instance_exec(id, query:, &find)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|