funicular 0.0.1 → 0.2.0

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/README.md +66 -20
  4. data/Rakefile +103 -2
  5. data/demo/keymap_editor.html +582 -0
  6. data/demo/test_cable.html +179 -0
  7. data/demo/test_chartjs.html +235 -0
  8. data/demo/test_component.html +201 -0
  9. data/demo/test_diff_patch.html +146 -0
  10. data/demo/test_error_boundary.html +284 -0
  11. data/demo/test_router.html +257 -0
  12. data/demo/test_vdom.html +100 -0
  13. data/demo/tic-tac-toe.html +201 -0
  14. data/docs/architecture.md +118 -0
  15. data/exe/funicular +32 -0
  16. data/lib/funicular/assets/funicular.css +23 -0
  17. data/lib/funicular/assets/funicular.rb +21 -0
  18. data/lib/funicular/assets/funicular_debug.css +73 -0
  19. data/lib/funicular/assets/funicular_debug.js +183 -0
  20. data/lib/funicular/commands/routes.rb +69 -0
  21. data/lib/funicular/compiler.rb +143 -0
  22. data/lib/funicular/configuration.rb +76 -0
  23. data/lib/funicular/helpers/picoruby_helper.rb +112 -0
  24. data/lib/funicular/middleware.rb +123 -0
  25. data/lib/funicular/plugin.rb +147 -0
  26. data/lib/funicular/railtie.rb +26 -0
  27. data/lib/funicular/route_parser.rb +137 -0
  28. data/lib/funicular/schema.rb +167 -0
  29. data/lib/funicular/ssr/runtime.rb +101 -0
  30. data/lib/funicular/ssr.rb +51 -0
  31. data/lib/funicular/testing/node_runner.mjs +293 -0
  32. data/lib/funicular/testing/node_runner.rb +190 -0
  33. data/lib/funicular/testing.rb +22 -0
  34. data/lib/funicular/vendor/picorbc/VERSION +1 -0
  35. data/lib/funicular/vendor/picorbc/picorbc.js +5283 -0
  36. data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
  37. data/lib/funicular/vendor/picoruby/VERSION +1 -0
  38. data/lib/funicular/vendor/picoruby/debug/init.iife.js +130 -0
  39. data/lib/funicular/vendor/picoruby/debug/picoruby.js +6423 -0
  40. data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
  41. data/lib/funicular/vendor/picoruby/dist/init.iife.js +130 -0
  42. data/lib/funicular/vendor/picoruby/dist/picoruby.js +2 -0
  43. data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
  44. data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -0
  45. data/lib/funicular/vendor/picoruby-test-node/picoruby.js +6885 -0
  46. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
  47. data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -0
  48. data/lib/funicular/version.rb +1 -1
  49. data/lib/funicular.rb +32 -1
  50. data/lib/generators/funicular/chat/chat_generator.rb +104 -0
  51. data/lib/generators/funicular/chat/templates/application_cable_channel.rb.tt +4 -0
  52. data/lib/generators/funicular/chat/templates/application_cable_connection.rb.tt +4 -0
  53. data/lib/generators/funicular/chat/templates/create_funicular_chat_messages.rb.tt +10 -0
  54. data/lib/generators/funicular/chat/templates/funicular_chat.css.tt +141 -0
  55. data/lib/generators/funicular/chat/templates/funicular_chat_channel.rb.tt +5 -0
  56. data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +135 -0
  57. data/lib/generators/funicular/chat/templates/funicular_chat_component_picotest.rb.tt +64 -0
  58. data/lib/generators/funicular/chat/templates/funicular_chat_controller.rb.tt +4 -0
  59. data/lib/generators/funicular/chat/templates/funicular_chat_message.rb.tt +13 -0
  60. data/lib/generators/funicular/chat/templates/funicular_chat_messages_controller.rb.tt +23 -0
  61. data/lib/generators/funicular/chat/templates/initializer.rb.tt +4 -0
  62. data/lib/generators/funicular/chat/templates/show.html.erb.tt +6 -0
  63. data/lib/tasks/funicular.rake +218 -0
  64. data/minitest/fixtures/funicular_app/components/greeting_component.rb +16 -0
  65. data/minitest/fixtures/funicular_app/initializer.rb +5 -0
  66. data/minitest/funicular_test.rb +13 -0
  67. data/minitest/hydration_test.rb +87 -0
  68. data/minitest/plugin_test.rb +51 -0
  69. data/minitest/schema_test.rb +106 -0
  70. data/minitest/ssr_test.rb +94 -0
  71. data/minitest/test_helper.rb +7 -0
  72. data/minitest/validations_test.rb +183 -0
  73. data/mrbgem.rake +16 -0
  74. data/mrblib/0_validations.rb +206 -0
  75. data/mrblib/1_validators.rb +180 -0
  76. data/mrblib/cable.rb +432 -0
  77. data/mrblib/component.rb +1050 -0
  78. data/mrblib/debug.rb +208 -0
  79. data/mrblib/differ.rb +254 -0
  80. data/mrblib/environment_inquirer.rb +34 -0
  81. data/mrblib/error_boundary.rb +125 -0
  82. data/mrblib/file_upload.rb +192 -0
  83. data/mrblib/form_builder.rb +300 -0
  84. data/mrblib/funicular.rb +245 -0
  85. data/mrblib/html_serializer.rb +121 -0
  86. data/mrblib/http.rb +183 -0
  87. data/mrblib/model.rb +196 -0
  88. data/mrblib/patcher.rb +269 -0
  89. data/mrblib/router.rb +266 -0
  90. data/mrblib/store.rb +304 -0
  91. data/mrblib/store_collection.rb +171 -0
  92. data/mrblib/store_singleton.rb +79 -0
  93. data/mrblib/styles.rb +83 -0
  94. data/mrblib/vdom.rb +273 -0
  95. data/sig/cable.rbs +66 -0
  96. data/sig/component.rbs +149 -0
  97. data/sig/debug.rbs +28 -0
  98. data/sig/differ.rbs +18 -0
  99. data/sig/environment_iquirer.rbs +10 -0
  100. data/sig/error_boundary.rbs +14 -0
  101. data/sig/file_upload.rbs +18 -0
  102. data/sig/form_builder.rbs +29 -0
  103. data/sig/funicular.rbs +24 -1
  104. data/sig/html_serializer.rbs +20 -0
  105. data/sig/http.rbs +37 -0
  106. data/sig/model.rbs +28 -0
  107. data/sig/patcher.rbs +18 -0
  108. data/sig/router.rbs +44 -0
  109. data/sig/store.rbs +89 -0
  110. data/sig/store_collection.rbs +43 -0
  111. data/sig/store_singleton.rbs +19 -0
  112. data/sig/styles.rbs +25 -0
  113. data/sig/validations.rbs +103 -0
  114. data/sig/vdom.rbs +59 -0
  115. metadata +154 -8
