funicular 0.1.0 → 0.2.1

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +12 -4
  4. data/Rakefile +29 -0
  5. data/docs/architecture.md +113 -404
  6. data/lib/funicular/assets/funicular.css +23 -0
  7. data/lib/funicular/compiler.rb +25 -18
  8. data/lib/funicular/helpers/picoruby_helper.rb +65 -3
  9. data/lib/funicular/middleware.rb +34 -9
  10. data/lib/funicular/plugin.rb +147 -0
  11. data/lib/funicular/schema.rb +167 -0
  12. data/lib/funicular/ssr/runtime.rb +101 -0
  13. data/lib/funicular/ssr.rb +51 -0
  14. data/lib/funicular/testing/node_runner.mjs +293 -0
  15. data/lib/funicular/testing/node_runner.rb +190 -0
  16. data/lib/funicular/testing.rb +22 -0
  17. data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
  18. data/lib/funicular/vendor/picoruby/debug/picoruby.js +115 -87
  19. data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
  20. data/lib/funicular/vendor/picoruby/dist/picoruby.js +1 -1
  21. data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
  22. data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -0
  23. data/lib/funicular/vendor/picoruby-test-node/picoruby.js +6908 -0
  24. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
  25. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -0
  26. data/lib/funicular/version.rb +1 -1
  27. data/lib/funicular.rb +3 -0
  28. data/lib/generators/funicular/chat/chat_generator.rb +104 -0
  29. data/lib/generators/funicular/chat/templates/application_cable_channel.rb.tt +4 -0
  30. data/lib/generators/funicular/chat/templates/application_cable_connection.rb.tt +4 -0
  31. data/lib/generators/funicular/chat/templates/create_funicular_chat_messages.rb.tt +10 -0
  32. data/lib/generators/funicular/chat/templates/funicular_chat.css.tt +141 -0
  33. data/lib/generators/funicular/chat/templates/funicular_chat_channel.rb.tt +5 -0
  34. data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +135 -0
  35. data/lib/generators/funicular/chat/templates/funicular_chat_component_picotest.rb.tt +64 -0
  36. data/lib/generators/funicular/chat/templates/funicular_chat_controller.rb.tt +4 -0
  37. data/lib/generators/funicular/chat/templates/funicular_chat_message.rb.tt +13 -0
  38. data/lib/generators/funicular/chat/templates/funicular_chat_messages_controller.rb.tt +23 -0
  39. data/lib/generators/funicular/chat/templates/initializer.rb.tt +4 -0
  40. data/lib/generators/funicular/chat/templates/show.html.erb.tt +6 -0
  41. data/lib/tasks/funicular.rake +87 -4
  42. data/minitest/fixtures/funicular_app/components/greeting_component.rb +16 -0
  43. data/minitest/fixtures/funicular_app/initializer.rb +5 -0
  44. data/minitest/form_for_test.rb +106 -0
  45. data/minitest/hydration_test.rb +87 -0
  46. data/minitest/plugin_test.rb +51 -0
  47. data/minitest/schema_test.rb +106 -0
  48. data/minitest/ssr_test.rb +94 -0
  49. data/minitest/validations_test.rb +183 -0
  50. data/mrbgem.rake +1 -0
  51. data/mrblib/0_validations.rb +206 -0
  52. data/mrblib/1_validators.rb +180 -0
  53. data/mrblib/cable.rb +24 -9
  54. data/mrblib/component.rb +246 -47
  55. data/mrblib/debug.rb +3 -0
  56. data/mrblib/differ.rb +47 -37
  57. data/mrblib/file_upload.rb +9 -1
  58. data/mrblib/form_builder.rb +28 -6
  59. data/mrblib/funicular.rb +97 -8
  60. data/mrblib/html_serializer.rb +121 -0
  61. data/mrblib/http.rb +123 -29
  62. data/mrblib/model.rb +50 -0
  63. data/mrblib/patcher.rb +78 -8
  64. data/mrblib/router.rb +40 -3
  65. data/mrblib/store.rb +304 -0
  66. data/mrblib/store_collection.rb +171 -0
  67. data/mrblib/store_singleton.rb +79 -0
  68. data/mrblib/vdom.rb +2 -0
  69. data/sig/cable.rbs +1 -0
  70. data/sig/component.rbs +13 -5
  71. data/sig/funicular.rbs +14 -1
  72. data/sig/html_serializer.rbs +20 -0
  73. data/sig/http.rbs +21 -6
  74. data/sig/model.rbs +6 -1
  75. data/sig/patcher.rbs +4 -1
  76. data/sig/router.rbs +3 -2
  77. data/sig/store.rbs +89 -0
  78. data/sig/store_collection.rbs +43 -0
  79. data/sig/store_singleton.rbs +19 -0
  80. data/sig/validations.rbs +103 -0
  81. data/sig/vdom.rbs +6 -6
  82. metadata +48 -12
  83. data/docs/README.md +0 -419
  84. data/docs/advanced-features.md +0 -632
  85. data/docs/components-and-state.md +0 -539
  86. data/docs/data-fetching.md +0 -528
  87. data/docs/forms.md +0 -446
  88. data/docs/rails-integration.md +0 -426
  89. data/docs/realtime.md +0 -543
  90. data/docs/routing-and-navigation.md +0 -427
  91. data/docs/styling.md +0 -285
