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,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ class Audio
7
+ include Channels::Base
8
+
9
+ type("interfacets:audio")
10
+
11
+ class Builder
12
+ attr_reader :event, :callbacks
13
+ attr_accessor :entity
14
+
15
+ def initialize
16
+ @event = {}
17
+ @callbacks = {}
18
+ end
19
+
20
+ def flush_event
21
+ @event.tap { @event = {} }
22
+ end
23
+
24
+ def play(sounds, reset_offset: false)
25
+ event[:type] = "play"
26
+ event[:payload] ||= { sounds:, resetOffset: reset_offset }
27
+ end
28
+
29
+ def load_audios(audios, &block)
30
+ id = SecureRandom.uuid
31
+ @event = {
32
+ id:,
33
+ type: "load",
34
+ payload: audios,
35
+ }
36
+ @callbacks[id] = block
37
+ end
38
+
39
+ def request_mic(&block)
40
+ id = SecureRandom.uuid
41
+ @callbacks[id] = block
42
+ @event = {
43
+ id:,
44
+ type: "request-mic",
45
+ payload: {},
46
+ }
47
+ end
48
+
49
+ def start_recording(&block)
50
+ id = SecureRandom.uuid
51
+ @callbacks[id] = block
52
+ @event = {
53
+ id:,
54
+ type: "start-record",
55
+ payload: {},
56
+ }
57
+ end
58
+
59
+ def stop_recording(&block)
60
+ id = SecureRandom.uuid
61
+ @callbacks[id] = block
62
+ @event = {
63
+ id:,
64
+ type: "stop-record",
65
+ payload: {},
66
+ }
67
+ end
68
+ end
69
+
70
+ def prepare(entity)
71
+ @builder ||= Builder.new
72
+ end
73
+
74
+ def builder(stream = "default")
75
+ @builder
76
+ end
77
+
78
+ def handle(event:, entity:, **)
79
+ return unless [
80
+ "loaded",
81
+ "mic-ready",
82
+ "recording-started",
83
+ "recording-stopped",
84
+ ].include?(event.fetch("type"))
85
+
86
+ callback_id = event.dig("payload", "id")
87
+ callback = builder.callbacks.delete(callback_id)
88
+ if event["payload"].nil? || event["payload"].empty?
89
+ entity.instance_exec(&callback) if callback
90
+ elsif callback
91
+ entity.instance_exec(event.fetch("payload"), &callback)
92
+ end
93
+ end
94
+
95
+ def result
96
+ { streams: { default: @builder.flush_event } }
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ module Base
7
+ extend ActiveSupport::Concern
8
+
9
+ attr_reader :id
10
+
11
+ def initialize(id:)
12
+ @id = id
13
+ end
14
+
15
+ def type
16
+ self.class.type
17
+ end
18
+
19
+ class_methods do
20
+ def type(str = nil)
21
+ @type = str if str
22
+ @type
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ class PageVisibility
7
+ include Channels::Base
8
+
9
+ type("interfacets:page-visibility")
10
+
11
+ def configure(facet:); end
12
+
13
+ def handle(facet:, event:)
14
+ facet.visibility_change(event.dig("payload", "state"))
15
+ end
16
+
17
+ def render(_facet); end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ module React
7
+ class Builder
8
+ attr_reader :nodes, :callbacks, :entity
9
+ def initialize(entity, cache)
10
+ @entity = entity
11
+ @nodes = []
12
+ @callbacks = {}
13
+ @stack = []
14
+ @cache = cache
15
+ @memos = (@cache["Interfacets.Memos"] ||= {})
16
+ end
17
+
18
+ def __reset__
19
+ @nodes = []
20
+ @stack = []
21
+ @memos = (@cache["Interfacets.Memos"] ||= {})
22
+ end
23
+
24
+ def apply(nodes)
25
+ nodes.each { @nodes << _1 }
26
+ end
27
+
28
+ def capture
29
+ raise ArgumentError unless block_given?
30
+
31
+ original_nodes = @nodes
32
+ @nodes = []
33
+ yield
34
+ captured_nodes = @nodes
35
+ @nodes = original_nodes
36
+ captured_nodes
37
+ end
38
+
39
+ def string(val)
40
+ string_node(val)
41
+ end
42
+
43
+ def str(...)
44
+ string(...)
45
+ end
46
+
47
+ # Need to override ruby's "p" methods (ie, puts)
48
+ def p(...)
49
+ add_node("p", ...)
50
+ end
51
+
52
+ def memo(*keys, on:, &)
53
+ System.logger.warn("Memo is disabled")
54
+ return add_node(
55
+ "React.Fragment",
56
+ &
57
+ )
58
+ memo_key = (
59
+ ([entity.internal_id] + keys)
60
+ .map(&:to_json)
61
+ .join(".")
62
+ )
63
+ memo_val = Array(on).map(&:to_json).join(".")
64
+
65
+ if @memos.dig(memo_key, :memo_val) == memo_val
66
+ @nodes << @memos.dig(memo_key, :component)
67
+ else
68
+ component = add_node(
69
+ "Interfacets.Memo",
70
+ memoKey: memo_key,
71
+ memoVal: memo_val,
72
+ &
73
+ )
74
+
75
+ @memos[memo_key] = { memo_val:, component: }
76
+ end
77
+ end
78
+
79
+ def cache(*keys, &)
80
+ cache_key = keys.map(&:to_json).join(".")
81
+ if @cache.key?(cache_key)
82
+ @nodes << @cache.fetch(cache_key)
83
+ else
84
+ @cache[cache_key] = (
85
+ add_node(
86
+ "Interfacets.Cache",
87
+ cacheKey: cache_key,
88
+ &
89
+ )
90
+ )
91
+ end
92
+ end
93
+
94
+ def safe_html(str)
95
+ # add_node("React.Fragment", dangerouslySetInnerHTML: { __html: str })
96
+ end
97
+
98
+ def f(id, callback = nil, &block)
99
+ register_handler(id: id.to_json, callback: callback || block)
100
+ end
101
+
102
+ def prop(path)
103
+ paths = (
104
+ if path.is_a?(String)
105
+ path.split(".")
106
+ elsif path.is_a?(Symbol)
107
+ [path.to_s]
108
+ else
109
+ path
110
+ end
111
+ )
112
+
113
+ {
114
+ type: "interfacets:react-dom:prop",
115
+ payload: { path: paths },
116
+ }
117
+ end
118
+
119
+ def function(...)
120
+ f(...)
121
+ end
122
+
123
+ def respond_to_missing?(...)
124
+ true
125
+ end
126
+
127
+ def method_missing(meth, *, **, &)
128
+ raise if meth.to_s == "name"
129
+ add_node(meth, *, **, &)
130
+ end
131
+
132
+ private
133
+
134
+ def string_node(str)
135
+ @nodes << {
136
+ type: "interfacets:string-node",
137
+ attributes: { value: str },
138
+ }
139
+ end
140
+
141
+ def register_handler(id:, callback:)
142
+ callbacks[id] = callback
143
+
144
+ {
145
+ type: "interfacets:react-dom:memoized-action",
146
+ payload: { id: },
147
+
148
+ }
149
+ end
150
+
151
+ def add_node(element, str = nil, **attrs)
152
+ raise("cannot pass both a block and a string") if str && block_given?
153
+ raise if element == :entity
154
+
155
+ new_element = {
156
+ type: "interfacets:react-dom:element",
157
+ element:,
158
+ children: [],
159
+ attributes: (
160
+ attrs.transform_values { |value| transform_attr(value) }
161
+ ),
162
+ }
163
+
164
+ if block_given? || str
165
+ # recurse
166
+ original_nodes = @nodes
167
+ @nodes = new_element[:children]
168
+ begin
169
+ yield if block_given?
170
+ string_node(str) if str
171
+ ensure
172
+ @nodes = original_nodes
173
+ end
174
+ end
175
+
176
+ @nodes << new_element
177
+ new_element
178
+ end
179
+
180
+ def transform_attr(value)
181
+ if value.is_a?(Proc)
182
+ register_handler(
183
+ id: value.object_id.to_s,
184
+ callback: value,
185
+ )
186
+
187
+ {
188
+ type: "interfacets:react-dom:action",
189
+ payload: { id: value.object_id.to_s },
190
+ }
191
+ elsif value.is_a?(Array)
192
+ value.map { transform_attr(_1) }
193
+ elsif value.is_a?(Hash)
194
+ value.transform_values { |k| transform_attr(k) }
195
+ else
196
+ value
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ module React
7
+ class Channel
8
+ include Channels::Base
9
+
10
+ type("interfacets:react-dom")
11
+
12
+ def prepare(entity)
13
+ @cache ||= {}
14
+
15
+ if entity.internal_entity_id == @builder&.entity&.internal_entity_id
16
+ # keep all the callbacks we've ever assigned, if its the same entity
17
+ # should probably prune these? Only need to remember last renders
18
+ # callbacks?
19
+ builder.__reset__
20
+ else
21
+ @builder = Builder.new(entity, @cache)
22
+ end
23
+ end
24
+
25
+ def builder(stream = "default")
26
+ @builder
27
+ end
28
+
29
+ def result
30
+ @callbacks = builder.callbacks
31
+
32
+ {
33
+ streams: {
34
+ default: { dom: builder.nodes }
35
+ }
36
+ }
37
+ end
38
+
39
+ def handle(entity:, event:, build_entity:)
40
+ raise if event.nil?
41
+
42
+ actions = [
43
+ "interfacets:react-dom:action",
44
+ "interfacets:react-dom:memoized-action",
45
+ "interfacets:react-dom:online",
46
+ ]
47
+
48
+ raise("unknown event: #{event}") unless actions.include?(event.fetch("type"))
49
+
50
+ return if event.fetch("type") == "interfacets:react-dom:online"
51
+
52
+ payload = event.fetch("payload")
53
+ callback_id = payload.fetch("id")
54
+ callback = @callbacks.fetch(callback_id)
55
+ Evaluator.call(entity, callback, payload["event"])
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ module React
7
+ class Dom
8
+ attr_accessor :children, :handlers, :facet
9
+
10
+ def initialize(facet:)
11
+ @children = []
12
+ @portals = []
13
+ @handlers = {}
14
+ @facet = facet
15
+ end
16
+
17
+ def reset
18
+ @children = []
19
+ @portals = []
20
+ @handlers = {}
21
+ end
22
+
23
+ def handle(facet:, payload:)
24
+ handler_id = payload.fetch("id")
25
+ handler = handlers.fetch(handler_id)
26
+ callback = handler.fetch(:callback)
27
+ og_facet = handler.fetch(:facet)
28
+
29
+ Evaluator.call(og_facet, callback, payload["event"])
30
+ end
31
+
32
+ def register_handler(id:, callback:, facet:)
33
+ @handlers[id] = { facet:, callback: }
34
+
35
+ {
36
+ type: "interfacets:react-dom:memoized-action",
37
+ payload: { id: },
38
+
39
+ }
40
+ end
41
+
42
+ def string_node(str)
43
+ @children << {
44
+ type: "interfacets:string-node",
45
+ attributes: { value: str },
46
+ }
47
+ end
48
+
49
+ def call(type, str = nil, **attrs)
50
+ raise("cannot pass both a block and a string") if str && block_given?
51
+ raise if type == :facet
52
+
53
+ new_element = {
54
+ type:,
55
+ children: [],
56
+ attributes: (
57
+ attrs.transform_values { |value|
58
+ if value.is_a?(Proc)
59
+ register_handler(
60
+ id: value.object_id.to_s,
61
+ callback: value,
62
+ facet:,
63
+ )
64
+
65
+ {
66
+ type: "interfacets:react-dom:action",
67
+ payload: { id: value.object_id.to_s },
68
+ }
69
+ else
70
+ value
71
+ end
72
+ }
73
+ ),
74
+ }
75
+
76
+ if block_given? || str
77
+ # recurse
78
+ original_children = @children
79
+ @children = new_element[:children]
80
+ yield if block_given?
81
+ string_node(str) if str
82
+ @children = original_children
83
+ end
84
+
85
+ @children << new_element
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ module React
7
+ class Evaluator
8
+ def self.call(facet, handler, event)
9
+ new(facet).call(handler, event)
10
+ end
11
+
12
+ # This just isolates the evaluation so that we don't
13
+ # conflict with other methods
14
+ #
15
+ # for associations, this facet needs to be the relevant one...
16
+ attr_reader :facet
17
+
18
+ def initialize(facet)
19
+ @facet = facet
20
+ end
21
+
22
+ def call(handler, event)
23
+ Shared::Utils.permissive_exec(self, event, &handler)
24
+ end
25
+
26
+ def channel(name)
27
+ System.current_bus.channel(name).builder
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ class SpeechToText
7
+ include Channels::Base
8
+
9
+ type("speechToText")
10
+
11
+ class Builder
12
+ attr_accessor :facet
13
+ attr_accessor :entity
14
+
15
+ def initialize
16
+ @event = {}
17
+ @callbacks = {}
18
+ end
19
+
20
+ def flush_event
21
+ @event.tap { @event = {} }
22
+ end
23
+
24
+ def record(
25
+ on_done: ->(*, **) {},
26
+ on_progress: ->(*,**) {},
27
+ on_submit: ->(*, **) {}
28
+ )
29
+ id = SecureRandom.uuid
30
+ @event = {
31
+ id:,
32
+ type: "record",
33
+ }
34
+ @callbacks[id] = {
35
+ on_progress:,
36
+ on_done:,
37
+ on_submit:,
38
+ }
39
+ end
40
+
41
+ def callbacks
42
+ @callbacks ||= {}
43
+ end
44
+
45
+ def event
46
+ @event ||= {}
47
+ end
48
+ end
49
+
50
+ attr_reader :helper
51
+
52
+ def prepare(entity)
53
+ @builder ||= Builder.new
54
+ @builder.entity = entity
55
+ end
56
+
57
+ def builder(stream = "default")
58
+ @builder
59
+ end
60
+
61
+ def handle(event:, entity:, **)
62
+ callback_id = event.dig("payload", "id")
63
+
64
+ case event.fetch("type")
65
+ when "text-result"
66
+ builder
67
+ .callbacks
68
+ .delete(callback_id)
69
+ .fetch(:on_done)
70
+ .then do
71
+ entity.instance_exec(event.dig("payload", "text"), &_1)
72
+ end
73
+ when "text-progress"
74
+ builder
75
+ .callbacks
76
+ .fetch(callback_id)
77
+ .fetch(:on_progress)
78
+ .then do
79
+ entity.instance_exec(event.dig("payload", "text"), &_1)
80
+ end
81
+ when "text-submit"
82
+ builder
83
+ .callbacks
84
+ .fetch(callback_id)
85
+ .fetch(:on_submit)
86
+ .then do
87
+ entity.instance_exec(event.dig("payload", "text"), &_1)
88
+ end
89
+ else
90
+ raise "unknown event type: #{event.inspect}"
91
+ end
92
+ end
93
+
94
+ def result
95
+ { streams: { default: @builder.flush_event } }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ class Timer
7
+ include Channels::Base
8
+
9
+ type("interfacets:timer")
10
+
11
+ class Builder
12
+ attr_reader :event, :callbacks
13
+
14
+ def initialize
15
+ @event = {}
16
+ @callbacks = {}
17
+ end
18
+
19
+ def flush_event
20
+ @event.tap { @event = {} }
21
+ end
22
+
23
+ def callback(in_ms:, &block)
24
+ id = SecureRandom.uuid
25
+ event[:ms] = in_ms
26
+ event[:response] = { id: }
27
+ callbacks[id] = block
28
+ end
29
+ end
30
+
31
+ def prepare(entity)
32
+ @builder ||= Builder.new
33
+ end
34
+
35
+ def builder(stream = "defualt")
36
+ @builder
37
+ end
38
+
39
+ def handle(event:, entity:, **)
40
+ callback_id = event.dig("payload", "id")
41
+ entity.instance_exec(&builder.callbacks.fetch(callback_id))
42
+ builder.callbacks.delete(callback_id)
43
+ end
44
+
45
+ def result
46
+ { streams: { default: @builder.flush_event } }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end