data/sig/component.rbs ADDED
@@ -0,0 +1,149 @@
1
+ module Funicular
2
+ class Component
3
+ class StateAccessor
4
+ def initialize: (Hash[Symbol, untyped] state_hash) -> void
5
+ def []: (Symbol key) -> untyped
6
+ def method_missing: (Symbol method, *untyped args) -> untyped
7
+ def respond_to_missing?: (Symbol method, ?bool include_private) -> bool
8
+ def errors: () -> Hash[Symbol, String]
9
+ end
10
+
11
+ HTML_TAGS: Array[String]
12
+
13
+ attr_accessor props: Hash[Symbol, untyped]
14
+ attr_accessor vdom: VDOM::VNode | VDOM::Text | nil
15
+ attr_accessor dom_element: JS::Element
16
+ attr_accessor mounted: bool
17
+ attr_reader refs: Hash[Symbol, JS::Element]
18
+ @event_listeners: Array[Integer]
19
+ @child_components: Array[Component]
20
+ @suspense_data: Hash[Symbol, untyped]
21
+ @suspense_states: Hash[Symbol, Symbol]
22
+ @suspense_errors: Hash[Symbol, untyped]
23
+ @suspense_pending_timers: Array[Integer]
24
+ self.@suspense_definitions: Hash[Symbol, suspense_definition]
25
+
26
+ def initialize: (?Hash[Symbol, untyped] props) -> void
27
+
28
+ # Styles DSL class methods
29
+ def self.styles: () { (StyleBuilder) -> void } -> void
30
+ def self.styles_definitions: () -> Hash[Symbol, Hash[Symbol, untyped]]
31
+
32
+ # Suspense DSL class methods
33
+ # Note: Using untyped for Proc types to avoid instance_exec block type mismatch warnings
34
+ type suspense_definition = { loader: untyped, on_resolve: untyped, min_delay: Integer? }
35
+ def self.use_suspense: (Symbol name, untyped loader, ?on_resolve: untyped, ?min_delay: Integer) -> void
36
+ def self.suspense_definitions: () -> Hash[Symbol, suspense_definition]
37
+
38
+ def state: () -> StateAccessor
39
+
40
+ # Styles DSL instance method
41
+ def s: () -> StyleAccessor
42
+
43
+ # Public API
44
+ def initialize_state: () -> Hash[Symbol, untyped]
45
+
46
+ # Suspense instance methods
47
+ def load_suspense_data: () -> void
48
+ def load_single_suspense: (Symbol name, ?suspense_definition? definition) -> void
49
+ def reload_suspense: (Symbol name) -> void
50
+ def suspense_loading?: (*Symbol names) -> bool
51
+ def suspense_error?: (Symbol name) -> bool
52
+ def suspense_error: (Symbol name) -> untyped
53
+ def suspense: (fallback: ^() -> void, ?error: ^(untyped) -> void) { () -> void } -> void
54
+
55
+ def patch: (Hash[Symbol, untyped] new_state) -> 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
59
+ def unmount: () -> void
60
+ def render: () -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
61
+ def bind_events: (JS::Element dom_element, VDOM::VNode | VDOM::Text | nil vnode) -> void
62
+ def build_vdom: () -> (VDOM::VNode | VDOM::Text | nil)
63
+
64
+ # Lifecycle hooks (public, can be overridden in subclasses)
65
+ def component_will_mount: () -> void
66
+ def component_mounted: () -> void
67
+ def component_will_update: () -> void
68
+ def component_updated: () -> void
69
+ def component_will_unmount: () -> void
70
+ def component_unmounted: () -> void
71
+ def component_raised: (Exception e) -> void
72
+
73
+ # Child component helper
74
+ def component: (Class component_class, ?Hash[Symbol, untyped] props) ?{ () -> untyped } -> VDOM::Component
75
+
76
+ # Rails-style form helper
77
+ def form_for: (Symbol model_key, ?Hash[Symbol, untyped] options) { (FormBuilder) -> void } -> VDOM::Element
78
+
79
+ # Rails-style routing helpers
80
+ def link_to: (String path, ?method: Symbol, ?navigate: bool, **untyped options) { -> untyped } -> VDOM::Element
81
+ def method_missing: (Symbol method, *untyped args) -> untyped
82
+ def respond_to_missing?: (Symbol method, ?bool include_private) -> bool
83
+
84
+ # Transition helpers
85
+ def remove_via: (String element_id, String from, String to, ?duration: Integer) ?{ () -> void } -> void
86
+ def add_via: (String element_id, String from, String to, ?duration: Integer) ?{ () -> void } -> void
87
+
88
+ # Private methods
89
+ private def handle_link_click: (String path) -> void
90
+ private def handle_link_with_method: (String path, Symbol method) -> void
91
+ private def handle_link_response: (HTTP::Response response, String path, Symbol method) -> void
92
+ private def normalize_state_value: (untyped value) -> untyped
93
+ private def re_render: () -> void
94
+ private def normalize_vnode: (untyped value) -> (VDOM::Element | VDOM::Text | VDOM::Component | nil)
95
+ private def add_data_component_attribute: (VDOM::VNode vnode) -> void
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]
97
+ private def cleanup_events: () -> void
98
+ private def cleanup_suspense_timers: () -> void
99
+ private def add_child: (untyped child) -> void
100
+ private def collect_child_components: (VDOM::VNode | VDOM::Text | nil vnode) -> void
101
+ private def collect_child_components_recursive: (VDOM::VNode | VDOM::Text | nil vnode, Array[Component] components) -> void
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
+
109
+ # HTML element methods (DSL)
110
+ def div: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
111
+ def span: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
112
+ def p: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
113
+ def a: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
114
+ def h1: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
115
+ def h2: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
116
+ def h3: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
117
+ def h4: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
118
+ def h5: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
119
+ def h6: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
120
+ def ul: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
121
+ def ol: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
122
+ def li: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
123
+ def table: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
124
+ def thead: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
125
+ def tbody: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
126
+ def tr: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
127
+ def th: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
128
+ def td: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
129
+ def form: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
130
+ def input: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
131
+ def textarea: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
132
+ def button: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
133
+ def select: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
134
+ def option: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
135
+ def label: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
136
+ def header: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
137
+ def footer: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
138
+ def nav: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
139
+ def section: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
140
+ def article: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
141
+ def aside: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
142
+ def img: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
143
+ def video: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
144
+ def audio: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
145
+ def canvas: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
146
+ def br: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
147
+ def hr: (?Hash[Symbol, untyped] props) ?{ -> untyped } -> VDOM::Element
148
+ end
149
+ end
data/sig/debug.rbs ADDED
@@ -0,0 +1,28 @@
1
+ $__funicular_debug__: Funicular::Debug
2
+
3
+ module Funicular
4
+ module Debug
5
+ self.@error_registry: Array[Hash[Symbol, untyped]]
6
+
7
+ def self.enabled?: () -> bool
8
+ def self.component_registry: () -> Hash[Integer, Funicular::Component]
9
+ def self.register_component: (Funicular::Component) -> Integer?
10
+ def self.unregister_component: (Integer) -> void
11
+ def self.get_component: (Integer) -> Funicular::Component?
12
+ def self.all_components: () -> Array[Funicular::Component]
13
+ def self.component_tree: () -> String
14
+ def self.get_component_state: (Integer) -> String
15
+ def self.get_component_instance_variables: (Integer) -> String
16
+ def self.expose_to_global: () -> void
17
+ def self.report_error: (ErrorBoundary boundary, Exception error, ?(Hash[Symbol, Component] | nil) error_info) -> Hash[Symbol, untyped]?
18
+ def self.clear_errors: () -> void
19
+ def self.error_list: () -> String
20
+ def self.last_error: () -> Hash[Symbol, untyped]?
21
+ def self.error_count: () -> Integer
22
+
23
+ private def self.error_registry: () -> Array[Hash[Symbol, untyped]]
24
+ private def self.get_state_keys: (Funicular::Component) -> Array[String]
25
+ private def self.get_child_ids: (Funicular::Component) -> Array[Integer]
26
+ private def self.collect_direct_children: (Funicular::VDOM::VNode, Array[Funicular::Component]) -> void
27
+ end
28
+ end
data/sig/differ.rbs ADDED
@@ -0,0 +1,18 @@
1
+ module Funicular
2
+ module VDOM
3
+ class Differ
4
+ type children_t = Array[child_t]
5
+ def self.diff: (untyped old_node, untyped new_node) -> Array[patch_t]
6
+
7
+ private def self.diff_text: (Text old_node, Text new_node) -> Array[patch_t]
8
+ private def self.diff_element: (Element old_node, Element new_node) -> Array[patch_t]
9
+ private def self.diff_props: (Hash[Symbol, untyped] old_props, Hash[Symbol, untyped] new_props) -> Hash[Symbol, untyped]
10
+ private def self.diff_children: (children_t old_children, children_t new_children) -> Array[patch_t]
11
+ private def self.diff_children_with_keys: (children_t old_children, children_t new_children) -> Array[patch_t]
12
+ private def self.diff_children_by_index: (children_t old_children, children_t new_children) -> Array[patch_t]
13
+ private def self.diff_component: (VDOM::Component old_node, VDOM::Component new_node) -> Array[patch_t]
14
+
15
+ private def self.props_changed_excluding_procs?: (Hash[Symbol, untyped] old_props, Hash[Symbol, untyped] new_props) -> bool
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Funicular
2
+ class EnvironmentInquirer
3
+ def initialize: (EnvironmentInquirer | String env) -> void
4
+ def to_s: () -> String
5
+ def inspect: () -> String
6
+ def ==: (String other) -> bool
7
+ def development?: () -> bool
8
+ end
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ module Funicular
2
+ class ErrorBoundary < Component
3
+ attr_accessor error_caught_during_render: bool?
4
+
5
+ def initialize_state: () -> Hash[Symbol, untyped]
6
+ def catch_error: (Exception error, ?Hash[Symbol, untyped]? error_info) -> bool
7
+ def reset: () -> void
8
+ def render: () -> (VDOM::VNode | String | Integer | Float | Array[untyped] | nil)
9
+
10
+ private def render_fallback: () -> (VDOM::VNode | String)
11
+ private def default_fallback: () -> VDOM::VNode
12
+ private def render_children: () -> VDOM::VNode
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Funicular
2
+ module FileUpload
3
+ self.@callback_counters: Array[Integer]
4
+
5
+ JS_HELPER_CODE: String
6
+
7
+ def self.mount: () -> void
8
+ def self.select_file_with_preview: (String input_id) { (JS::Object? file, String? preview_url) -> void } -> void
9
+ def self.upload_with_formdata: (
10
+ String url,
11
+ ?fields: Hash[untyped, untyped],
12
+ ?file_field: String?,
13
+ ?file: JS::Object?
14
+ ) { (Hash[untyped, untyped] result) -> void } -> void
15
+ def self.store_file: (String input_id, ?String storage_key) -> JS::Object?
16
+ def self.retrieve_file: (?String storage_key) -> JS::Object?
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ module Funicular
2
+ class FormBuilder
3
+ attr_reader component: Component
4
+ attr_reader model_key: Symbol
5
+ attr_reader options: Hash[Symbol, untyped]
6
+
7
+ def initialize: (Component component, Symbol model_key, ?Hash[Symbol, untyped] options) -> void
8
+
9
+ # Field builder methods
10
+ def text_field: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
11
+ def password_field: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
12
+ def email_field: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
13
+ def number_field: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
14
+ def textarea: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
15
+ def checkbox: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
16
+ def select: (Symbol field_name, Array[untyped] choices, ?Hash[Symbol, untyped] options) -> VDOM::Element
17
+ def file_field: (Symbol field_name, ?Hash[Symbol, untyped] options) -> VDOM::Element
18
+ def submit: (?String label, ?Hash[Symbol, untyped] options) -> VDOM::Element
19
+ def label: (Symbol field_name, ?String? text, ?Hash[Symbol, untyped] options) -> VDOM::Element
20
+
21
+ # Generic field builder
22
+ private def build_field: (Symbol field_name, String field_type, Hash[Symbol, untyped] field_options) -> VDOM::Element
23
+
24
+ # Helper methods for nested state access
25
+ private def get_nested_value: (Component::StateAccessor state, String key_path) -> untyped
26
+ private def set_nested_value: (Symbol model_key, String field_key, untyped new_value) -> void
27
+ private def deep_merge_value: (Hash[Symbol, untyped] hash, Array[String] keys, untyped value) -> Hash[Symbol, untyped]
28
+ end
29
+ end
data/sig/funicular.rbs CHANGED
@@ -1,4 +1,27 @@
1
1
  module Funicular
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ self.@server: bool
5
+ self.@form_builder_config: Hash[Symbol, String]?
6
+
7
+ def self.version: () -> String
8
+ def self.env: () -> EnvironmentInquirer
9
+ def self.env=: (EnvironmentInquirer | String environment) -> EnvironmentInquirer?
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
+
19
+ def self.load_schemas: (Hash[singleton(Model), String] models) ?{ () -> void } -> void
20
+ def self.start: (?singleton(Component)? component_class, ?container: String | JS::Element, ?props: Hash[Symbol, untyped], ?hydrate: bool) ?{ (Router router) -> void } -> (Component | Router | nil)
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]
24
+ def self.configure_debug: () ?{ (self) -> void } -> void
25
+ def self.export_debug_config: () -> void
26
+
4
27
  end