@@ -0,0 +1,79 @@
1
+ module Funicular
2
+ class Store
3
+ # Singleton store: one value per scope. Suitable for things like a
4
+ # per-channel draft text or per-user preferences blob.
5
+ #
6
+ # class DraftStore < Funicular::Store::Singleton
7
+ # database "funicular_drafts"
8
+ # scope :channel_id
9
+ # cleared_on :logout
10
+ # end
11
+ #
12
+ # draft = DraftStore.where(channel_id: 1)
13
+ # draft.value = "hello"
14
+ # draft.value # => "hello"
15
+ # draft.delete
16
+ class Singleton < Store
17
+ def self.scope_class
18
+ Funicular::Store::Singleton::Scope
19
+ end
20
+
21
+ class Scope < Funicular::Store::Scope
22
+ def value
23
+ rec = read
24
+ return nil unless rec.is_a?(Hash)
25
+ if expired_record?(rec)
26
+ erase
27
+ return nil
28
+ end
29
+ rec["v"]
30
+ end
31
+
32
+ # Setting "" on a String-typed value deletes the entry, matching
33
+ # the semantics of the original DraftStore.
34
+ def value=(v)
35
+ if v.is_a?(String) && v.empty?
36
+ delete
37
+ return v
38
+ end
39
+ write(v)
40
+ fire_change(v)
41
+ v
42
+ end
43
+
44
+ def delete
45
+ erase
46
+ fire_change(nil)
47
+ nil
48
+ end
49
+
50
+ def present?
51
+ !value.nil?
52
+ end
53
+
54
+ def expired?
55
+ rec = read
56
+ expired_record?(rec)
57
+ end
58
+
59
+ private
60
+
61
+ def read
62
+ kvs[storage_key]
63
+ end
64
+
65
+ def write(v)
66
+ kvs[storage_key] = {
67
+ "v" => v,
68
+ "wrote_at" => now_seconds,
69
+ "expires_in" => @store_class.__expires_in
70
+ }
71
+ end
72
+
73
+ def erase
74
+ kvs.delete(storage_key)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
data/mrblib/vdom.rb CHANGED
@@ -132,8 +132,10 @@ module Funicular
132
132
  # Handle boolean attributes
133
133
  if value.nil? || value.to_s == "false"
134
134
  # Do not set attribute (leave it absent)
135
+ dom_node[key_str] = false
135
136
  else
136
137
  dom_node.setAttribute(key_str, key_str)
138
+ dom_node[key_str] = true
137
139
  end
138
140
  else
139
141
  # Attribute
data/sig/cable.rbs CHANGED
@@ -41,6 +41,7 @@ module Funicular
41
41
  def notify_subscription_confirmed: (String identifier) -> void
42
42
  def notify_subscription_rejected: (String identifier) -> void
43
43
  def notify_message: (String identifier, untyped message) -> void
44
+ def resubscribe_all: () -> void
44
45
  end
45
46
 
46
47
  class Subscription
