interfacets 0.9.9 → 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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.tmp +5 -0
  3. data/Rakefile +4 -0
  4. data/lib/interfacets/client/assets.rb +1 -0
  5. data/lib/interfacets/client/bus.rb +5 -1
  6. data/lib/interfacets/client/channels/api.rb +10 -3
  7. data/lib/interfacets/client/registry.rb +9 -70
  8. data/lib/interfacets/client/utils/mruby_patches.rb +1 -1
  9. data/lib/interfacets/client/utils/open_struct.rb +90 -77
  10. data/lib/interfacets/component_registry.rb +115 -0
  11. data/lib/interfacets/component_schema_parser.rb +84 -0
  12. data/lib/interfacets/server/api.rb +5 -25
  13. data/lib/interfacets/server/assets/facet.rb +5 -3
  14. data/lib/interfacets/server/assets.rb +13 -7
  15. data/lib/interfacets/server/basic_router.rb +15 -10
  16. data/lib/interfacets/server/bus.rb +4 -9
  17. data/lib/interfacets/server/config.rb +1 -1
  18. data/lib/interfacets/server/registry.rb +23 -184
  19. data/lib/interfacets/shared/basic_routable.rb +45 -0
  20. data/lib/interfacets/shared/entities/bus.rb +41 -29
  21. data/lib/interfacets/shared/entities/specs/handlers.rb +16 -0
  22. data/lib/interfacets/shared/entities/specs.rb +46 -9
  23. data/lib/interfacets/shared/entity.rb +23 -99
  24. data/lib/interfacets/shared/entity_dsl.rb +154 -0
  25. data/lib/interfacets/shared/facet.rb +200 -0
  26. data/lib/interfacets/shared/generated_store.rb +6 -2
  27. data/lib/interfacets/shared/utils.rb +1 -1
  28. data/lib/interfacets/shared/validations.rb +5 -1
  29. data/lib/interfacets/{client → shared}/view.rb +33 -6
  30. data/lib/interfacets/test/component_registry.rb +63 -0
  31. data/lib/interfacets/test/js/inline_bus.rb +21 -12
  32. data/lib/interfacets/test/js/nodo_bus.rb +18 -1
  33. data/lib/interfacets/test/js/receivers/api.rb +14 -3
  34. data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
  35. data/lib/interfacets/test/js/receivers/react/node.rb +29 -63
  36. data/lib/interfacets/test/js/receivers/react.rb +4 -3
  37. data/lib/interfacets/test/js/receivers/timer.rb +77 -0
  38. data/lib/interfacets/test/js/receivers/url.rb +5 -0
  39. data/lib/interfacets/test/standard_elements.yml +173 -0
  40. data/lib/interfacets/test/{browser.rb → ui_simulator.rb} +18 -6
  41. data/lib/interfacets/test/validation_engine.rb +151 -0
  42. data/lib/interfacets/test.rb +0 -4
  43. data/lib/interfacets/version.rb +1 -1
  44. data/lib/interfacets.rb +3 -0
  45. metadata +29 -18
  46. data/lib/interfacets/client/facet.rb +0 -26
  47. data/lib/interfacets/client/facet2.rb +0 -15
  48. data/lib/interfacets/client/facets/attributes/accessor.rb +0 -28
  49. data/lib/interfacets/client/facets/attributes/association.rb +0 -50
  50. data/lib/interfacets/client/facets/attributes/bind.rb +0 -25
  51. data/lib/interfacets/client/facets/attributes/collection.rb +0 -47
  52. data/lib/interfacets/client/facets/attributes/readonly.rb +0 -19
  53. data/lib/interfacets/client/facets/deserializer.rb +0 -30
  54. data/lib/interfacets/client/facets/schema/deserializer.rb +0 -63
  55. data/lib/interfacets/client/facets/schema.rb +0 -63
  56. data/lib/interfacets/client/facets/serializer.rb +0 -18
  57. data/lib/interfacets/server/basic_routable.rb +0 -40
  58. data/lib/interfacets/server/facet.rb +0 -51
  59. data/lib/interfacets/shared/entity_collection.rb +0 -88
