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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +173 -1
  3. data/LICENSE +21 -0
  4. data/Rakefile +4 -6
  5. data/lib/interfacets/client/actor.rb +20 -0
  6. data/lib/interfacets/client/assets.rb +209 -0
  7. data/lib/interfacets/client/bus.rb +69 -0
  8. data/lib/interfacets/client/channels/api.rb +95 -0
  9. data/lib/interfacets/client/channels/audio.rb +101 -0
  10. data/lib/interfacets/client/channels/base.rb +28 -0
  11. data/lib/interfacets/client/channels/page_visibility.rb +21 -0
  12. data/lib/interfacets/client/channels/react/builder.rb +203 -0
  13. data/lib/interfacets/client/channels/react/channel.rb +61 -0
  14. data/lib/interfacets/client/channels/react/dom.rb +91 -0
  15. data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
  16. data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
  17. data/lib/interfacets/client/channels/timer.rb +51 -0
  18. data/lib/interfacets/client/channels/url.rb +52 -0
  19. data/lib/interfacets/client/config.rb +22 -0
  20. data/lib/interfacets/client/delegator.rb +37 -0
  21. data/lib/interfacets/client/facet.rb +26 -0
  22. data/lib/interfacets/client/facet2.rb +15 -0
  23. data/lib/interfacets/client/facets/attributes/accessor.rb +28 -0
  24. data/lib/interfacets/client/facets/attributes/association.rb +50 -0
  25. data/lib/interfacets/client/facets/attributes/bind.rb +25 -0
  26. data/lib/interfacets/client/facets/attributes/collection.rb +47 -0
  27. data/lib/interfacets/client/facets/attributes/readonly.rb +19 -0
  28. data/lib/interfacets/client/facets/deserializer.rb +30 -0
  29. data/lib/interfacets/client/facets/schema/deserializer.rb +63 -0
  30. data/lib/interfacets/client/facets/schema.rb +63 -0
  31. data/lib/interfacets/client/facets/serializer.rb +18 -0
  32. data/lib/interfacets/client/registry.rb +84 -0
  33. data/lib/interfacets/client/system.rb +88 -0
  34. data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
  35. data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
  36. data/lib/interfacets/client/utils/open_struct.rb +102 -0
  37. data/lib/interfacets/client/utils/securerandom.rb +69 -0
  38. data/lib/interfacets/client/view.rb +47 -0
  39. data/lib/interfacets/client.rb +13 -0
  40. data/lib/interfacets/mruby/build.dockerfile +66 -0
  41. data/lib/interfacets/mruby/build_config.rb +20 -0
  42. data/lib/interfacets/mruby/entrypoint.rb +23 -0
  43. data/lib/interfacets/mruby/init.c +66 -0
  44. data/lib/interfacets/server/api.rb +64 -0
  45. data/lib/interfacets/server/assets/facet.rb +61 -0
  46. data/lib/interfacets/server/assets.rb +210 -0
  47. data/lib/interfacets/server/basic_routable.rb +40 -0
  48. data/lib/interfacets/server/basic_router.rb +74 -0
  49. data/lib/interfacets/server/bus.rb +39 -0
  50. data/lib/interfacets/server/config.rb +87 -0
  51. data/lib/interfacets/server/facet.rb +51 -0
  52. data/lib/interfacets/server/facets/deserializer.rb +25 -0
  53. data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
  54. data/lib/interfacets/server/facets/serializer.rb +50 -0
  55. data/lib/interfacets/server/registry.rb +212 -0
  56. data/lib/interfacets/shared/entities/bus.rb +218 -0
  57. data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
  58. data/lib/interfacets/shared/entities/specs/handlers.rb +117 -0
  59. data/lib/interfacets/shared/entities/specs.rb +124 -0
  60. data/lib/interfacets/shared/entity.rb +178 -0
  61. data/lib/interfacets/shared/entity_collection.rb +88 -0
  62. data/lib/interfacets/shared/generated_store.rb +145 -0
  63. data/lib/interfacets/shared/utils.rb +54 -0
  64. data/lib/interfacets/shared/validations.rb +71 -0
  65. data/lib/interfacets/test/browser.rb +63 -0
  66. data/lib/interfacets/test/js/inline_bus.rb +91 -0
  67. data/lib/interfacets/test/js/nodo_bus.rb +81 -0
  68. data/lib/interfacets/test/js/receivers/api.rb +37 -0
  69. data/lib/interfacets/test/js/receivers/react/node.rb +167 -0
  70. data/lib/interfacets/test/js/receivers/react.rb +31 -0
  71. data/lib/interfacets/test/js/receivers/url.rb +55 -0
  72. data/lib/interfacets/test.rb +17 -0
  73. data/lib/interfacets/version.rb +1 -1
  74. data/lib/interfacets.rb +26 -2
  75. metadata +103 -6
  76. data/README.md +0 -35
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ module Specs
7
+ module Handlers
8
+ class Handler
9
+ attr_reader :entity, :spec, :cache
10
+ def initialize(entity:, spec:)
11
+ @entity = entity
12
+ @spec = spec
13
+ @cache = {}
14
+ end
15
+
16
+ def type
17
+ spec.type
18
+ end
19
+
20
+ def unwrap(value)
21
+ if value.is_a?(Entity)
22
+ value.store
23
+ elsif value.is_a?(Array)
24
+ value.map { unwrap(_1) }
25
+ else
26
+ value
27
+ end
28
+ end
29
+
30
+ def wrap(value)
31
+ if value.is_a?(Entity)
32
+ value
33
+ elsif cache[value]
34
+ cache[value]
35
+ elsif value
36
+ @cache[value] = spec.klass.new(
37
+ store: value,
38
+ nesting: spec.name,
39
+ parent: entity,
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ class Reference < Handler
46
+ def get
47
+ wrap(entity.instance_exec(&spec.getter))
48
+ .tap {
49
+ @cache = {}
50
+ @cache[_1&.store] = _1
51
+ }
52
+ end
53
+
54
+ def set(val)
55
+ entity.instance_exec(unwrap(val), &spec.setter)
56
+ wrap(val)
57
+ end
58
+
59
+ def build(**attributes)
60
+ value = entity.instance_exec(&spec.builder)
61
+ attributes.each do |name, val|
62
+ value.send("#{name}=", val)
63
+ end
64
+
65
+ # TODO: make sure this is right
66
+ # set(value)
67
+
68
+ wrap(value)
69
+ end
70
+ end
71
+
72
+ class Collection < Handler
73
+ def get
74
+ values = entity.instance_exec(&spec.getter) || []
75
+
76
+ CollectionProxy.new(
77
+ values,
78
+ wrap: ->(val) { wrap(val) },
79
+ unwrap: ->(val) { unwrap(val) },
80
+ ).tap { |entities|
81
+ @cache = entities.map { [_1.store, _1] }.to_h
82
+ }
83
+ end
84
+
85
+ def set(val)
86
+ @cache ||= {}
87
+ val.each do |item|
88
+ if item.is_a?(Entity)
89
+ @cache[item.store] = item
90
+ end
91
+ end
92
+
93
+ entity.instance_exec(unwrap(val), &spec.setter)
94
+ end
95
+
96
+ def build(**attributes)
97
+ value = unwrap(entity.instance_exec(&spec.builder))
98
+ attributes.each do |name, val|
99
+ value.send("#{name}=", val)
100
+ end
101
+
102
+ items = entity.instance_exec(&spec.getter)
103
+
104
+ if items.nil?
105
+ set([value])
106
+ elsif !items.include?(value)
107
+ items << value
108
+ end
109
+
110
+ wrap(value)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ module Specs
7
+
8
+ # I would like to use Object.new for object equality here, however,
9
+ # in the intergration tests, our shared code gets evaled on the frontend
10
+ # and again on the backend (for now). For that reason, we cannot use
11
+ # object equality because the constant will be overridden.
12
+ NOT_PASSED = :interfacets__not_passed_flag
13
+ ANY = :interfacets__any_flag
14
+
15
+ module Acceptable
16
+ def accepted_by?(role)
17
+ standardized_accepted_by == ANY || standardized_accepted_by.include?(role)
18
+ end
19
+
20
+ def standardized_accepted_by
21
+ @standardized_accepted_by ||= (
22
+ if accepted_by == ANY
23
+ ANY
24
+ else
25
+ Array(accepted_by).map(&:to_s)
26
+ end
27
+ )
28
+ end
29
+ end
30
+
31
+ class Action
32
+ include Acceptable
33
+
34
+ attr_reader :name, :accepted_by, :only_if_valid
35
+ def initialize(name:, accepted_by:, only_if_valid:)
36
+ @name = name
37
+ @accepted_by = accepted_by
38
+ @only_if_valid = only_if_valid
39
+ end
40
+
41
+
42
+ def type = :action
43
+
44
+ def dispatch(entity)
45
+ if !only_if_valid || entity.valid?
46
+ entity.send(name)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Accessor
52
+ include Acceptable
53
+
54
+ attr_reader :name, :accepted_by
55
+ def initialize(name:, accepted_by:)
56
+ @name = name
57
+ @accepted_by = accepted_by
58
+ end
59
+
60
+ def type = :accessor
61
+ end
62
+
63
+ class Association
64
+ include Acceptable
65
+
66
+ attr_reader(
67
+ :name,
68
+ :accepted_by,
69
+ :klass,
70
+ :getter,
71
+ :setter,
72
+ :builder,
73
+ :parent,
74
+ :identifier,
75
+ )
76
+ def initialize(parent:, name:)
77
+ @parent = parent
78
+ @name = name
79
+ @accepted_by = Entities::Specs::ANY
80
+ @getter = -> { store.send(name) }
81
+ @setter = ->(val) { store.send("#{name}=", val) }
82
+ @builder = ->() { store.association(name).build }
83
+ @identifier = "id"
84
+ @klass = Class.new(Entity) do
85
+ define_singleton_method(:name) { "#{parent.name}.#{name}" }
86
+ end
87
+ end
88
+
89
+ def apply(
90
+ accepted_by: NOT_PASSED,
91
+ getter: NOT_PASSED,
92
+ setter: NOT_PASSED,
93
+ builder: NOT_PASSED,
94
+ identifier: NOT_PASSED,
95
+ &block
96
+ )
97
+ @accepted_by = Array(accepted_by).map(&:to_s) unless accepted_by == NOT_PASSED
98
+ @getter = getter unless getter == NOT_PASSED
99
+ @setter = setter unless setter == NOT_PASSED
100
+ @builder = builder unless builder == NOT_PASSED
101
+ @identifier = identifier.to_s unless identifier == NOT_PASSED
102
+ klass.class_exec(&block) if block_given?
103
+ end
104
+ end
105
+
106
+ class Reference < Association
107
+ def type = :reference
108
+
109
+ def handler(entity)
110
+ Handlers::Reference.new(entity:, spec: self)
111
+ end
112
+ end
113
+
114
+ class Collection < Association
115
+ def type = :collection
116
+
117
+ def handler(entity)
118
+ Handlers::Collection.new(entity:, spec: self)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ class Entity
6
+ include Shared::Validations
7
+
8
+ class << self
9
+ attr_accessor :manifest
10
+
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
75
+
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(
110
+ :internal_entity_id,
111
+ getter: -> {
112
+ if store.respond_to?(:internal_entity_id)
113
+ store.internal_entity_id
114
+ else
115
+ (@interfacets_id ||= SecureRandom.uuid)
116
+ end
117
+ },
118
+ setter: ->(val) {
119
+ if store.respond_to?(:internal_entity_id=)
120
+ store.internal_entity_id = val
121
+ else
122
+ @interfacets_id = val
123
+ end
124
+ }
125
+ )
126
+ end
127
+ end
128
+
129
+ attr_reader :store, :parent
130
+ def initialize(store:, nesting:, parent:)
131
+ @store = store
132
+ @parent = parent
133
+ @nesting = nesting
134
+ end
135
+
136
+ def record
137
+ store
138
+ end
139
+
140
+ def ==(other)
141
+ if other.is_a?(Entity)
142
+ store == other.store
143
+ else
144
+ store == other
145
+ end
146
+ end
147
+
148
+ def role
149
+ if parent.nil?
150
+ self.class.role
151
+ else
152
+ parent.role
153
+ end
154
+ end
155
+
156
+ def uid
157
+ [self.class.name, internal_entity_id].join("__")
158
+ end
159
+
160
+ def entity_nesting
161
+ @entity_nesting ||= (parent&.entity_nesting || []) + [[@nesting, internal_entity_id]]
162
+ end
163
+
164
+ def association(name)
165
+ # inlined to avoid polluting API
166
+ @association_handlers ||= (
167
+ self
168
+ .class
169
+ .associations
170
+ .map { |name, spec| [name, spec.handler(self) ] }
171
+ .to_h
172
+ )
173
+
174
+ @association_handlers.fetch(name.to_s)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ class EntityCollection
6
+ include Enumerable
7
+
8
+ attr_reader :items
9
+ def initialize(items, &block)
10
+ @items = items || []
11
+ @wrap = block
12
+ end
13
+
14
+ def each(&)
15
+ @items.each do |item|
16
+ yield(@wrap.call(item))
17
+ end
18
+ end
19
+
20
+ def to_a
21
+ @items.map { @wrap.call(_1) }
22
+ end
23
+
24
+ def size
25
+ @items.size
26
+ end
27
+
28
+ def length
29
+ @items.length
30
+ end
31
+
32
+ def empty?
33
+ @items.empty?
34
+ end
35
+
36
+ def include?(item)
37
+ @items.include?(to_record(item))
38
+ end
39
+
40
+ def [](index)
41
+ item = @items[index]
42
+ item ? @wrap.call(item) : nil
43
+ end
44
+
45
+ def first
46
+ item = @items.first
47
+ item ? @wrap.call(item) : nil
48
+ end
49
+
50
+ def last
51
+ item = @items.last
52
+ item ? @wrap.call(item) : nil
53
+ end
54
+
55
+ def <<(item_to_add)
56
+ @items << to_record(item_to_add)
57
+ self
58
+ end
59
+
60
+ def push(*items_to_add)
61
+ @items.push(*items_to_add.map { to_record(_1) })
62
+ self
63
+ end
64
+
65
+ def delete(item)
66
+ @items.delete(to_record(item))
67
+ end
68
+
69
+ def delete_at(index)
70
+ @items.delete_at(index)
71
+ end
72
+
73
+ def original
74
+ @items
75
+ end
76
+
77
+ def inspect
78
+ "#<#{self.class.name} decorating=#{@items.inspect} with=#{@entity_class}>"
79
+ end
80
+
81
+ private
82
+
83
+ def to_record(item)
84
+ item.is_a?(Entity) ? item.record : item
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,145 @@
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
28
+ Client::System.current_bus.channel("interfacets:api").builder.submit(name)
29
+ end
30
+ end
31
+
32
+ klass
33
+ end
34
+ end
35
+
36
+ class Handler
37
+ attr_reader :store, :spec
38
+ def initialize(store:, spec:)
39
+ @store = store
40
+ @spec = spec
41
+ end
42
+
43
+ def build
44
+ if spec.type == :reference
45
+ value = spec.klass.new
46
+ store.send("#{spec.name}=", value)
47
+ value
48
+ else
49
+ value = spec.klass.new
50
+ items = store.send(spec.name)
51
+ if items.nil?
52
+ store.send("#{spec.name}=", [value])
53
+ else
54
+ items << value
55
+ end
56
+
57
+ value
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ class << self
64
+ def construct(...)
65
+ Constructor.call(...)
66
+ end
67
+
68
+ class Spec
69
+ attr_reader(:name, :klass, :type)
70
+ def initialize(name:, klass:, type:)
71
+ @name = name
72
+ @klass = klass
73
+ @type = type
74
+ end
75
+
76
+ def build
77
+ spec.klass.new
78
+ end
79
+ end
80
+
81
+ def accessor(name)
82
+ name = name.to_s
83
+
84
+ define_method(name) do
85
+ @attributes[name]
86
+ end
87
+
88
+ define_method("#{name}=") do |val|
89
+ @attributes[name] = val
90
+ end
91
+ end
92
+
93
+ def association(name, klass:, type: :reference)
94
+ name = name.to_s
95
+
96
+ associations[name] = Spec.new(
97
+ name:,
98
+ klass:,
99
+ type:,
100
+ )
101
+
102
+ define_method(name) do
103
+ @attributes[name]
104
+ end
105
+
106
+ define_method("#{name}=") do |val|
107
+ @attributes[name] = val
108
+ end
109
+ end
110
+
111
+ def collection(*args, **params)
112
+ association(*args, **params, type: :collection)
113
+ end
114
+
115
+ def associations
116
+ @associations ||= {}
117
+ end
118
+ end
119
+
120
+ attr_accessor :attributes
121
+ def initialize
122
+ @attributes = {
123
+ "internal_entity_id" => SecureRandom.uuid
124
+ }
125
+ end
126
+
127
+ def ==(other)
128
+ attributes == other.attributes
129
+ end
130
+
131
+ def association(name)
132
+ @association_handlers ||= (
133
+ self
134
+ .class
135
+ .associations
136
+ .map { |name, spec| [name, Handler.new(store: self, spec:)] }
137
+ .to_h
138
+ )
139
+
140
+ @association_handlers.fetch(name)
141
+ end
142
+
143
+ end
144
+ end
145
+ 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(/^\s*$/) 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