data/sig/component.rbs CHANGED
@@ -12,9 +12,9 @@ module Funicular
12
12
 
13
13
  attr_accessor props: Hash[Symbol, untyped]
14
14
  attr_accessor vdom: VDOM::VNode | VDOM::Text | nil
15
- attr_accessor dom_element: JS::Object
15
+ attr_accessor dom_element: JS::Element
16
16
  attr_accessor mounted: bool
17
- attr_reader refs: Hash[Symbol, JS::Object]
17
+ attr_reader refs: Hash[Symbol, JS::Element]
18
18
  @event_listeners: Array[Integer]
19
19
  @child_components: Array[Component]
20
20
  @suspense_data: Hash[Symbol, untyped]
@@ -53,10 +53,12 @@ module Funicular
53
53
  def suspense: (fallback: ^() -> void, ?error: ^(untyped) -> void) { () -> void } -> void
54
54
 
55
55
  def patch: (Hash[Symbol, untyped] new_state) -> void
56
- def mount: (JS::Object container) -> void
56
+ def mount: (JS::Element container) -> void
57
+ def hydrate: (JS::Element dom_element) -> void
58
+ def seed_state: (Hash[untyped, untyped]? state_hash) -> self
57
59
  def unmount: () -> void
58
60
  def render: () -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
59
- def bind_events: (JS::Object dom_element, VDOM::VNode | VDOM::Text | nil vnode) -> void
61
+ def bind_events: (JS::Element dom_element, VDOM::VNode | VDOM::Text | nil vnode) -> void
60
62
  def build_vdom: () -> (VDOM::VNode | VDOM::Text | nil)
61
63
 
62
64
  # Lifecycle hooks (public, can be overridden in subclasses)
@@ -91,13 +93,19 @@ module Funicular
91
93
  private def re_render: () -> void
92
94
  private def normalize_vnode: (untyped value) -> (VDOM::Element | VDOM::Text | VDOM::Component | nil)
93
95
  private def add_data_component_attribute: (VDOM::VNode vnode) -> void
94
- private def collect_refs: (JS::Object dom_element, VDOM::VNode | VDOM::Text | nil vnode, ?Hash[Symbol, JS::Object] refs_map) -> Hash[Symbol, JS::Object]
96
+ private def collect_refs: (JS::Element dom_element, VDOM::VNode | VDOM::Text | nil vnode, ?Hash[Symbol, JS::Element] refs_map) -> Hash[Symbol, JS::Element]
95
97
  private def cleanup_events: () -> void
96
98
  private def cleanup_suspense_timers: () -> void
97
99
  private def add_child: (untyped child) -> void
98
100
  private def collect_child_components: (VDOM::VNode | VDOM::Text | nil vnode) -> void
99
101
  private def collect_child_components_recursive: (VDOM::VNode | VDOM::Text | nil vnode, Array[Component] components) -> void
100
102
 
103
+ # Hydration helpers
104
+ private def hydration_match?: (untyped vnode, untyped dom_element) -> bool
105
+ private def warn_hydration_mismatch: (untyped vnode, untyped dom_element) -> void
106
+ private def full_render_fallback: (VDOM::VNode | VDOM::Text | nil new_vdom, untyped server_dom) -> JS::Element
107
+ private def hydrate_child_components: (untyped vnode, untyped dom_element) -> void
108
+
101
109
  # HTML element methods (DSL)
102
110
  def div: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
103
111
  def span: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
data/sig/funicular.rbs CHANGED
@@ -1,13 +1,26 @@
1
1
  module Funicular
2
2
  VERSION: String
3
3
 
4
+ self.@server: bool
5
+ self.@form_builder_config: Hash[Symbol, String]?
6
+
4
7
  def self.version: () -> String
5
8
  def self.env: () -> EnvironmentInquirer
6
9
  def self.env=: (EnvironmentInquirer | String environment) -> EnvironmentInquirer?
7
10
  def self.router: () -> Router?
11
+
12
+ # SSR / hydration support
13
+ def self.server?: () -> bool
14
+ def self.server=: (untyped value) -> bool
15
+ def self.window_state: () -> Hash[String, untyped]
16
+ def self.has_ssr_state?: () -> bool
17
+ def self.first_element_child: (JS::Element container_element) -> JS::Element?
18
+
8
19
  def self.load_schemas: (Hash[singleton(Model), String] models) ?{ () -> void } -> void
