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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +173 -1
  3. data/.tmp +5 -0
  4. data/LICENSE +21 -0
  5. data/Rakefile +9 -7
  6. data/lib/interfacets/client/actor.rb +20 -0
  7. data/lib/interfacets/client/assets.rb +210 -0
  8. data/lib/interfacets/client/bus.rb +73 -0
  9. data/lib/interfacets/client/channels/api.rb +102 -0
  10. data/lib/interfacets/client/channels/audio.rb +101 -0
  11. data/lib/interfacets/client/channels/base.rb +28 -0
  12. data/lib/interfacets/client/channels/page_visibility.rb +21 -0
  13. data/lib/interfacets/client/channels/react/builder.rb +203 -0
  14. data/lib/interfacets/client/channels/react/channel.rb +61 -0
  15. data/lib/interfacets/client/channels/react/dom.rb +91 -0
  16. data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
  17. data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
  18. data/lib/interfacets/client/channels/timer.rb +51 -0
  19. data/lib/interfacets/client/channels/url.rb +52 -0
  20. data/lib/interfacets/client/config.rb +22 -0
  21. data/lib/interfacets/client/delegator.rb +37 -0
  22. data/lib/interfacets/client/registry.rb +23 -0
  23. data/lib/interfacets/client/system.rb +88 -0
  24. data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
  25. data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
  26. data/lib/interfacets/client/utils/open_struct.rb +115 -0
  27. data/lib/interfacets/client/utils/securerandom.rb +69 -0
  28. data/lib/interfacets/client.rb +13 -0
  29. data/lib/interfacets/component_registry.rb +115 -0
  30. data/lib/interfacets/component_schema_parser.rb +84 -0
  31. data/lib/interfacets/mruby/build.dockerfile +66 -0
  32. data/lib/interfacets/mruby/build_config.rb +20 -0
  33. data/lib/interfacets/mruby/entrypoint.rb +23 -0
  34. data/lib/interfacets/mruby/init.c +66 -0
  35. data/lib/interfacets/server/api.rb +44 -0
  36. data/lib/interfacets/server/assets/facet.rb +63 -0
  37. data/lib/interfacets/server/assets.rb +216 -0
  38. data/lib/interfacets/server/basic_router.rb +79 -0
  39. data/lib/interfacets/server/bus.rb +34 -0
  40. data/lib/interfacets/server/config.rb +87 -0
  41. data/lib/interfacets/server/facets/deserializer.rb +25 -0
  42. data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
  43. data/lib/interfacets/server/facets/serializer.rb +50 -0
  44. data/lib/interfacets/server/registry.rb +51 -0
  45. data/lib/interfacets/shared/basic_routable.rb +45 -0
  46. data/lib/interfacets/shared/entities/bus.rb +230 -0
  47. data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
  48. data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
  49. data/lib/interfacets/shared/entities/specs.rb +161 -0
  50. data/lib/interfacets/shared/entity.rb +102 -0
  51. data/lib/interfacets/shared/entity_dsl.rb +154 -0
  52. data/lib/interfacets/shared/facet.rb +200 -0
  53. data/lib/interfacets/shared/generated_store.rb +149 -0
  54. data/lib/interfacets/shared/utils.rb +54 -0
  55. data/lib/interfacets/shared/validations.rb +75 -0
  56. data/lib/interfacets/shared/view.rb +74 -0
  57. data/lib/interfacets/test/component_registry.rb +63 -0
  58. data/lib/interfacets/test/js/inline_bus.rb +100 -0
  59. data/lib/interfacets/test/js/nodo_bus.rb +98 -0
  60. data/lib/interfacets/test/js/receivers/api.rb +48 -0
  61. data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
  62. data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
  63. data/lib/interfacets/test/js/receivers/react.rb +32 -0
  64. data/lib/interfacets/test/js/receivers/timer.rb +77 -0
  65. data/lib/interfacets/test/js/receivers/url.rb +60 -0
  66. data/lib/interfacets/test/standard_elements.yml +173 -0
  67. data/lib/interfacets/test/ui_simulator.rb +75 -0
  68. data/lib/interfacets/test/validation_engine.rb +151 -0
  69. data/lib/interfacets/test.rb +13 -0
  70. data/lib/interfacets/version.rb +1 -1
  71. data/lib/interfacets.rb +29 -2
  72. metadata +114 -6
  73. data/README.md +0 -35
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ class Entity
6
+ include Shared::Validations
7
+ include EntityDsl
8
+
9
+ class << self
10
+ def inherited(subclass)
11
+ subclass.inherit_attributes(self)
12
+
13
+ subclass.action(:after_load, accepted_by: :client)
14
+
15
+ subclass.accessor(
16
+ :internal_entity_id,
17
+ getter: -> {
18
+ if store.respond_to?(:internal_entity_id)
19
+ store.internal_entity_id
20
+ else
21
+ (@interfacets_id ||= SecureRandom.uuid)
22
+ end
23
+ },
24
+ setter: ->(val) {
25
+ if store.respond_to?(:internal_entity_id=)
26
+ store.internal_entity_id = val
27
+ else
28
+ @interfacets_id = val
29
+ end
30
+ }
31
+ )
32
+ end
33
+ end
34
+
35
+ attr_reader :store, :parent
36
+ def initialize(store:, nesting:, parent:)
37
+ @store = store
38
+ @parent = parent
39
+ @nesting = nesting
40
+ end
41
+
42
+ def record
43
+ store
44
+ end
45
+
46
+ def ==(other)
47
+ if other.is_a?(Entity)
48
+ store == other.store
49
+ else
50
+ store == other
51
+ end
52
+ end
53
+
54
+ def role
55
+ if parent.nil?
56
+ self.class.role
57
+ else
58
+ parent.role
59
+ end
60
+ end
61
+
62
+ def uid
63
+ [self.class.name, internal_entity_id].join("__")
64
+ end
65
+
66
+ def entity_nesting
67
+ @entity_nesting ||= (parent&.entity_nesting || []) + [[@nesting, internal_entity_id]]
68
+ end
69
+
70
+ def entity_at(nesting)
71
+ # ignore first entry, cause it's self
72
+ real_nesting = nesting[1..-1]
73
+
74
+ if real_nesting.count == 0
75
+ self
76
+ else
77
+ assoc, internal_entity_id = real_nesting[0]
78
+
79
+ if association(assoc).collection?
80
+ association(assoc).get.find { _1.internal_entity_id == internal_entity_id }
81
+ else
82
+ value = association(assoc).get
83
+ value.internal_entity_id == internal_entity_id ? value : nil
84
+ end
85
+ end
86
+ end
87
+
88
+ def association(name)
89
+ # inlined to avoid polluting API
90
+ @association_handlers ||= (
91
+ self
92
+ .class
93
+ .associations
94
+ .map { |name, spec| [name, spec.handler(self) ] }
95
+ .to_h
96
+ )
97
+
98
+ @association_handlers.fetch(name.to_s)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module EntityDsl
6
+ extend ActiveSupport::Concern
7
+
8
+ included do |base|
9
+ if base.respond_to?(:inherit_attributes)
10
+ base.inherit_attributes(self)
11
+ end
12
+ end
13
+
14
+ class_methods do
15
+ attr_accessor :manifest
16
+
17
+ def role(name = nil)
18
+ if name
19
+ @role = name.to_s
20
+ else
21
+ @role
22
+ end
23
+ end
24
+
25
+ def inheritable_attributes
26
+ {
27
+ accessors:,
28
+ actions:,
29
+ mergers:,
30
+ role: @role,
31
+ associations:,
32
+ validators:
33
+ }
34
+ end
35
+
36
+ def inherit_attributes(parent)
37
+ return unless parent.respond_to?(:inheritable_attributes)
38
+ attrs = parent.inheritable_attributes
39
+ @accessors = attrs[:accessors].dup.merge(accessors)
40
+ @actions = attrs[:actions].dup.merge(actions)
41
+ @mergers = attrs[:mergers].dup.merge(mergers)
42
+ @role ||= attrs[:role]
43
+
44
+ attrs[:associations].each do |name, spec|
45
+ associations[name] ||= spec.dup_for(self)
46
+ end
47
+
48
+ @validators = (attrs[:validators].dup + validators).uniq
49
+ end
50
+
51
+ def merge(name, *events, &block)
52
+ spec = Entities::Specs::Merger.new(
53
+ name:,
54
+ block:
55
+ )
56
+
57
+ target = (mergers[name.to_s] ||= {})
58
+
59
+ if events.empty?
60
+ target[:default] = spec
61
+ else
62
+ events.each do |event|
63
+ target[event.to_s] = spec
64
+ end
65
+ end
66
+ end
67
+
68
+ def accessor(
69
+ name,
70
+ getter: -> { store.send(name) },
71
+ setter: ->(val) { store.send("#{name}=", val) },
72
+ accepted_by: Entities::Specs::ANY
73
+ )
74
+ name = name.to_s
75
+ accessors[name] = Entities::Specs::Accessor.new(name:, accepted_by:)
76
+
77
+ define_method(name, &getter)
78
+ define_method("#{name}=", &setter)
79
+ end
80
+
81
+ def association(name, mod = nil, **spec_params, &block)
82
+ name = name.to_s
83
+ associations[name] ||= Entities::Specs::Association.new(parent: self, name: name)
84
+ associations[name].apply(mod:, **spec_params, &block)
85
+
86
+ define_method(name) do
87
+ association(name).get
88
+ end
89
+
90
+ define_method("#{name}=") do |val|
91
+ association(name).set(val)
92
+ end
93
+ end
94
+
95
+ def reference(*a, **p, &b)
96
+ association(*a, **p, type: :reference, &b)
97
+ end
98
+
99
+ def collection(*a, **p, &b)
100
+ association(*a, **p, type: :collection, &b)
101
+ end
102
+
103
+ def server_action(name, only_if_valid: true)
104
+ action(name, accepted_by: :server, only_if_valid:)
105
+ action("after_#{name}", accepted_by: :client)
106
+
107
+ define_method(name) do
108
+ store.send(name, entity: self)
109
+ end
110
+ end
111
+
112
+ def action(name, accepted_by: Entities::Specs::ANY, only_if_valid: false)
113
+ name = name.to_s
114
+ actions[name] = Entities::Specs::Action.new(name:, accepted_by:, only_if_valid:)
115
+
116
+ define_method(name) {}
117
+ end
118
+
119
+ def accessors
120
+ @accessors ||= {}
121
+ end
122
+
123
+ def associations
124
+ @associations ||= {}
125
+ end
126
+
127
+ def actions
128
+ @actions ||= {}
129
+ end
130
+
131
+ def mergers
132
+ @mergers ||= Hash.new { |h, k|
133
+ h[k] = {
134
+ default: Entities::Specs::Merger.new(
135
+ name: k,
136
+ block: ->(entity, value) {
137
+ entity.send("#{k}=", value)
138
+ }
139
+ )
140
+ }
141
+ }
142
+ end
143
+
144
+ def attributes
145
+ accessors.merge(associations)
146
+ end
147
+
148
+ def validators
149
+ @validators ||= []
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Facet
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ attr_accessor :shared, :entity, :store
10
+
11
+ def mount(facet, as:, type: nil, &block)
12
+ if block_given?
13
+ parent_facet = facet
14
+ facet = Module.new do
15
+ include Interfacets::Shared::Facet
16
+
17
+ server_entity do
18
+ include parent_facet.server_entity_module
19
+ end
20
+
21
+ entity_base do
22
+ include parent_facet.entity_base_module
23
+ end
24
+
25
+ client_entity do
26
+ include parent_facet.client_entity_module
27
+ end
28
+
29
+ view_class.view(&parent_facet.view_class.view) if parent_facet.view_class.view
30
+
31
+ class_exec(&block)
32
+ end
33
+ end
34
+
35
+ server_entity do
36
+ association(as, facet.server_entity_module, type:)
37
+ end
38
+
39
+ client_entity do
40
+ association(as, facet.client_entity_module, type:)
41
+ end
42
+
43
+ entity_base do
44
+ association(as, facet.entity_base_module, type:)
45
+ end
46
+ end
47
+
48
+ def view(&block)
49
+ view_class.view(&block)
50
+ end
51
+
52
+ def view_class
53
+ @view_class ||= Class.new(Interfacets::Shared::View)
54
+ end
55
+
56
+ def client_entity(&block)
57
+ clients << block
58
+ end
59
+
60
+ def clients
61
+ @clients ||= []
62
+ end
63
+
64
+ def client_entity_module
65
+ @client_entity_module ||= (
66
+ facet = self
67
+ Module.new do
68
+ extend ActiveSupport::Concern
69
+ include facet.entity_base_module
70
+
71
+ included do
72
+ define_singleton_method(:view) do
73
+ facet.view_class
74
+ end
75
+
76
+ role("client")
77
+ facet.clients.each { class_exec(&_1) }
78
+ end
79
+ end
80
+ )
81
+ end
82
+
83
+ def client_entity_class
84
+ @client_entity_class ||= (
85
+ facet = self
86
+ Class
87
+ .new(entity_base_class)
88
+ .tap do |k|
89
+ k.class_exec do
90
+ include facet.client_entity_module
91
+
92
+ define_singleton_method(:store) do
93
+ @store ||= (
94
+ ::Interfacets::Shared::GeneratedStore.construct(self)
95
+ .tap { self.const_set("Store", _1) }
96
+ )
97
+ end
98
+
99
+ self.manifest = facet.entity_base_class
100
+
101
+ def channel(name)
102
+ Interfacets::Client::System.current_bus.channel(name).builder
103
+ end
104
+ end
105
+ end
106
+ )
107
+ end
108
+
109
+ def entity_base(&block)
110
+ bases << block
111
+ end
112
+
113
+ def bases
114
+ @bases ||= []
115
+ end
116
+
117
+ def entity_base_module
118
+ @entity_base_module ||= (
119
+ facet = self
120
+ Module.new do
121
+ extend ActiveSupport::Concern
122
+ include Interfacets::Shared::EntityDsl
123
+ included do
124
+ facet.bases.each { class_exec(&_1) }
125
+ end
126
+ end
127
+ )
128
+ end
129
+
130
+ def entity_base_class
131
+ @entity_base_class ||= (
132
+ facet = self
133
+
134
+ Class.new(Shared::Entity)
135
+ .tap { |k| k.include(facet.entity_base_module) }
136
+ .tap { facet.const_set("EntityBase", _1) }
137
+ )
138
+ end
139
+
140
+ def server_entity_module
141
+ @server_entity_module ||= (
142
+ facet = self
143
+ Module.new do
144
+ extend ActiveSupport::Concern
145
+ include facet.entity_base_module
146
+ included do
147
+ role("server")
148
+ facet.servers.each { class_exec(&_1) }
149
+ end
150
+ end
151
+ )
152
+ end
153
+
154
+ def server_entity_class
155
+ @server_entity_class ||= (
156
+ facet = self
157
+
158
+ Class.new(entity_base_class) {
159
+ include facet.server_entity_module
160
+
161
+ attr_reader :channel
162
+ def initialize(*a, channel:, **p, &b)
163
+ @channel = channel
164
+ super(*a, **p, &b)
165
+ end
166
+
167
+ def build_entity(...)
168
+ channel.build(...)
169
+ end
170
+
171
+ self.manifest = facet.entity_base_class
172
+ }
173
+ ).tap { facet.const_set("ServerBase", _1) }
174
+ end
175
+
176
+ def server_entity(unshift: false, &block)
177
+ if unshift
178
+ servers.unshift(block)
179
+ else
180
+ servers << block
181
+ end
182
+ end
183
+
184
+ def servers
185
+ @servers ||= []
186
+ end
187
+ end
188
+
189
+ attr_reader :entity, :view
190
+ def initialize(entity)
191
+ @entity = entity
192
+ @view = self.class.view.new if self.class.view.is_a?(Class)
193
+ end
194
+
195
+ def render(channels)
196
+ view.render(entity:, channels:)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ class GeneratedStore
6
+ class Constructor
7
+ def self.call(entity_class)
8
+ klass = Class.new(GeneratedStore) do
9
+ define_singleton_method(:name) { "#{entity_class.name}.GeneratedStore" }
10
+ end
11
+
12
+ entity_class.accessors.each do |name, spec|
13
+ klass.accessor(name)
14
+ end
15
+
16
+ entity_class.associations.each do |name, spec|
17
+ sub_class = call(spec.klass)
18
+
19
+ if spec.type == :reference
20
+ klass.association(name, klass: sub_class)
21
+ else
22
+ klass.collection(name, klass: sub_class)
23
+ end
24
+ end
25
+
26
+ entity_class.actions.each do |name, spec|
27
+ klass.define_method(name) do |entity:|
28
+ Client::System
29
+ .current_bus
30
+ .channel("interfacets:api")
31
+ .builder
32
+ .submit(name, nesting: entity.entity_nesting)
33
+ end
34
+ end
35
+
36
+ klass
37
+ end
38
+ end
39
+
40
+ class Handler
41
+ attr_reader :store, :spec
42
+ def initialize(store:, spec:)
43
+ @store = store
44
+ @spec = spec
45
+ end
46
+
47
+ def build
48
+ if spec.type == :reference
49
+ value = spec.klass.new
50
+ store.send("#{spec.name}=", value)
51
+ value
52
+ else
53
+ value = spec.klass.new
54
+ items = store.send(spec.name)
55
+ if items.nil?
56
+ store.send("#{spec.name}=", [value])
57
+ else
58
+ items << value
59
+ end
60
+
61
+ value
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ class << self
68
+ def construct(...)
69
+ Constructor.call(...)
70
+ end
71
+
72
+ class Spec
73
+ attr_reader(:name, :klass, :type)
74
+ def initialize(name:, klass:, type:)
75
+ @name = name
76
+ @klass = klass
77
+ @type = type
78
+ end
79
+
80
+ def build
81
+ spec.klass.new
82
+ end
83
+ end
84
+
85
+ def accessor(name)
86
+ name = name.to_s
87
+
88
+ define_method(name) do
89
+ @attributes[name]
90
+ end
91
+
92
+ define_method("#{name}=") do |val|
93
+ @attributes[name] = val
94
+ end
95
+ end
96
+
97
+ def association(name, klass:, type: :reference)
98
+ name = name.to_s
99
+
100
+ associations[name] = Spec.new(
101
+ name:,
102
+ klass:,
103
+ type:,
104
+ )
105
+
106
+ define_method(name) do
107
+ @attributes[name]
108
+ end
109
+
110
+ define_method("#{name}=") do |val|
111
+ @attributes[name] = val
112
+ end
113
+ end
114
+
115
+ def collection(*args, **params)
116
+ association(*args, **params, type: :collection)
117
+ end
118
+
119
+ def associations
120
+ @associations ||= {}
121
+ end
122
+ end
123
+
124
+ attr_accessor :attributes
125
+ def initialize
126
+ @attributes = {
127
+ "internal_entity_id" => SecureRandom.uuid
128
+ }
129
+ end
130
+
131
+ def ==(other)
132
+ attributes == other.attributes
133
+ end
134
+
135
+ def association(name)
136
+ @association_handlers ||= (
137
+ self
138
+ .class
139
+ .associations
140
+ .map { |name, spec| [name, Handler.new(store: self, spec:)] }
141
+ .to_h
142
+ )
143
+
144
+ @association_handlers.fetch(name)
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Utils
6
+ extend self
7
+
8
+ def permissive_exec(receiver, *, &lambda)
9
+ if lambda.arity.zero?
10
+ receiver.instance_exec(&lambda)
11
+ else
12
+ receiver.instance_exec(*, &lambda)
13
+ end
14
+ end
15
+
16
+ def permissive_call(lambda, ...)
17
+ if lambda.arity.zero?
18
+ lambda.call
19
+ else
20
+ lambda.call(...)
21
+ end
22
+ end
23
+
24
+ def facet_id_to_path(config:, facet:, id:)
25
+ [config.mount_point, facet, id].join("/")
26
+ end
27
+
28
+ def path_to_facet_id(config:, path:)
29
+ parts = path.sub(/.*#{config.mount_point}/, "").split("/")
30
+ id = parts.pop
31
+ facet = parts.join("/")
32
+
33
+ [facet, id]
34
+ end
35
+
36
+ # ActiveSupport?
37
+ def blank?(str)
38
+ return true if str.nil?
39
+ return str.empty? if str.is_a?(Array)
40
+ return str.match(/\A\s*\z/) if str.is_a?(String)
41
+
42
+ raise("unsupported blank check")
43
+ end
44
+
45
+ def present?(str)
46
+ !blank?(str)
47
+ end
48
+
49
+ def presence(obj)
50
+ present?(obj) ? obj : nil
51
+ end
52
+ end
53
+ end
54
+ end