@@ -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 ADDED
@@ -0,0 +1,37 @@
1
+ module Funicular
2
+ module HTTP
3
+ CACHE_DB_NAME: String
4
+ CACHE_STORE: String
5
+
6
+ self.@cache: IndexedDB::KVS?
7
+
8
+ class Response
9
+ attr_reader data: untyped
10
+ attr_reader status: Integer
11
+ attr_reader ok: bool
12
+
13
+ def initialize: (Integer status, untyped data) -> void
14
+ def error?: () -> bool
15
+ def error_message: () -> String?
16
+ end
17
+
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
29
+ def self.csrf_token: () -> String?
30
+
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
36
+ end
37
+ end
data/sig/model.rbs ADDED
@@ -0,0 +1,28 @@
1
+ module Funicular
2
+ class Model
3
+ include Funicular::Model::Validations
4
+ extend Funicular::Model::Validations::ClassMethods
5
+
6
+ attr_reader id: untyped
7
+ @changed_attributes: Hash[String, untyped]
8
+
9
+ def self.schema: () -> Hash[String, Hash[String, untyped]]
10
+ def self.schema=: (Hash[String, Hash[String, untyped]] schema) -> Hash[String, Hash[String, untyped]]
11
+ def self.endpoints: () -> Hash[String, Hash[String, String]]
12
+ def self.endpoints=: (Hash[String, Hash[String, String]] endpoints) -> Hash[String, Hash[String, String]]
13
+
14
+ def initialize: (?Hash[untyped, untyped] attributes) -> void
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
18
+
19
+ def self.all: (?Hash[untyped, untyped] params) ?{ (Array[Model]? instances, String? error) -> void } -> void
20
+ def self.find: (?untyped id, ?endpoint_name: String, ?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
22
+ def self.destroy: (?untyped id) ?{ (bool success, untyped result) -> void } -> void
23
+
24
+ def update: (?Hash[untyped, untyped]? attrs) ?{ (bool success, untyped result) -> void } -> void
25
+ def destroy: () ?{ (bool success, untyped result) -> void } -> void
26
+ def reload: () ?{ (Model? instance, String? error) -> void } -> void
27
+ end
28
+ end
data/sig/patcher.rbs ADDED
@@ -0,0 +1,18 @@
1
+ module Funicular
2
+ module VDOM
3
+ BOOLEAN_ATTRIBUTES: Array[String]
4
+
5
+ class Patcher
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.
9
+ def apply: (JS::Object element, Array[patch_t] patches) -> JS::Object
10
+
11
+ # Accepts JS::Object since callers narrow via is_a?(JS::Element) at runtime
12
+ private def update_props: (JS::Object element, Hash[Symbol, String?] props_patch) -> void
13
+ private def create_element: (untyped vnode) -> JS::Object
14
+ private def unmount_component: (VDOM::VNode vnode) -> void
15
+ end
16
+
17
+ end
18
+ end
data/sig/router.rbs ADDED
@@ -0,0 +1,44 @@
1
+ module Funicular
2
+ type route_constraints_t = Hash[Symbol, Regexp]
3
+ type route_definition_t = { method: Symbol, path: String, component: singleton(Component), name: String?, pattern_segments: Array[String], constraints: route_constraints_t }
4
+
5
+ # URL helper methods are dynamically generated based on route definitions
6
+ # Example: router.get('/users/:id', to: UserComponent, as: 'user')
7
+ # generates: user_path(id) -> String
8
+ module RouteHelpers
9
+ # Dynamic methods are generated at runtime by Router#generate_url_helper
10
+ # Method signatures depend on route parameters:
11
+ # - No parameters: def helper_name_path: () -> String
12
+ # - With parameters: def helper_name_path: (Integer | String | untyped) -> String
13
+ def method_missing: (Symbol method, *untyped args) -> String
14
+ def respond_to_missing?: (Symbol method, ?bool include_private) -> bool
15
+ end
16
+
17
+ class Router
18
+ attr_reader routes: Array[route_definition_t]
19
+ attr_reader current_component: Component?
20
+ attr_reader current_path: String?
21
+
22
+ def initialize: (JS::Element? container) -> void
23
+ def get: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
24
+ def post: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
25
+ def put: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
26
+ def patch: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
27
+ def delete: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
28
+ def add_route: (String path, singleton(Component) component_class, ?as: String?, ?constraints: route_constraints_t?) -> void
29
+ def set_default: (String path) -> void
30
+ def match: (String path) -> [singleton(Component)?, Hash[Symbol, untyped]]
31
+ def start: (?hydrate: bool) -> void
32
+ def stop: () -> void
33
+ def navigate: (String path) -> void
34
+ def current_hash_path: () -> String
35
+ def current_location_path: () -> String
36
+
37
+ private def add_route_with_method: (Symbol method, String path, singleton(Component) component_class, String? name, ?route_constraints_t? constraints) -> void
38
+ private def generate_url_helper: (String name, String path_pattern) -> void
39
+ private def extract_param_names: (String path_pattern) -> Array[Symbol]
40
+ private def handle_route_change: () -> void
41
+ private def unmount_current_component: () -> void
42
+ private def find_route: (String path) -> [singleton(Component) | nil, Hash[Symbol, untyped]]
43
+ end
44
+ end
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
data/sig/styles.rbs ADDED
@@ -0,0 +1,25 @@
1
+ module Funicular
2
+ # StyleValue represents a CSS class string that can be combined with other styles
3
+ class StyleValue
4
+ attr_reader value: String
5
+ def initialize: (String | untyped value) -> void
6
+ def |: (StyleValue | String | nil other) -> StyleValue
7
+ def to_s: () -> String
8
+ end
9
+
10
+ # StyleAccessor provides access to defined styles via method calls
11
+ class StyleAccessor
12
+ def initialize: (Hash[Symbol, Hash[Symbol, untyped]] definitions) -> void
13
+ private def method_missing: (Symbol name, *untyped args) -> StyleValue
14
+ private def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
15
+ end
16
+
17
+ # StyleBuilder is used to define styles within the styles {} block
18
+ class StyleBuilder
19
+ @definitions: Hash[Symbol, Hash[Symbol, untyped]]
20
+
21
+ def initialize: () -> void
22
+ def to_definitions: () -> Hash[Symbol, Hash[Symbol, untyped]]
23
+ private def method_missing: (Symbol name, *untyped args) -> void
24
+ end
25
+ end