9
- def self.start: (?singleton(Component)? component_class, ?container: String | JS::Object, ?props: Hash[Symbol, untyped]) ?{ (Router router) -> void } -> (Component | Router)
20
+ def self.start: (?singleton(Component)? component_class, ?container: String | JS::Element, ?props: Hash[Symbol, untyped], ?hydrate: bool) ?{ (Router router) -> void } -> (Component | Router | nil)
10
21
  def self.configure_forms: () ?{ (Hash[Symbol, String]) -> void } -> void
22
+ def self.form_builder_config: () -> Hash[Symbol, String]?
23
+ def self.form_builder_config=: (Hash[Symbol, String] config) -> Hash[Symbol, String]
11
24
  def self.configure_debug: () ?{ (self) -> void } -> void
12
25
  def self.export_debug_config: () -> void
13
26
 
@@ -0,0 +1,20 @@
1
+ module Funicular
2
+ module VDOM
3
+ class HTMLSerializer
4
+ VOID_ELEMENTS: Array[String]
5
+ SKIP_PROPS: Array[Symbol]
6
+
7
+ def self.serialize: (VNode? vnode) -> String
8
+ def render: (VNode? vnode) -> String
9
+
10
+ private
11
+ def render_element: (Element element) -> String
12
+ def render_children: (Array[child_t] children) -> String
13
+ def render_text: (Text text) -> String
14
+ def render_component: (Component component_vnode) -> String
15
+ def serialize_props: (Hash[Symbol, untyped] props) -> String
16
+ def escape_html: (untyped str) -> String
17
+ def escape_attr: (untyped str) -> String
18
+ end
19
+ end
20
+ end
data/sig/http.rbs CHANGED
@@ -1,5 +1,10 @@
1
1
  module Funicular
2
2
  module HTTP
3
+ CACHE_DB_NAME: String
4
+ CACHE_STORE: String
5
+
6
+ self.@cache: IndexedDB::KVS?
7
+
3
8
  class Response
4
9
  attr_reader data: untyped
5
10
  attr_reader status: Integer
@@ -10,13 +15,23 @@ module Funicular
10
15
  def error_message: () -> String?
11
16
  end
12
17
 
13
- def self.get: (String url) { (Response) -> void } -> void
14
- def self.post: (String url, ?Hash[untyped, untyped]? body) { (Response) -> void } -> void
15
- def self.patch: (String url, ?Hash[untyped, untyped]? body) { (Response) -> void } -> void
16
- def self.delete: (String url) { (Response) -> void } -> void
17
- def self.put: (String url, ?Hash[untyped, untyped]? body) { (Response) -> void } -> void
18
+ def self.cache_init!: () -> IndexedDB::KVS
19
+ def self.cache_purge: (String url) -> nil
20
+ def self.cache_clear: () -> nil
21
+ def self.cache_lookup: (String url) -> untyped
22
+ def self.cache_write: (String url, Hash[String, untyped] entry) -> nil
23
+
24
+ def self.get: (String url, ?cache: Integer?) { (Response) -> void } -> void
25
+ def self.post: (String url, ?Hash[untyped, untyped]? body, ?cache: Integer?) { (Response) -> void } -> void
26
+ def self.patch: (String url, ?Hash[untyped, untyped]? body, ?cache: Integer?) { (Response) -> void } -> void
27
+ def self.delete: (String url, ?cache: Integer?) { (Response) -> void } -> void
28
+ def self.put: (String url, ?Hash[untyped, untyped]? body, ?cache: Integer?) { (Response) -> void } -> void
18
29
  def self.csrf_token: () -> String?
19
30
 
20
- private def self.request: (String method, String url, Hash[untyped, untyped]? body) { (Response) -> void } -> void
31
+ private def self.warn_unsupported_cache: (String verb) -> void
32
+ private def self.now_seconds: () -> Integer
33
+ private def self.cache_hit?: (untyped entry, Integer ttl) -> bool
34
+ private def self.serve_from_cache: (Hash[String, untyped] entry) { (Response) -> void } -> void
35
+ private def self.request: (String method, String url, Hash[untyped, untyped]? body, ?cache: Integer?) { (Response) -> void } -> void
21
36
  end
