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,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Channels
6
+ class Url
7
+ include Channels::Base
8
+
9
+ type("interfacets:url")
10
+
11
+ class Builder
12
+ attr_reader :state
13
+ def initialize
14
+ @state = {}
15
+ end
16
+
17
+ def path(path, query: {})
18
+ state[:urlSpec] = {
19
+ url: (
20
+ System
21
+ .current_bus
22
+ .url_for(path)
23
+ ),
24
+ queryParams: query,
25
+ }
26
+ end
27
+
28
+ def redirect(url, query: {})
29
+ @redirected = true
30
+ state[:redirectUrlSpec] = { url:, query: }
31
+ end
32
+
33
+ def redirected?
34
+ @redirected
35
+ end
36
+ end
37
+
38
+ def prepare(entity)
39
+ @builder ||= Builder.new
40
+ end
41
+
42
+ def builder(stream = "default")
43
+ @builder
44
+ end
45
+
46
+ def result
47
+ { streams: { default: @builder.state } }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class Config
6
+ attr_accessor(
7
+ :mount_point,
8
+ :root_url
9
+ )
10
+
11
+ def url_for(path:)
12
+ [
13
+ root_url,
14
+ mount_point,
15
+ path,
16
+ ]
17
+ .map { _1.sub(%r{/$}, "").sub(%r{^/}, "") }
18
+ .join("/")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ # alias: CrappyDelegator
6
+ class Delegator
7
+ def initialize(obj)
8
+ @__obj__ = obj
9
+ end
10
+
11
+ def __getobj__
12
+ @__obj__
13
+ end
14
+
15
+ def respond_to_missing?(...)
16
+ @__obj__.send(:respond_to_missing?, ...)
17
+ end
18
+
19
+ def method_missing(method, *a, **p, &)
20
+ @__obj__.send(method, *a, **p, &)
21
+ end
22
+
23
+ # delegation helpers
24
+ def inspect
25
+ [self.class.name, __getobj__.inspect].join(" -> ")
26
+ end
27
+
28
+ def to_s
29
+ inspect
30
+ end
31
+
32
+ def presence
33
+ self
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class Facet
6
+ class << self
7
+ attr_accessor(
8
+ :shared,
9
+ :entity,
10
+ :view,
11
+ :store,
12
+ )
13
+ end
14
+
15
+ attr_reader :entity, :view
16
+ def initialize(entity)
17
+ @entity = entity
18
+ @view = self.class.view.new
19
+ end
20
+
21
+ def render(channels)
22
+ view.render(entity:, channels:)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class Facet2
6
+ class << self
7
+ attr_accessor(
8
+ :shared_entity,
9
+ :entity,
10
+ :view
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Attributes
7
+ class Accessor
8
+ attr_reader :send_if
9
+ def initialize(send_if:)
10
+ @send_if = send_if
11
+ end
12
+
13
+ def coerce(val, **)
14
+ val
15
+ end
16
+
17
+ def serialize(val)
18
+ val
19
+ end
20
+
21
+ def should_send?(facet)
22
+ facet.instance_exec(&send_if)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Attributes
7
+ class Association
8
+ attr_reader :nested, :anonymous, :config, :bind_map
9
+
10
+ def initialize(nested:, anonymous:, config:, bind_map:)
11
+ @nested = nested
12
+ @anonymous = anonymous
13
+ @config = config
14
+ @bind_map = bind_map
15
+ end
16
+
17
+ def coerce(val, parent:)
18
+ return if val.nil?
19
+
20
+ facet.build(
21
+ attrs: val,
22
+ binds: bind_map.transform_values { |v| Bind.new(name: v, facet: parent) },
23
+ )
24
+ end
25
+
26
+ def build(attrs:, parent:)
27
+ facet.build(
28
+ attrs:,
29
+ binds: bind_map.transform_values { |v| Bind.new(name: v, facet: parent) },
30
+ )
31
+ end
32
+
33
+ def serialize(val)
34
+ val&.serialize
35
+ end
36
+
37
+ def facet
38
+ @facet ||= (
39
+ if anonymous
40
+ @nested
41
+ else
42
+ config.schema(@nested.fetch("name"))
43
+ end
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Attributes
7
+ class Bind
8
+ attr_reader :name, :facet
9
+ def initialize(name:, facet:)
10
+ @name = name
11
+ @facet = facet
12
+ end
13
+
14
+ def get
15
+ facet.public_send(name)
16
+ end
17
+
18
+ def set(v)
19
+ facet.public_send("#{name}=", v)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Attributes
7
+ class Collection
8
+ attr_reader :nested, :anonymous, :config, :bind_map
9
+
10
+ def initialize(nested:, anonymous:, config:, bind_map:)
11
+ @nested = nested
12
+ @anonymous = anonymous
13
+ @config = config
14
+ @bind_map = bind_map
15
+ end
16
+
17
+ def coerce(val, parent:)
18
+ return [] if val.nil?
19
+
20
+ val.map { build(attrs: _1, parent:) }
21
+ end
22
+
23
+ def build(attrs:, parent:)
24
+ facet.build(
25
+ attrs:,
26
+ binds: bind_map.transform_values { |v| Bind.new(name: v, facet: parent) },
27
+ )
28
+ end
29
+
30
+ def serialize(val)
31
+ val.map(&:serialize)
32
+ end
33
+
34
+ def facet
35
+ @facet ||= (
36
+ if anonymous
37
+ @nested
38
+ else
39
+ config.schema(@nested.fetch("name"))
40
+ end
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Attributes
7
+ class Readonly
8
+ def coerce(val, **)
9
+ val
10
+ end
11
+
12
+ def serialize(val)
13
+ val
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ class Deserializer
7
+ def self.call(data:, config: Client.system.config)
8
+ Logger.main.debug("deserializing facet: #{data.fetch("facet_class")}")
9
+
10
+ klass = Object.const_get(data.fetch("facet_class"))
11
+
12
+ Logger.main.debug("constructing: #{klass.client.name}")
13
+
14
+ facet = klass.new
15
+
16
+ entity = (
17
+ klass
18
+ .client
19
+ .build(data.fetch("attributes"), parent: nil, facet:)
20
+ )
21
+
22
+ Logger.main.debug("entity constructed")
23
+
24
+ facet.entity = entity
25
+ facet
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Schema
7
+ class Deserializer
8
+ def self.call(...)
9
+ new(...).call
10
+ end
11
+
12
+ attr_reader :schema, :results
13
+ def initialize(schema:)
14
+ @schema = schema
15
+ @results = { views: {}, entities: {}, apis: {} }
16
+ end
17
+
18
+ def call
19
+ schema.fetch("views").each do |view_type, view_json|
20
+ block = view_json.fetch("body")
21
+
22
+ results[:views][view_type] = View.new(
23
+ block: eval(block),
24
+ name: view_type,
25
+ )
26
+ end
27
+
28
+ schema.fetch("apis").each do |api_type, entity_json|
29
+ block = entity_json.fetch("body")
30
+
31
+ results[:apis][api_type] =
32
+ Module.new do
33
+ extend ActiveSupport::Concern
34
+ define_singleton_method(:name) { api_type }
35
+
36
+ included do
37
+ class_exec(&eval(block))
38
+ end
39
+ end
40
+ end
41
+
42
+ schema.fetch("entities").each do |entity_type, client_json|
43
+ block = client_json.fetch("body")
44
+ api_type = client_json.fetch("api_type")
45
+
46
+ api_mod = results[:apis].fetch(api_type)
47
+
48
+ results[:entities][entity_type] =
49
+ Class.new(Entity) do
50
+ define_singleton_method(:name) { entity_type }
51
+ include api_mod
52
+
53
+ class_exec(&eval(block))
54
+ end
55
+ end
56
+
57
+ results
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ module Schema
7
+ extend ActiveSupport::Concern
8
+ class_methods do
9
+ def view_spec(&block)
10
+ @view_spec = block
11
+ end
12
+
13
+ def view
14
+ # TODO: change this to just inherit from view
15
+ @view ||= View.new(
16
+ block: @view_spec,
17
+ name: "#{self.name}::View",
18
+ )
19
+ end
20
+
21
+ def client_spec(&block)
22
+ @client_spec = block
23
+ end
24
+
25
+ def client
26
+ # scoping
27
+ client_name = "#{self.name}::Client"
28
+ client_spec = @client_spec
29
+ entity = self.entity
30
+
31
+ @client ||=
32
+ Class.new(Entity) do
33
+ define_singleton_method(:name) { client_name }
34
+ extend_entity { include entity }
35
+
36
+ class_exec(&client_spec)
37
+ end
38
+ end
39
+
40
+ def entity_spec(&block)
41
+ @entity_spec = block
42
+ end
43
+
44
+ def entity
45
+ # scoping
46
+ entity_name = "#{self.name}::Entity"
47
+ entity_spec = @entity_spec
48
+
49
+ @entity ||=
50
+ Module.new do
51
+ extend ActiveSupport::Concern
52
+ define_singleton_method(:name) { entity_name }
53
+
54
+ included do
55
+ class_exec(&entity_spec)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Facets
6
+ class Serializer
7
+ class << self
8
+ def call(facet)
9
+ {
10
+ facet_class: facet.class.name,
11
+ attributes: facet.entity.serialize,
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class Registry
6
+ def build(name)
7
+ entity = Object.const_get("#{name}::Client::Entity")
8
+
9
+ entity.new(
10
+ store: entity.store.new,
11
+ parent: nil,
12
+ nesting: ["root"]
13
+ )
14
+ end
15
+
16
+ def register(schema, namespace: "Things")
17
+ schema.each do |name, schema|
18
+ next if registry.key?(name)
19
+
20
+ shared = Class.new(Shared::Entity) do
21
+ schema.fetch("shared").each { class_exec(&eval(_1)) }
22
+ end
23
+
24
+ view = Class.new(View) do
25
+ schema.fetch("view").each { view(&eval(_1)) }
26
+ end
27
+
28
+ entity = Class.new(Shared::Entity) do
29
+ define_singleton_method(:view) { view }
30
+ self.manifest = shared
31
+
32
+ schema.fetch("shared").each { class_exec(&eval(_1)) }
33
+ schema.fetch("client").each { class_exec(&eval(_1)) }
34
+
35
+ actions.each do |name, spec|
36
+ if name.start_with?("after_")
37
+ define_method(name) {}
38
+ end
39
+ end
40
+ end
41
+
42
+ store = Shared::GeneratedStore.construct(entity)
43
+
44
+ mod = set_const(
45
+ [
46
+ namespace,
47
+ name.split("::"),
48
+ ].flatten,
49
+ Module.new
50
+ )
51
+ entity.define_singleton_method(:facet_name) { name }
52
+ mod.const_set("Entity", entity)
53
+ entity.const_set("Shared", shared)
54
+ entity.const_set("View", view)
55
+
56
+ registry[name] = { entity:, store: }
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def set_const(mods, klass)
63
+
64
+ namespace = Object
65
+
66
+ mods[0..-1].each do |mod|
67
+ namespace = (
68
+ if namespace.const_defined?(mod, false)
69
+ namespace.const_get(mod)
70
+ else
71
+ namespace.const_set(mod, Module.new)
72
+ end
73
+ )
74
+ end
75
+
76
+ namespace.const_set(mods.last, klass)
77
+ end
78
+
79
+ def registry
80
+ @registry ||= {}
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class System
6
+ module Transmit
7
+ def self.call(data)
8
+ Kernel.js_eval("self.rubyEvent(#{data.to_json})")
9
+ end
10
+ end
11
+
12
+ class << self
13
+ attr_accessor(
14
+ :current_bus,
15
+ :logger,
16
+ )
17
+
18
+ def logger
19
+ @logger ||= InterfacetsLogger.main
20
+ end
21
+
22
+ def start(transmit: Transmit)
23
+ @instance = new(transmit)
24
+ end
25
+ end
26
+
27
+ attr_accessor :transmit
28
+ def initialize(transmit)
29
+ @transmit = transmit
30
+ end
31
+
32
+ def handle(event)
33
+ if event.fetch("type") == "interfacets:system:create_bus"
34
+ create_bus(event.fetch("payload"))
35
+ else
36
+ bus_id = event.dig("destination", "bus")
37
+ raise("unknown event: #{event.inspect}") unless bus_id
38
+
39
+ System.current_bus = bus_registry.fetch(bus_id)
40
+ payload = System.current_bus.handle(event)
41
+
42
+ transmit.(
43
+ {
44
+ type: "interfacets:system:render",
45
+ payload:,
46
+ },
47
+ )
48
+ end
49
+ ensure
50
+ GC.start
51
+ end
52
+
53
+ # private
54
+
55
+ def config
56
+ @config ||= Config.new
57
+ end
58
+
59
+ def create_bus(payload)
60
+ id = payload.fetch("id")
61
+ hydration_event = payload.fetch("hydration")
62
+
63
+ channel_ids = payload.fetch("channel_ids")
64
+
65
+ Bus.new(
66
+ id:,
67
+ channels: [
68
+ Channels::Api.new(id: "interfacets:api"),
69
+ Channels::React::Channel.new(id: "dom"),
70
+ Channels::Url.new(id: "url"),
71
+ (Channels::SpeechToText.new(id: "speechToText") if channel_ids.include?("speechToText")),
72
+ (Channels::Timer.new(id: "timer") if channel_ids.include?("timer")),
73
+ (Channels::Audio.new(id: "audio") if channel_ids.include?("audio")),
74
+ ].compact,
75
+ root_url: payload.fetch("config").fetch("root_url"),
76
+ ).tap do
77
+ bus_registry[id] = _1
78
+ end
79
+
80
+ handle(hydration_event)
81
+ end
82
+
83
+ def bus_registry
84
+ @bus_registry ||= {}
85
+ end
86
+ end
87
+ end
88
+ end