@@ -4,109 +4,15 @@ module Interfacets
4
4
  module Shared
5
5
  class Entity
6
6
  include Shared::Validations
7
+ include EntityDsl
7
8
 
8
9
  class << self
9
- attr_accessor :manifest
10
+ def inherited(subclass)
11
+ subclass.inherit_attributes(self)
10
12
 
11
- def role(name = nil)
12
- @role = name.to_s if name
13
- @role
14
- end
15
-
16
- def accessor(
17
- name,
18
- getter: -> { store.send(name) },
19
- setter: ->(val) { store.send("#{name}=", val) },
20
- accepted_by: Entities::Specs::ANY
21
- )
22
- name = name.to_s
23
- accessors[name] = Entities::Specs::Accessor.new(name:, accepted_by:)
24
-
25
- define_method(name, &getter)
26
- define_method("#{name}=", &setter)
27
- end
28
-
29
- def association(*args, type: :reference, **params, &block)
30
- if type == :reference
31
- reference(*args, **params, &block)
32
- elsif type == :collection
33
- collection(*args, **params, &block)
34
- else
35
- raise ArgumentError
36
- end
37
- end
38
-
39
- def reference(
40
- name,
41
- **spec_params,
42
- &block
43
- )
44
- name = name.to_s
45
- spec = (associations[name] ||= Entities::Specs::Reference.new(parent: self, name: name))
46
- spec.apply(**spec_params, &block)
47
-
48
- define_method(name) do
49
- association(name).get
50
- end
51
-
52
- define_method("#{name}=") do |val|
53
- association(name).set(val)
54
- end
55
- end
56
-
57
- def collection(
58
- name,
59
- **spec_params,
60
- &block
61
- )
62
- name = name.to_s
63
-
64
- spec = (associations[name] ||= Entities::Specs::Collection.new(parent: self, name: name))
65
- spec.apply(**spec_params, &block)
66
-
67
- define_method(name) do
68
- association(name).get
69
- end
70
-
71
- define_method("#{name}=") do |val|
72
- association(name).set(val)
73
- end
74
- end
13
+ subclass.action(:after_load, accepted_by: :client)
75
14
 