22
37
  end
data/sig/model.rbs CHANGED
@@ -1,5 +1,8 @@
1
1
  module Funicular
2
2
  class Model
3
+ include Funicular::Model::Validations
4
+ extend Funicular::Model::Validations::ClassMethods
5
+
3
6
  attr_reader id: untyped
4
7
  @changed_attributes: Hash[String, untyped]
5
8
 
@@ -10,10 +13,12 @@ module Funicular
10
13
 
11
14
  def initialize: (?Hash[untyped, untyped] attributes) -> void
12
15
  def self.load_schema: (Hash[String, untyped] schema_data) -> void
16
+ def self.register_schema_validations: (untyped validations) -> void
17
+ def self.normalize_validation_options: (Symbol | String kind, untyped opts) -> untyped
13
18
 
14
19
  def self.all: (?Hash[untyped, untyped] params) ?{ (Array[Model]? instances, String? error) -> void } -> void
15
20
  def self.find: (?untyped id, ?endpoint_name: String, ?model_class: singleton(Model)) ?{ (Model? instance, String? error) -> void } -> void
16
- def self.create: (Hash[untyped, untyped] attrs, ?model_class: singleton(Model)) ?{ (Model? instance, String? error) -> void } -> void
21
+ def self.create: (Hash[untyped, untyped] attrs, ?model_class: singleton(Model)) ?{ (Model? instance, untyped error) -> void } -> void
17
22
  def self.destroy: (?untyped id) ?{ (bool success, untyped result) -> void } -> void
18
23
 
19
24
  def update: (?Hash[untyped, untyped]? attrs) ?{ (bool success, untyped result) -> void } -> void
data/sig/patcher.rbs CHANGED
@@ -3,9 +3,12 @@ module Funicular
3
3
  BOOLEAN_ATTRIBUTES: Array[String]
4
4
 
5
5
  class Patcher
6
- def initialize: (?JS::Object? doc) -> void
6
+ def initialize: (?JS::Element? doc) -> void
7
+ # `apply` accepts any DOM node so text-node patches (e.g. :replace)
8
+ # can recurse into it; Element-only operations are narrowed inside.
7
9
  def apply: (JS::Object element, Array[patch_t] patches) -> JS::Object
8
10
 
11
+ # Accepts JS::Object since callers narrow via is_a?(JS::Element) at runtime
9
12
  private def update_props: (JS::Object element, Hash[Symbol, String?] props_patch) -> void
10
13
  private def create_element: (untyped vnode) -> JS::Object
11
14
  private def unmount_component: (VDOM::VNode vnode) -> void
data/sig/router.rbs CHANGED
@@ -19,7 +19,7 @@ module Funicular
19
19
  attr_reader current_component: Component?
20
20
  attr_reader current_path: String?
21
21
 
22
- def initialize: (JS::Object container) -> void
22
+ def initialize: (JS::Element? container) -> void
23
23
  def get: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
24
24
  def post: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
25
25
  def put: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
@@ -27,7 +27,8 @@ module Funicular
27
27
  def delete: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
28
28
  def add_route: (String path, singleton(Component) component_class, ?as: String?, ?constraints: route_constraints_t?) -> void
29
29
  def set_default: (String path) -> void
30
- def start: () -> void
30
+ def match: (String path) -> [singleton(Component)?, Hash[Symbol, untyped]]
31
+ def start: (?hydrate: bool) -> void
31
32
  def stop: () -> void
32
33
  def navigate: (String path) -> void
33
34
  def current_hash_path: () -> String
