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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ def self.start(...)
6
+ @system = System.start(...)
7
+ end
8
+
9
+ def self.system
10
+ @system
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require "interfacets/component_schema_parser"
7
+
8
+ module Interfacets
9
+ class ComponentRegistry
10
+ def initialize(config_path:)
11
+ @config_path = config_path
12
+ @schema_cache = {}
13
+ end
14
+
15
+ def write_client_registry(path:)
16
+ components = load_components
17
+ content = generate_registry_content(components)
18
+ write_atomic(path, content)
19
+ end
20
+
21
+ private
22
+
23
+ def load_components
24
+ raise Errno::ENOENT, "Configuration file not found at #{@config_path}" unless File.exist?(@config_path)
25
+
26
+ # 1. Load the raw YAML
27
+ components = YAML.load_file(@config_path, aliases: true) || {}
28
+
29
+ # 2. Parse with the V2 parser
30
+ parser = Interfacets::ComponentSchemaParser.new
31
+ parsed_components = {}
32
+ components.each do |name, config|
33
+ next unless config.is_a?(Hash)
34
+ component_data = { name => config }
35
+ # The result of parse is the component data, which we merge.
36
+ parsed_components.merge!(parser.parse(component_data))
37
+ end
38
+
39
+ parsed_components
40
+ end
41
+
42
+ def generate_registry_content(components)
43
+ registry_components = components.select { |_, config| config.key?("js") || has_transforms?(config) }
44
+
45
+ imports = []
46
+ mappings = []
47
+ any_transforms = false
48
+
49
+ registry_components.each do |name, config|
50
+ js_config = config["js"]
51
+ path = js_config&.dig("path")
52
+
53
+ transforms = extract_transforms(config)
54
+
55
+ if path
56
+ imports << if js_config["default"]
57
+ "import #{name} from \"#{path}\";"
58
+ elsif (export = js_config["export"])
59
+ "import { #{export} as #{name} } from \"#{path}\";"
60
+ else
61
+ "import { #{name} } from \"#{path}\";"
62
+ end
63
+ else
64
+ # Native element
65
+ imports << "const #{name} = \"#{name}\";"
66
+ end
67
+
68
+ if transforms.any?
69
+ any_transforms = true
70
+ mappings << " #{name}: withTransform(#{name}, #{transforms.to_json}),"
71
+ else
72
+ mappings << " #{name},"
73
+ end
74
+ end
75
+
76
+ if any_transforms
77
+ imports.unshift('import { withTransform } from "interfacets/withTransform";')
78
+ end
79
+
80
+ template = []
81
+ template << imports.join("\n") if imports.any?
82
+ template << <<~JS.strip
83
+ export const registry = {
84
+ #{mappings.join("\n")}
85
+ };
86
+ JS
87
+
88
+ "#{template.join("\n\n")}\n"
89
+ end
90
+
91
+ def has_transforms?(config)
92
+ extract_transforms(config).any?
93
+ end
94
+
95
+ def extract_transforms(config)
96
+ transforms = {}
97
+ if props = config["props"]
98
+ props.each do |prop_name, prop_config|
99
+ if prop_config["is_event"] && prop_config.key?("transform")
100
+ transforms[prop_name] = prop_config["transform"]
101
+ end
102
+ end
103
+ end
104
+ transforms
105
+ end
106
+
107
+
108
+
109
+ def write_atomic(path, content)
110
+ temp_file = "#{path}.tmp"
111
+ File.write(temp_file, content)
112
+ FileUtils.mv(temp_file, path)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schemer"
4
+
5
+ module Interfacets
6
+ class ComponentSchemaParser
7
+ SCHEMA = {
8
+ "$schema" => "http://json-schema.org/draft-07/schema#",
9
+ "type" => "object",
10
+ "patternProperties" => {
11
+ "^.*$" => {
12
+ "type" => "object",
13
+ "properties" => {
14
+ "schema" => {
15
+ "if" => { "type" => "object" },
16
+ "then" => {
17
+ "not" => { "required" => ["props"] }
18
+ }
19
+ },
20
+ "props" => {
21
+ "type" => "object",
22
+ "patternProperties" => {
23
+ "^.*$" => {
24
+ "type" => "object",
25
+ "properties" => {
26
+ "is_event" => { "type" => "boolean" }
27
+ },
28
+ "if" => {
29
+ "required" => ["is_event"],
30
+ "properties" => { "is_event" => { "const" => true } }
31
+ },
32
+ "then" => {
33
+ "properties" => {
34
+ "transform" => { "$ref" => "#/definitions/Transform" },
35
+ "payload" => { "type" => "object" }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "definitions" => {
45
+ "Transform" => {
46
+ "oneOf" => [
47
+ { "type" => "null" },
48
+ { "type" => "string" },
49
+ {
50
+ "type" => "object",
51
+ "additionalProperties" => {
52
+ "type" => "array",
53
+ "items" => [
54
+ { "type" => "integer" }
55
+ ],
56
+ "additionalItems" => { "type" => "string" },
57
+ "minItems" => 1
58
+ }
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ }.freeze
64
+
65
+ SCHEMER = JSONSchemer.schema(SCHEMA)
66
+
67
+ def parse(component_config)
68
+ errors = SCHEMER.validate(component_config).to_a
69
+ if errors.any?
70
+ if errors.any? { |e| e["data_pointer"].include?("/schema") && e["type"] == "not" }
71
+ raise StandardError, "Old schema format is not supported."
72
+ end
73
+
74
+ message = "Validation failed:\n"
75
+ errors.each do |error|
76
+ message += " - #{error["data_pointer"]}: #{error["type"]} #{error["details"]}\n"
77
+ end
78
+ raise StandardError, message
79
+ end
80
+
81
+ component_config
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,66 @@
1
+ FROM emscripten/emsdk:3.1.62
2
+
3
+ RUN apt update
4
+ RUN apt install -y vim git
5
+ RUN apt install -y ruby
6
+ RUN git clone https://github.com/mruby/mruby.git /home/root/interfacets/mruby
7
+ WORKDIR /home/root/interfacets/mruby
8
+ # RUN git fetch origin master && git reset --hard origin/master
9
+ RUN git fetch origin --tags && git reset --hard 3.4.0
10
+ RUN rake all test
11
+ ENV PATH="/home/root/interfacets/mruby/bin:${PATH}"
12
+ COPY ./lib/interfacets/mruby/build_config.rb \
13
+ /home/root/interfacets/
14
+ WORKDIR /home/root/interfacets
15
+ RUN cd mruby && \
16
+ sed -i '/MRB_STR_LENGTH_MAX 1048576/c#define MRB_STR_LENGTH_MAX 0' src/string.c && \
17
+ MRUBY_CONFIG=../build_config.rb rake
18
+ RUN mkdir -p assets/mruby build
19
+ RUN cp -r mruby/include assets/mruby \
20
+ && cp mruby/build/emscripten/lib/libmruby.a assets/mruby
21
+ COPY ./lib/interfacets/mruby/init.c \
22
+ ./lib/interfacets/mruby/entrypoint.rb \
23
+ /home/root/interfacets/
24
+ # RUN mrbc --help
25
+ RUN mrbc -B ruby_app -o build/app.c entrypoint.rb
26
+ RUN echo "\n" >> build/app.c && cat init.c >> build/app.c
27
+ RUN emcc \
28
+ -s EXPORT_NAME=MRuby \
29
+ -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
30
+ -s ENVIRONMENT=web \
31
+ -s MODULARIZE=1 \
32
+ -s EXPORT_ES6=1 \
33
+ -s ALLOW_MEMORY_GROWTH=1 \
34
+ -s SINGLE_FILE=1 \
35
+ -I ./assets/mruby/include \
36
+ build/app.c \
37
+ ./assets/mruby/libmruby.a \
38
+ -o build/ruby.js
39
+
40
+ RUN emcc \
41
+ -s EXPORT_NAME=MRuby \
42
+ # -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
43
+ -s ENVIRONMENT=node \
44
+ -s MODULARIZE=1 \
45
+ -s EXPORT_ES6=1 \
46
+ -s SIDE_MODULE=1 \
47
+ -s ALLOW_MEMORY_GROWTH=1 \
48
+ -I ./assets/mruby/include \
49
+ build/app.c \
50
+ ./assets/mruby/libmruby.a \
51
+ -o build/test.wasm
52
+
53
+ RUN emcc \
54
+ -s EXPORT_NAME=MRuby \
55
+ -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
56
+ -s ENVIRONMENT=node \
57
+ -s MODULARIZE=1 \
58
+ -s EXPORT_ES6=1 \
59
+ -s MAIN_MODULE=1 \
60
+ -s ALLOW_MEMORY_GROWTH=1 \
61
+ -I ./assets/mruby/include \
62
+ build/app.c \
63
+ ./assets/mruby/libmruby.a \
64
+ -o build/test.js
65
+
66
+ RUN mv build /build
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ MRuby::Build.new do |conf|
4
+ toolchain :gcc
5
+ conf.gembox("full-core")
6
+ end
7
+
8
+ MRuby::CrossBuild.new("emscripten") do |conf|
9
+ toolchain :clang
10
+ conf.gembox("full-core")
11
+ conf.cc.command = "emcc"
12
+ conf.cc.flags = ["-Os", "-fPIC"]
13
+ conf.linker.command = "emcc"
14
+ conf.archiver.command = "emar"
15
+
16
+ conf.gem(github: "mattn/mruby-json")
17
+ conf.gem(github: "mattn/mruby-base64")
18
+ conf.gem(github: "monochromegane/mruby-secure-random")
19
+ conf.gem(github: "iij/mruby-regexp-pcre")
20
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ def wrapped_eval(str, path)
5
+ eval(str, binding, path)
6
+ rescue StandardError, SyntaxError => e
7
+ $stderr.puts(e.class)
8
+ $stderr.puts(e.message)
9
+ $stderr.puts(e.backtrace.join("\n"))
10
+ end
11
+
12
+ def wrapped_call(json_string)
13
+ spec = JSON.parse(json_string)
14
+ eval(spec.fetch("receiver")).send(
15
+ spec.fetch("method"),
16
+ *spec.fetch("args", []),
17
+ )
18
+ rescue StandardError, SyntaxError => e
19
+ $stderr.puts(e.class)
20
+ $stderr.puts(e.message)
21
+ $stderr.puts(e.backtrace.join("\n"))
22
+ end
23
+ end
@@ -0,0 +1,66 @@
1
+ #include <mruby.h>
2
+ #include <mruby/irep.h>
3
+ #include <emscripten.h>
4
+
5
+ mrb_value js_eval(mrb_state* mrb, mrb_value self)
6
+ {
7
+ char *js_code;
8
+ mrb_get_args(mrb, "z", &js_code);
9
+ emscripten_run_script(js_code);
10
+ return self;
11
+ }
12
+
13
+ mrb_state *mrb;
14
+ void EMSCRIPTEN_KEEPALIVE ruby_eval(char* str, char* path) {
15
+ int ai = mrb_gc_arena_save(mrb);
16
+ mrb_funcall(
17
+ mrb,
18
+ mrb_top_self(mrb),
19
+ "wrapped_eval",
20
+ 2,
21
+ mrb_str_new_cstr(mrb, str),
22
+ mrb_str_new_cstr(mrb, path)
23
+ );
24
+
25
+ if (mrb->exc) mrb_print_error(mrb);
26
+ mrb_gc_arena_restore(mrb, ai);
27
+ }
28
+
29
+ // json of the form:
30
+ // {receiver: , method:, args: [ ...] ]
31
+ void EMSCRIPTEN_KEEPALIVE ruby_call(char* json) {
32
+ int ai = mrb_gc_arena_save(mrb);
33
+
34
+ mrb_funcall(
35
+ mrb,
36
+ mrb_top_self(mrb),
37
+ "wrapped_call",
38
+ 1,
39
+ mrb_str_new_cstr(mrb, json)
40
+ );
41
+
42
+ if (mrb->exc) mrb_print_error(mrb);
43
+ mrb_gc_arena_restore(mrb, ai);
44
+ }
45
+
46
+ int main() {
47
+ // Leave this open for all eternity
48
+ mrb = mrb_open();
49
+
50
+ if (!mrb) { /* handle error */ }
51
+
52
+ mrb_define_method(
53
+ mrb,
54
+ mrb->kernel_module,
55
+ "js_eval",
56
+ js_eval,
57
+ MRB_ARGS_REQ(1)
58
+ );
59
+
60
+ mrb_load_irep(mrb, ruby_app);
61
+
62
+ // If an exception, print error
63
+ if (mrb->exc) mrb_print_error(mrb);
64
+
65
+ return 0;
66
+ }
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Server
5
+ class Api
6
+ attr_reader :entity, :name, :registry
7
+ def initialize(entity:, name:, registry:)
8
+ @entity = entity
9
+ @name = name
10
+ @registry = registry
11
+ end
12
+
13
+ def handle(event)
14
+ Shared::Entities::Bus
15
+ .new(entity:)
16
+ .handle(event:)
17
+
18
+ if entity.channel.rendered?
19
+ entity.channel.render_facet
20
+ else
21
+ emit("after_#{event.fetch("action")}", nesting: event.fetch("nesting"))
22
+ end
23
+ end
24
+
25
+ def render
26
+ emit("after_load", nesting: ["root"])
27
+ end
28
+
29
+ private
30
+
31
+ def emit(action, nesting: )
32
+ {
33
+ facet: name,
34
+ payload: (
35
+ Shared::Entities::Bus
36
+ .new(entity:)
37
+ .serialize(to: "client", action:, nesting:)
38
+ )
39
+ }
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Server
5
+ module Assets
6
+ class Facet
7
+ def self.register(klass:, assets:)
8
+ assets[klass.name] = new(klass).code
9
+ end
10
+
11
+ attr_reader :klass
12
+ def initialize(klass)
13
+ @klass = klass
14
+ end
15
+
16
+ def code
17
+ # nest it all so names resolve
18
+ *mods, klass_name = klass.name.split("::")
19
+
20
+ headers = []
21
+ footers = []
22
+
23
+ current_mod = ""
24
+ mods.each do |mod_name|
25
+ current_mod += "::#{mod_name}"
26
+ type = current_mod.constantize.is_a?(Class) ? :class : :module
27
+ headers << "#{type} #{mod_name}"
28
+ footers << "end"
29
+ end
30
+
31
+ headers << "class #{klass_name}"
32
+ headers << " include Interfacets::Shared::Facet"
33
+ footers << "end"
34
+
35
+ <<~TXT
36
+ #{headers.join("\n")}
37
+
38
+ include Interfacets::Shared::Facets::Schema
39
+
40
+ view_spec #{write_source(klass.client_config.view.block)}
41
+
42
+ entity_spec #{write_source(klass.client_config.api.block)}
43
+
44
+ client_spec #{write_source(klass.client_config.entity.block)}
45
+ #{footers.join("\n")}
46
+ TXT
47
+ end
48
+
49
+ private
50
+
51
+ def write_source(block)
52
+ return unless block
53
+
54
+ RubyVM::AbstractSyntaxTree
55
+ .of(block, keep_script_lines: true)
56
+ .source
57
+ .strip
58
+
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end