76
- def server_action(name, only_if_valid: true)
77
- action(name, accepted_by: :server, only_if_valid:)
78
- action("after_#{name}", accepted_by: :client)
79
- end
80
-
81
- def action(name, accepted_by: Entities::Specs::ANY, only_if_valid: false)
82
- name = name.to_s
83
- actions[name] = Entities::Specs::Action.new(name:, accepted_by:, only_if_valid:)
84
-
85
- define_method(name) do
86
- store.send(name)
87
- end
88
- end
89
-
90
- def accessors
91
- @accessors ||= {}
92
- end
93
-
94
- def associations
95
- @associations ||= {}
96
- end
97
-
98
- def actions
99
- @actions ||= {}
100
- end
101
-
102
- def attributes
103
- accessors.merge(associations)
104
- end
105
-
106
- def inherited(mod)
107
- mod.action(:after_load, accepted_by: :client)
108
-
109
- mod.accessor(
15
+ subclass.accessor(
110
16
  :internal_entity_id,
111
17
  getter: -> {
112
18
  if store.respond_to?(:internal_entity_id)
@@ -161,6 +67,24 @@ module Interfacets
161
67
  @entity_nesting ||= (parent&.entity_nesting || []) + [[@nesting, internal_entity_id]]
162
68
  end
163
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
+
164
88
  def association(name)
165
89
  # inlined to avoid polluting API
166
90
  @association_handlers ||= (
@@ -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
@@ -24,8 +24,12 @@ module Interfacets
24
24
  end
25
25
 
26
26
  entity_class.actions.each do |name, spec|
27
- klass.define_method(name) do
28
- Client::System.current_bus.channel("interfacets:api").builder.submit(name)
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)
29
33
  end
30
34
  end
31
35
 
@@ -37,7 +37,7 @@ module Interfacets
37
37
  def blank?(str)
38
38
  return true if str.nil?
39
39
  return str.empty? if str.is_a?(Array)
40
- return str.match(/^\s*$/) if str.is_a?(String)
40
+ return str.match(/\A\s*\z/) if str.is_a?(String)
41
41
 
42
42
  raise("unsupported blank check")
43
43
  end
@@ -13,7 +13,11 @@ module Interfacets
13
13
  end
14
14
 
15
15
  def add(k, v)
16
- @errors[k] << v
16
+ @errors[k.to_sym] << v
17
+ end
18
+
19
+ def clear
20
+ @errors.clear
17
21
  end
18
22
 
19
23
  def [](k)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Interfacets
4
- module Client
4
+ module Shared
5
5
  class View
6
6
  class Evaluator
7
- def self.call(...)
8
- new(...).call
7
+ def self.call(**p)
8
+ new(**p).call
9
9
  end
10
10
 
11
11
  attr_reader :entity, :channels, :block
@@ -20,10 +20,23 @@ module Interfacets
20
20
  instance_exec(entity, &@block)
21
21
  end
22
22
 
23
- def render(channel_id, stream: "default", &block)
23
+ def render(entity)
24
+ entity
25
+ .class
26
+ .view
27
+ .render(
28
+ entity:,
29
+ channels:
30
+ )
31
+ end
32
+
33
+ def render_to(channel_id, stream: "default", &block)
34
+ channel_id = channel_id.to_s
35
+
24
36
  channels
25
- .fetch(channel_id.to_s)
37
+ .fetch(channel_id)
26
38
  .builder(stream.to_s)
39
+ .then { @current_builder = _1 }
27
40
  .then { instance_exec(_1, &block) }
28
41
  end
29
42
 
@@ -37,10 +50,24 @@ module Interfacets
37
50
  @view = block if block_given?
38
51
  @view
39
52
  end
53
+
54
+ def view=(val)
55
+ @view = val
56
+ end
57
+
58
+ def render(**p)
59
+ new.render(**p)
60
+ end
40
61
  end
41
62
 
42
63
  def render(entity:, channels:)
43
- Evaluator.call(entity: entity, channels:, block: self.class.view)
64
+ return unless self.class.view
65
+
66
+ Evaluator.call(
67
+ entity: entity,
68
+ channels: channels,
69
+ block: self.class.view
70
+ )
44
71
  end
45
72
  end
46
73
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Interfacets
6
+ module Test
7
+ class ComponentRegistry
8
+ DEFAULT_CONFIG_PATH = "config/interfacets/components.yml"
9
+
10
+ def self.load(path = DEFAULT_CONFIG_PATH)
11
+ new(path)
12
+ end
13
+
14
+ attr_reader :contracts
15
+
16
+ def initialize(path)
17
+ @path = path
18
+ @schema_cache = {}
19
+ @contracts = load_contracts
20
+ end
21
+
22
+ def find_component(name)
23
+ @contracts[name.to_s]
24
+ end
25
+
26
+ def definitions
27
+ @contracts["definitions"] || {}
28
+ end
29
+
30
+ private
31
+
32
+ def load_contracts
33
+ return {} unless File.exist?(@path)
34
+
35
+ contracts = YAML.load_file(@path, aliases: true) || {}
36
+ contracts.each do |name, config|
37
+ next unless config.is_a?(Hash)
38
+
39
+ schema = config["schema"]
40
+ if schema.is_a?(String)
41
+ config["schema"] = load_schema_file(name, schema)
42
+ end
43
+ end
44
+ contracts
45
+ rescue Psych::SyntaxError => e
46
+ raise Error, "Failed to parse component registry at #{@path}: #{e.message}"
47
+ end
48
+
49
+ def load_schema_file(component_name, schema_path)
50
+ full_path = File.expand_path(schema_path, File.dirname(@path))
51
+ return @schema_cache[full_path] if @schema_cache.key?(full_path)
52
+
53
+ raise Errno::ENOENT, "Schema file not found for component '#{component_name}' at #{full_path}" unless File.exist?(full_path)
54
+
55
+ begin
56
+ @schema_cache[full_path] = YAML.load_file(full_path, aliases: true) || {}
57
+ rescue Psych::SyntaxError => e
58
+ raise Error, "Failed to parse schema file for component '#{component_name}' at #{full_path}: #{e.message}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end