data/sig/store.rbs ADDED
@@ -0,0 +1,89 @@
1
+ module Funicular
2
+ class Store
3
+ EVENT_REGISTRY: Hash[Symbol, Array[singleton(Funicular::Store)]]
4
+ KVS_POOL: Hash[Array[String], IndexedDB::KVS]
5
+
6
+ class SubscribesTo < Data
7
+ attr_reader channel_name: String
8
+ attr_reader params_proc: ^(Funicular::Store::Scope) -> Hash[Symbol, untyped]
9
+ attr_reader handler_block: Proc
10
+
11
+ def self.new: (String channel_name, ^(Funicular::Store::Scope) -> Hash[Symbol, untyped] params_proc, Proc handler_block) -> instance
12
+ end
13
+
14
+ class Subscription
15
+ attr_reader cable_sub: Funicular::Cable::Subscription
16
+
17
+ def initialize: (Funicular::Cable::Subscription cable_sub) -> void
18
+ def unsubscribe: () -> nil
19
+ end
20
+
21
+ class Scope
22
+ attr_reader store_class: singleton(Funicular::Store)
23
+ attr_reader scope_kwargs: Hash[Symbol, untyped]
24
+
25
+ @on_change: Hash[Integer, Proc]
26
+ @next_cb_id: Integer
27
+ @subscription: Funicular::Store::Subscription?
28
+
29
+ def initialize: (singleton(Funicular::Store) store_class, Hash[Symbol, untyped] scope_kwargs) -> void
30
+ def method_missing: (Symbol name, *untyped args) -> untyped
31
+ def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
32
+ def on_change: () { (untyped) -> void } -> Integer
33
+ def off_change: (Integer id) -> nil
34
+ def subscribed?: () -> bool
35
+ def subscription: () -> Funicular::Store::Subscription?
36
+ def subscribe!: () -> Funicular::Store::Subscription
37
+ def unsubscribe!: () -> nil
38
+
39
+ private def storage_key: () -> String
40
+ private def kvs: () -> IndexedDB::KVS
41
+ private def now_seconds: () -> Integer
42
+ private def expired_record?: (untyped rec) -> bool
43
+ private def fire_change: (untyped snapshot) -> void
44
+ end
45
+
46
+ self.@__database: String?
47
+ self.@__kvs_store_name: String?
48
+ self.@__scope_keys: Array[Symbol]?
49
+ self.@__expires_in: Integer?
50
+ self.@__source: untyped
51
+ self.@__belongs_to: Symbol?
52
+ self.@__cable_url: String?
53
+ self.@__cable_binding: SubscribesTo?
54
+ self.@__cleared_handlers: Hash[Symbol, Proc?]?
55
+ self.@__scope_pool: Hash[Hash[Symbol, untyped], Funicular::Store::Scope]?
56
+ self.@__consumer: Funicular::Cable::Consumer?
57
+
58
+ def self.database: (String name) -> String
59
+ def self.kvs_store: (String name) -> String
60
+ def self.scope: (*Symbol keys) -> Array[Symbol]
61
+ def self.expires_in: (Integer seconds) -> Integer
62
+ def self.source: (untyped model_class) -> untyped
63
+ def self.belongs_to: (Symbol name) -> Symbol
64
+ def self.cable_url: (String url) -> String
65
+ def self.subscribes_to: (String channel_name, params: ^(Funicular::Store::Scope) -> Hash[Symbol, untyped]) { (untyped, Funicular::Store::Scope) -> void } -> SubscribesTo
66
+ def self.cleared_on: (*Symbol event_names) ?{ (untyped) -> void } -> void
67
+
68
+ def self.where: (**untyped scope_kwargs) -> Funicular::Store::Scope
69
+ def self.scope_class: () -> singleton(Funicular::Store::Scope)
70
+
71
+ def self.__database: () -> String?
72
+ def self.__kvs_store_name: () -> String?
73
+ def self.__scope_keys: () -> Array[Symbol]?
74
+ def self.__expires_in: () -> Integer?
75
+ def self.__source: () -> untyped
76
+ def self.__belongs_to: () -> Symbol?
77
+ def self.__cable_url: () -> String?
78
+ def self.__cable_binding: () -> SubscribesTo?
79
+ def self.__cleared_handlers: () -> Hash[Symbol, Proc?]?
80
+ def self.__kvs: () -> IndexedDB::KVS
81
+ def self.__consumer: () -> Funicular::Cable::Consumer
82
+ def self.__handle_dispatch: (Symbol event, untyped payload) -> void
83
+ def self.__clear_all!: () -> nil
84
+
85
+ private def self.validate_scope_kwargs!: (Hash[Symbol, untyped] scope_kwargs) -> void
86
+
87
+ def self.dispatch: (Symbol | String event, ?untyped payload) -> nil
88
+ end
89
+ end
@@ -0,0 +1,43 @@
1
+ module Funicular
2
+ class Store
3
+ class Collection < Funicular::Store
4
+ DEFAULT_KEY_PROC: ^(untyped) -> untyped
5
+
6
+ self.@__limit: Integer?
7
+ self.@__order: Symbol?
8
+ self.@__key_proc: ^(untyped) -> untyped | nil
9
+
10
+ def self.limit: (Integer n) -> Integer
11
+ def self.order: (Symbol direction) -> Symbol
12
+ def self.key: (^(untyped) -> untyped proc) -> (^(untyped) -> untyped)
13
+ def self.scope_class: () -> singleton(Funicular::Store::Collection::Scope)
14
+
15
+ def self.__limit: () -> Integer?
16
+ def self.__order: () -> Symbol?
17
+ def self.__key_proc: () -> (^(untyped) -> untyped | nil)
18
+
19
+ class Scope < Funicular::Store::Scope
20
+ attr_reader store_class: singleton(Funicular::Store::Collection)
21
+ @store_class: singleton(Funicular::Store::Collection)
22
+
23
+ def all: () -> Array[untyped]
24
+ def replace: (untyped arr) -> Array[untyped]
25
+ def append: (untyped item) -> Array[untyped]
26
+ def remove: (untyped id) -> Array[untyped]
27
+ def last: () -> untyped
28
+ def last_id: () -> untyped
29
+ def size: () -> Integer
30
+ def clear: () -> nil
31
+ def expired?: () -> bool
32
+ def same_tail?: (untyped other) -> bool
33
+
34
+ private def key_proc: () -> ^(untyped) -> untyped
35
+ private def cap: (Array[untyped] arr) -> Array[untyped]
36
+ private def append_to: (Array[untyped] arr, untyped item) -> Array[untyped]
37
+ private def read: () -> untyped
38
+ private def write: (Array[untyped] items) -> Hash[String, untyped]
39
+ private def erase: () -> nil
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Funicular
2
+ class Store
3
+ class Singleton < Funicular::Store
4
+ def self.scope_class: () -> singleton(Funicular::Store::Singleton::Scope)
5
+
6
+ class Scope < Funicular::Store::Scope
7
+ def value: () -> untyped
8
+ def value=: (untyped v) -> untyped
9
+ def delete: () -> nil
10
+ def present?: () -> bool
11
+ def expired?: () -> bool
12
+
13
+ private def read: () -> untyped
14
+ private def write: (untyped v) -> Hash[String, untyped]
15
+ private def erase: () -> nil
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,103 @@
1
+ module Funicular
2
+ class Model
3
+ # Validation error collection (a small slice of ActiveModel::Errors).
4
+ class Errors
5
+ @messages: Hash[Symbol, Array[String]]
6
+
7
+ def initialize: () -> void
8
+ def add: (Symbol | String attribute, String message) -> String
9
+ def []: (Symbol | String attribute) -> Array[String]
10
+ def added?: (Symbol | String attribute) -> bool
11
+ def messages: () -> Hash[Symbol, Array[String]]
12
+ def full_messages: () -> Array[String]
13
+ def clear: () -> void
14
+ def empty?: () -> bool
15
+ def any?: () -> bool
16
+
17
+ private
18
+ def humanize: (Symbol | String attribute) -> String
19
+ end
20
+
21
+ # ActiveModel-style validation, mixed into Model.
22
+ module Validations
23
+ def self.included: (untyped base) -> void
24
+
25
+ # Base class for attribute validators.
26
+ class EachValidator
27
+ @attributes: Array[Symbol]
28
+ @options: Hash[Symbol, untyped]
29
+
30
+ attr_reader attributes: Array[Symbol]
31
+ attr_reader options: Hash[Symbol, untyped]
32
+
33
+ def initialize: (attributes: Array[Symbol], **untyped options) -> void
34
+ def kind: () -> Symbol
35
+ def validate: (untyped record) -> void
36
+ def validate_each: (untyped record, Symbol attribute, untyped value) -> void
37
+
38
+ private
39
+ def blank?: (untyped value) -> bool
40
+ def present?: (untyped value) -> bool
41
+ end
42
+
43
+ module ClassMethods
44
+ @validators: Array[EachValidator]
45
+
46
+ SHARED_OPTIONS: Array[Symbol]
47
+
48
+ def validators: () -> Array[EachValidator]
49
+ def validators_on: (Symbol | String attribute) -> Array[EachValidator]
50
+ def validates: (*Symbol | String attributes, **untyped options) -> void
51
+ def add_schema_validator: (Symbol | String attribute, Symbol | String kind, untyped opts) -> void
52
+
53
+ private
54
+ def validator_class_for: (Symbol | String key) -> singleton(EachValidator)?
55
+ def symbolize_keys: (untyped hash) -> Hash[Symbol, untyped]
56
+ end
57
+
58
+ # Instance API (mixed into Model).
59
+ @errors: Errors
60
+ def errors: () -> Errors
61
+ def read_attribute_for_validation: (Symbol attribute) -> untyped
62
+ def valid?: () -> bool
63
+ def invalid?: () -> bool
64
+
65
+ # --- concrete validators ---
66
+
67
+ class PresenceValidator < EachValidator
68
+ end
69
+
70
+ class AbsenceValidator < EachValidator
71
+ end
72
+
73
+ class LengthValidator < EachValidator
74
+ private
75
+ def range_include?: (untyped range, Integer length) -> bool
76
+ end
77
+
78
+ class FormatValidator < EachValidator
79
+ end
80
+
81
+ class NumericalityValidator < EachValidator
82
+ CHECKS: Hash[Symbol, String]
83
+
84
+ private
85
+ def compare: (Numeric number, Symbol option, untyped target) -> bool
86
+ def message_for: (Symbol option, untyped target) -> String
87
+ def to_number: (untyped value) -> (Numeric | nil)
88
+ end
89
+
90
+ class InclusionValidator < EachValidator
91
+ end
92
+
93
+ class ExclusionValidator < EachValidator
94
+ end
95
+
96
+ class AcceptanceValidator < EachValidator
97
+ end
98
+
99
+ class ConfirmationValidator < EachValidator
100
+ end
101
+ end
102
+ end
103
+ end
data/sig/vdom.rbs CHANGED
@@ -32,18 +32,18 @@ module Funicular
32
32
  class Renderer
33
33
  @error_boundary_stack: Array[Funicular::ErrorBoundary]
34
34
 
35
- def initialize: (?JS::Object? doc) -> void
36
- def render: (VDOM::VNode | VDOM::Text | nil vnode, ?JS::Object? parent) -> JS::Object
35
+ def initialize: (?JS::Element? doc) -> void
36
+ def render: (VDOM::VNode | VDOM::Text | nil vnode, ?JS::Element? parent) -> JS::Element
37
37
 
38
38
  private def current_error_boundary: () -> ErrorBoundary?
39
- private def render_element: (Element element, JS::Object? parent) -> JS::Object
40
- private def render_text: (VDOM::Text text, JS::Object? parent) -> JS::Object
41
- private def render_component: (VDOM::Component component_vnode, JS::Object? parent) -> JS::Object
39
+ private def render_element: (Element element, JS::Element? parent) -> JS::Element
40
+ private def render_text: (VDOM::Text text, JS::Element? parent) -> JS::Element
41
+ private def render_component: (VDOM::Component component_vnode, JS::Element? parent) -> JS::Element
42
42
  end
43
43
 
44
44
  def self.create_element: (String tag, ?Hash[Symbol, untyped] props, *child_t children) -> Element
45
45
  def self.create_text: (String content) -> Text
46
- def self.render: (VNode vnode, JS::Object container) -> void
46
+ def self.render: (VNode vnode, JS::Element container) -> void
47
47
  def self.diff: (VNode? old_vnode, VNode? new_vnode) -> Array[patch_t]
48
48
  def self.patch: (JS::Object element, Array[patch_t] patches) -> JS::Object
49
49