mayu-live 0.0.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 (204) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +661 -0
  3. data/README.md +598 -0
  4. data/exe/mayu +33 -0
  5. data/lib/mayu/app_metrics.rb +93 -0
  6. data/lib/mayu/banner.rb +12 -0
  7. data/lib/mayu/client/README.md +17 -0
  8. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
  9. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
  10. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
  11. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
  12. data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
  13. data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
  14. data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
  15. data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
  16. data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
  17. data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
  18. data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
  19. data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
  20. data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
  21. data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
  22. data/lib/mayu/client/dist/entries.json +3 -0
  23. data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
  24. data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
  25. data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
  26. data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
  27. data/lib/mayu/client/package.json +39 -0
  28. data/lib/mayu/client/rollup.config.js +81 -0
  29. data/lib/mayu/client/src/DecompressionStream.ts +15 -0
  30. data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
  31. data/lib/mayu/client/src/MimeTypes.ts +4 -0
  32. data/lib/mayu/client/src/NodeTree.ts +445 -0
  33. data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
  34. data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
  35. data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
  36. data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
  37. data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
  38. data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
  39. data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
  40. data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
  41. data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
  42. data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
  43. data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
  44. data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
  45. data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
  46. data/lib/mayu/client/src/global.d.ts +26 -0
  47. data/lib/mayu/client/src/h.ts +27 -0
  48. data/lib/mayu/client/src/logger.ts +56 -0
  49. data/lib/mayu/client/src/main.ts +271 -0
  50. data/lib/mayu/client/src/serializeEvent.ts +90 -0
  51. data/lib/mayu/client/src/stream.ts +175 -0
  52. data/lib/mayu/client/src/types.ts +1 -0
  53. data/lib/mayu/client/src/utils.ts +71 -0
  54. data/lib/mayu/client/tsconfig.json +18 -0
  55. data/lib/mayu/colors.rb +34 -0
  56. data/lib/mayu/commands/base.rb +22 -0
  57. data/lib/mayu/commands/build.rb +82 -0
  58. data/lib/mayu/commands.rb +53 -0
  59. data/lib/mayu/component/base.rb +177 -0
  60. data/lib/mayu/component/handler_ref.rb +99 -0
  61. data/lib/mayu/component/helpers.rb +93 -0
  62. data/lib/mayu/component/interface.rb +18 -0
  63. data/lib/mayu/component/wrapper.rb +165 -0
  64. data/lib/mayu/component.rb +54 -0
  65. data/lib/mayu/configuration.rb +195 -0
  66. data/lib/mayu/disable_sorbet.rb +23 -0
  67. data/lib/mayu/environment.rb +151 -0
  68. data/lib/mayu/event_stream.rb +158 -0
  69. data/lib/mayu/fetch.rb +88 -0
  70. data/lib/mayu/html.rb +53 -0
  71. data/lib/mayu/html.yaml +767 -0
  72. data/lib/mayu/message_cipher.rb +172 -0
  73. data/lib/mayu/message_cipher.test.rb +16 -0
  74. data/lib/mayu/metrics/collector.rb +161 -0
  75. data/lib/mayu/metrics/exporter.rb +47 -0
  76. data/lib/mayu/metrics/reporter.rb +187 -0
  77. data/lib/mayu/metrics.rb +82 -0
  78. data/lib/mayu/ref_counter.rb +57 -0
  79. data/lib/mayu/resources/README.md +14 -0
  80. data/lib/mayu/resources/asset.rb +71 -0
  81. data/lib/mayu/resources/assets.rb +76 -0
  82. data/lib/mayu/resources/dependency_graph.rb +306 -0
  83. data/lib/mayu/resources/dot_exporter.rb +167 -0
  84. data/lib/mayu/resources/generators/base.rb +18 -0
  85. data/lib/mayu/resources/generators/copy_file.rb +26 -0
  86. data/lib/mayu/resources/generators/image.rb +106 -0
  87. data/lib/mayu/resources/generators/write_file.rb +39 -0
  88. data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
  89. data/lib/mayu/resources/hot_swap.rb +46 -0
  90. data/lib/mayu/resources/mermaid_exporter.rb +210 -0
  91. data/lib/mayu/resources/registry.rb +190 -0
  92. data/lib/mayu/resources/resolver/base.rb +32 -0
  93. data/lib/mayu/resources/resolver/filesystem.rb +94 -0
  94. data/lib/mayu/resources/resolver/static.rb +27 -0
  95. data/lib/mayu/resources/resolver.rb +13 -0
  96. data/lib/mayu/resources/resource.rb +150 -0
  97. data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
  98. data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
  99. data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
  100. data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
  101. data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
  102. data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
  103. data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
  104. data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
  105. data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
  106. data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
  107. data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
  108. data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
  109. data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
  110. data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
  111. data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
  112. data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
  113. data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
  114. data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
  115. data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
  116. data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
  117. data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
  118. data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
  119. data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
  120. data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
  121. data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
  122. data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
  123. data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
  124. data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
  125. data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
  126. data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
  127. data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
  128. data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
  129. data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
  130. data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
  131. data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
  132. data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
  133. data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
  134. data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
  135. data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
  136. data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
  137. data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
  138. data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
  139. data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
  140. data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
  141. data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
  142. data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
  143. data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
  144. data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
  145. data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
  146. data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
  147. data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
  148. data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
  149. data/lib/mayu/resources/transformers/css.rb +100 -0
  150. data/lib/mayu/resources/transformers/css.test.rb +87 -0
  151. data/lib/mayu/resources/transformers/haml.rb +984 -0
  152. data/lib/mayu/resources/transformers/haml.test.rb +114 -0
  153. data/lib/mayu/resources/types/README.md +36 -0
  154. data/lib/mayu/resources/types/base.rb +35 -0
  155. data/lib/mayu/resources/types/component.rb +198 -0
  156. data/lib/mayu/resources/types/image.rb +169 -0
  157. data/lib/mayu/resources/types/javascript.rb +50 -0
  158. data/lib/mayu/resources/types/nil.rb +23 -0
  159. data/lib/mayu/resources/types/stylesheet.rb +119 -0
  160. data/lib/mayu/resources/types/svg.rb +69 -0
  161. data/lib/mayu/resources/types.rb +37 -0
  162. data/lib/mayu/routes.rb +170 -0
  163. data/lib/mayu/routing/builder.rb +108 -0
  164. data/lib/mayu/routing/matcher.rb +58 -0
  165. data/lib/mayu/routing/routes.rb +85 -0
  166. data/lib/mayu/routing.rb +17 -0
  167. data/lib/mayu/server/app.rb +494 -0
  168. data/lib/mayu/server/controller.rb +152 -0
  169. data/lib/mayu/server/errors.rb +110 -0
  170. data/lib/mayu/server/file_server.rb +140 -0
  171. data/lib/mayu/server.rb +63 -0
  172. data/lib/mayu/session.rb +358 -0
  173. data/lib/mayu/state/README.md +6 -0
  174. data/lib/mayu/state/action_creator.rb +191 -0
  175. data/lib/mayu/state/action_wrapper.rb +30 -0
  176. data/lib/mayu/state/loader.rb +220 -0
  177. data/lib/mayu/state/store.rb +82 -0
  178. data/lib/mayu/state.rb +8 -0
  179. data/lib/mayu/state.test.rb +97 -0
  180. data/lib/mayu/utils.rb +114 -0
  181. data/lib/mayu/vdom/children.rb +117 -0
  182. data/lib/mayu/vdom/component_marshaler.rb +53 -0
  183. data/lib/mayu/vdom/css_attributes.rb +131 -0
  184. data/lib/mayu/vdom/descriptor.rb +151 -0
  185. data/lib/mayu/vdom/descriptor.test.rb +26 -0
  186. data/lib/mayu/vdom/dom.rb +239 -0
  187. data/lib/mayu/vdom/h.rb +22 -0
  188. data/lib/mayu/vdom/id_generator.rb +55 -0
  189. data/lib/mayu/vdom/interfaces.rb +186 -0
  190. data/lib/mayu/vdom/marshalling.rb +78 -0
  191. data/lib/mayu/vdom/reconciliation.rb +205 -0
  192. data/lib/mayu/vdom/reconciliation.test.rb +56 -0
  193. data/lib/mayu/vdom/special_elements.rb +108 -0
  194. data/lib/mayu/vdom/update_context.rb +180 -0
  195. data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
  196. data/lib/mayu/vdom/vnode.rb +266 -0
  197. data/lib/mayu/vdom/vtree.rb +672 -0
  198. data/lib/mayu/vdom/vtree.test.rb +68 -0
  199. data/lib/mayu/vdom.rb +8 -0
  200. data/lib/mayu/vdom.test.rb +73 -0
  201. data/lib/mayu/version.rb +6 -0
  202. data/lib/mayu.rb +8 -0
  203. data/mayu-live.gemspec +70 -0
  204. metadata +612 -0
@@ -0,0 +1,82 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "base"
5
+ require_relative "../environment"
6
+
7
+ module Mayu
8
+ module Commands
9
+ class Build < Base
10
+ extend T::Sig
11
+
12
+ sig { params(argv: T::Array[String]).void }
13
+ def call(argv)
14
+ require "fileutils"
15
+
16
+ Async do
17
+ started_at = Time.now.to_f
18
+
19
+ metrics = AppMetrics.setup(Prometheus::Client.registry)
20
+ environment = Environment.new(configuration, metrics)
21
+ environment.init_js
22
+ resources = environment.resources
23
+
24
+ components = []
25
+
26
+ components.push(File.join("/app", "root"))
27
+
28
+ environment.routes.each do |route|
29
+ route.layouts.each do |layout|
30
+ components.push(File.join("/app", "pages", layout))
31
+ end
32
+
33
+ components.push(File.join("/app", "pages", route.template))
34
+ end
35
+
36
+ components.each do |component|
37
+ resources.load_resource(component).type.component
38
+ end
39
+
40
+ File.write("app-graph.md", <<~EOF)
41
+ ```mermaid
42
+ #{resources.dependency_graph.to_mermaid_source.chomp}
43
+ ```
44
+ EOF
45
+
46
+ mermaid_url = resources.mermaid_url
47
+
48
+ assets_dir = environment.path(:assets)
49
+ FileUtils.mkdir_p(assets_dir)
50
+ files_to_remove = Dir.glob(File.join(assets_dir, "*"))
51
+
52
+ unless files_to_remove.empty?
53
+ puts "\e[33mRemoving #{files_to_remove.size} files from #{assets_dir}\e[0m"
54
+ FileUtils.rm(files_to_remove)
55
+ end
56
+
57
+ puts "\e[35mGenerating assets\e[0m"
58
+
59
+ resources.generate_assets(
60
+ assets_dir,
61
+ concurrency: Async::Container.processor_count,
62
+ forever: false
63
+ ).wait
64
+
65
+ filename = configuration.paths.bundle_filename
66
+ puts "\e[35mWriting \e[1m#{filename}\e[0m"
67
+ File.write(filename, resources.dump)
68
+
69
+ puts
70
+ puts format(
71
+ "\e[36mBuilt app in \e[1m%.2f seconds\e[0m",
72
+ Time.now.to_f - started_at
73
+ )
74
+
75
+ puts
76
+ puts "View the app graph:"
77
+ puts "\e[34;4m#{resources.mermaid_url}\e[0m"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,53 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "configuration"
5
+ require_relative "commands/base"
6
+ require_relative "colors"
7
+ require_relative "banner"
8
+
9
+ module Mayu
10
+ module Commands
11
+ extend T::Sig
12
+
13
+ sig { params(argv: T::Array[String]).void }
14
+ def self.call(argv)
15
+ puts Colors.rainbow(BANNER)
16
+
17
+ case argv
18
+ in ["dev", *rest]
19
+ require_relative "server"
20
+ Server.start(load_config(:dev))
21
+ in ["devbundle", *rest]
22
+ require_relative "server"
23
+ Server.start(load_config(:devbundle))
24
+ in ["build", *rest]
25
+ require_relative "commands/build"
26
+ Commands::Build.new(
27
+ load_config(
28
+ :prod,
29
+ overrides: {
30
+ "use_bundle" => false,
31
+ "secret_key" => "not important, just needed to avoid an exception"
32
+ }
33
+ )
34
+ ).call(rest)
35
+ in ["serve", *rest]
36
+ require_relative "server"
37
+ Server.start(load_config(:prod))
38
+ else
39
+ puts "Invalid args: #{argv.inspect}"
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ sig do
45
+ params(env: Symbol, overrides: T::Hash[String, T.untyped]).returns(
46
+ Configuration
47
+ )
48
+ end
49
+ def self.load_config(env, overrides: {})
50
+ Mayu::Configuration.load_config(env, pwd: Dir.pwd, overrides:)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,177 @@
1
+ # typed: strict
2
+
3
+ require_relative "handler_ref"
4
+
5
+ module Mayu
6
+ module Component
7
+ class Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ abstract!
11
+
12
+ sig do
13
+ params(
14
+ styles: T::Hash[Symbol, String],
15
+ assets: T::Array[String]
16
+ ).returns(T.class_of(Base))
17
+ end
18
+ def self.setup_component(styles:, assets:)
19
+ T.unsafe(
20
+ class << self
21
+ self
22
+ end
23
+ ).undef_method(T.must(__method__))
24
+
25
+ const_set(
26
+ :MAYU,
27
+ { styles: styles.freeze, assets: assets.freeze }.freeze
28
+ )
29
+
30
+ self
31
+ end
32
+
33
+ sig { overridable.params(props: T.untyped).returns(Component::State) }
34
+ def self.get_initial_state(**props)
35
+ {}
36
+ end
37
+
38
+ class << self
39
+ extend T::Sig
40
+
41
+ sig { void }
42
+ def initialize
43
+ # This will never be called but will make Sorbet happy
44
+ @__mayu_resource = T.let(nil, T.nilable(Resources::Resource))
45
+ end
46
+
47
+ # TODO: Probably better use a WeakMap in Resources for this..
48
+ sig { params(__mayu_resource: Resources::Resource).void }
49
+ attr_writer :__mayu_resource
50
+
51
+ sig { returns(T.nilable(Resources::Resource)) }
52
+ def __mayu_resource
53
+ @__mayu_resource
54
+ end
55
+
56
+ sig { returns(Resources::Resource) }
57
+ def __mayu_resource!
58
+ @__mayu_resource or raise "__mayu_resource is not set"
59
+ end
60
+
61
+ sig { returns(T::Boolean) }
62
+ def __mayu_resource?
63
+ !!@__mayu_resource
64
+ end
65
+ end
66
+
67
+ sig do
68
+ overridable
69
+ .params(props: Component::Props, state: Component::State)
70
+ .returns(T.nilable(Component::State))
71
+ end
72
+ def self.get_derived_state_from_props(props, state)
73
+ nil
74
+ end
75
+
76
+ sig { params(wrapper: Wrapper).void }
77
+ def initialize(wrapper)
78
+ @__wrapper = wrapper
79
+ end
80
+
81
+ sig { returns(State) }
82
+ def state = mayu.state
83
+ sig { returns(Props) }
84
+ def props = mayu.props
85
+ sig { returns(String) }
86
+ def vnode_id = @__wrapper.vnode_id
87
+
88
+ sig { overridable.void }
89
+ def mount
90
+ end
91
+
92
+ sig { overridable.void }
93
+ def unmount
94
+ end
95
+
96
+ sig do
97
+ overridable
98
+ .params(next_props: Component::Props, next_state: Component::State)
99
+ .returns(T::Boolean)
100
+ end
101
+ def should_update?(next_props, next_state)
102
+ case
103
+ when props != next_props
104
+ true
105
+ when state != next_state
106
+ true
107
+ else
108
+ false
109
+ end
110
+ end
111
+
112
+ sig do
113
+ overridable
114
+ .params(prev_props: Component::Props, prev_state: Component::State)
115
+ .void
116
+ end
117
+ def did_update(prev_props, prev_state)
118
+ end
119
+
120
+ INLINE_CSS_ASSETS = T.let([], T::Array[String])
121
+
122
+ sig { returns(T::Array[String]) }
123
+ def self.assets
124
+ [self.stylesheet&.assets, const_get(:INLINE_CSS_ASSETS)].flatten
125
+ .compact
126
+ .map(&:filename)
127
+ end
128
+
129
+ # TODO: Could probably clean this up...
130
+ sig { returns(T.nilable(Resources::Types::Stylesheet)) }
131
+ def self.stylesheet = nil
132
+ sig { returns(Resources::Types::Stylesheet) }
133
+ def self.stylesheet! =
134
+ stylesheet ||
135
+ raise(RuntimeError, "There is no stylesheet for this component!")
136
+ sig { returns(Resources::Types::Stylesheet::ClassNames) }
137
+ def self.styles
138
+ Resources::Types::Stylesheet::ClassNames.new({})
139
+ end
140
+ sig { returns(Resources::Types::Stylesheet::ClassNames) }
141
+ def styles = self.class.styles
142
+
143
+ sig { params(blk: T.proc.bind(T.self_type).void).void }
144
+ def async(&blk) = @__wrapper.async(&blk)
145
+
146
+ sig { abstract.returns(ChildType) }
147
+ def render
148
+ end
149
+
150
+ sig do
151
+ params(name: Symbol, args: T.untyped, kwargs: T.untyped).returns(
152
+ HandlerRef
153
+ )
154
+ end
155
+ def handler(name, *args, **kwargs)
156
+ HandlerRef.new(self, name, args, kwargs)
157
+ end
158
+
159
+ sig { returns(Helpers) }
160
+ def mayu = @__wrapper.helpers
161
+ alias helpers mayu
162
+
163
+ sig do
164
+ params(
165
+ state: T.nilable(State),
166
+ blk: T.nilable(Wrapper::UpdateProc)
167
+ ).void
168
+ end
169
+ def update(state = nil, &blk)
170
+ @__wrapper.update(state, &blk)
171
+ end
172
+
173
+ sig { returns(VDOM::Children) }
174
+ def children = props[:children]
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,99 @@
1
+ # typed: strict
2
+
3
+ require_relative "base"
4
+
5
+ module Mayu
6
+ module Component
7
+ class HandlerRef
8
+ extend T::Sig
9
+
10
+ ID_LENGTH = 16
11
+ ID_FORMAT = /\A[[:graph:]]{#{ID_LENGTH}}\z/
12
+
13
+ sig { returns(String) }
14
+ attr_reader :id
15
+
16
+ sig do
17
+ params(
18
+ component: Base,
19
+ name: Symbol,
20
+ args: T::Array[T.untyped],
21
+ kwargs: T::Hash[Symbol, T.untyped]
22
+ ).void
23
+ end
24
+ def initialize(component, name, args = [], kwargs = {})
25
+ @component = component
26
+ @name = name
27
+ @args = args
28
+ @kwargs = kwargs
29
+ # TODO: Validate that args and kwargs match the method signature.
30
+ method = T.let(component.public_method(name), Method)
31
+ @arity = T.let(method.arity, Integer)
32
+ @id =
33
+ T.let(
34
+ [component.vnode_id, name, @args, @kwargs].inspect
35
+ .then { Digest::SHA256.digest(_1) }
36
+ .then { Base64.urlsafe_encode64(_1) }
37
+ .then { _1[0, ID_LENGTH] },
38
+ String
39
+ )
40
+ end
41
+
42
+ sig { returns(Integer) }
43
+ def hash = [self.class, @id].hash
44
+ sig { params(other: T.untyped).returns(T::Boolean) }
45
+ def eql?(other) = self.class === other && other.id == id
46
+ sig { params(other: T.untyped).returns(T::Boolean) }
47
+ def ==(other) = self.class === other && other.id == id
48
+
49
+ sig { returns(String) }
50
+ def inspect
51
+ "#<HandlerRef vnode_id=%d %s(%s)" %
52
+ [
53
+ @component.vnode_id,
54
+ @name,
55
+ [*@args.map(&:inspect), *@kwargs.inspect].join(", ")
56
+ ]
57
+ end
58
+
59
+ sig { params(args: T.untyped, kwargs: T.untyped).returns(HandlerRef) }
60
+ def bind_args(*args, **kwargs)
61
+ self.class.new(
62
+ @component,
63
+ @name,
64
+ [*@args, *args],
65
+ { **@kwargs, **kwargs }
66
+ )
67
+ end
68
+
69
+ sig { void }
70
+ def marshal_dump
71
+ []
72
+ end
73
+
74
+ sig { params(a: T.untyped).void }
75
+ def marshal_load(a)
76
+ @id = "invalid"
77
+ end
78
+
79
+ sig { params(payload: T.untyped).void }
80
+ def call(payload)
81
+ if @arity.zero?
82
+ T.unsafe(@component).public_send(@name)
83
+ else
84
+ T.unsafe(@component).public_send(@name, payload, *@args, **@kwargs)
85
+ end
86
+ end
87
+
88
+ sig { returns(String) }
89
+ def to_s
90
+ "Mayu.handle(event,'#{@id}')"
91
+ end
92
+
93
+ sig { params(other: T.untyped).returns(T::Boolean) }
94
+ def ==(other)
95
+ self.class === other && @id == other.id
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+
3
+ module Mayu
4
+ module Component
5
+ class Helpers
6
+ extend T::Sig
7
+
8
+ sig { params(wrapper: Component::Wrapper).void }
9
+ def initialize(wrapper)
10
+ @wrapper = wrapper
11
+ end
12
+
13
+ sig do
14
+ params(
15
+ url: String,
16
+ method: Symbol,
17
+ headers: T::Hash[String, String],
18
+ body: T.nilable(String)
19
+ ).returns(Fetch::Response)
20
+ end
21
+ def fetch(url, method: :GET, headers: {}, body: nil)
22
+ vnode.fetch(url, method:, headers:, body:)
23
+ end
24
+
25
+ sig { params(path: String).void }
26
+ def navigate(path)
27
+ vnode.navigate(path)
28
+ end
29
+
30
+ sig { params(selector: String, options: String).void }
31
+ def scroll_into_view(selector, **options)
32
+ vnode.action(:scroll_into_view, { selector:, options: })
33
+ end
34
+
35
+ sig { params(message: String).void }
36
+ def alert(message)
37
+ vnode.action(:alert, message)
38
+ end
39
+
40
+ sig { returns(State) }
41
+ def state = @wrapper.state
42
+ sig { returns(Props) }
43
+ def props = @wrapper.props
44
+
45
+ sig { returns(VDOM::Children) }
46
+ def children = props[:children]
47
+
48
+ sig { params(sources: Props).returns(Props) }
49
+ def merge_props(*sources)
50
+ result = sources.reduce({}, &:merge)
51
+
52
+ if result.delete(:class)
53
+ classes = sources.map { _1[:class] }.flatten.compact
54
+ result[:class] = T.unsafe(@wrapper.instance).styles[*classes]
55
+ end
56
+
57
+ result.transform_keys { _1.to_s.tr("-", "_").to_sym }
58
+ end
59
+
60
+ sig do
61
+ params(
62
+ name: T.nilable(String),
63
+ fallback: T.nilable(T.proc.returns(VDOM::Interfaces::Descriptor))
64
+ ).returns(
65
+ T.nilable(
66
+ T.any(
67
+ VDOM::Interfaces::Descriptor,
68
+ T::Array[VDOM::Interfaces::Descriptor]
69
+ )
70
+ )
71
+ )
72
+ end
73
+ def slot(name = nil, &fallback) = children.slot(name, &fallback)
74
+
75
+ sig { returns(T::Array[T.nilable(String)]) }
76
+ def slot_names = children.slots.keys
77
+
78
+ sig do
79
+ params(name: Symbol, args: T.untyped, kwargs: T.untyped).returns(
80
+ HandlerRef
81
+ )
82
+ end
83
+ def handler(name, *args, **kwargs)
84
+ HandlerRef.new(@wrapper.instance, name, args, kwargs)
85
+ end
86
+
87
+ private
88
+
89
+ sig { returns(VDOM::VNode) }
90
+ def vnode = @wrapper.vnode
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ require_relative "handler_ref"
4
+
5
+ module Mayu
6
+ module Component
7
+ module Interface
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ interface!
11
+ end
12
+
13
+ module DSL
14
+ extend T::Sig
15
+ extend T::Helpers
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,165 @@
1
+ # typed: strict
2
+
3
+ require "async/barrier"
4
+ require_relative "helpers"
5
+
6
+ module Mayu
7
+ module Component
8
+ class Wrapper
9
+ extend T::Sig
10
+
11
+ UpdateProc =
12
+ T.type_alias do
13
+ T.any(
14
+ T.proc.params(arg0: State).returns(State),
15
+ T.proc.params(kwargs: T.untyped).returns(State)
16
+ )
17
+ end
18
+
19
+ sig { returns(String) }
20
+ def vnode_id = @vnode.id
21
+
22
+ sig { returns(Props) }
23
+ attr_accessor :props
24
+ sig { returns(State) }
25
+ attr_accessor :state
26
+ sig { returns(State) }
27
+ attr_reader :next_state
28
+
29
+ sig { returns(Helpers) }
30
+ attr_reader :helpers
31
+ sig { returns(Component::Base) }
32
+ attr_reader :instance
33
+
34
+ sig { returns(T::Boolean) }
35
+ def dirty? = @dirty
36
+ sig { returns(TrueClass) }
37
+ def dirty! = @dirty = true
38
+
39
+ sig { returns(VDOM::VNode) }
40
+ attr_reader :vnode
41
+
42
+ sig do
43
+ params(vnode: VDOM::VNode, klass: T.class_of(Base), props: Props).void
44
+ end
45
+ def initialize(vnode, klass, props = {})
46
+ @vnode = vnode
47
+ @props = T.let(props, Props)
48
+ @state = T.let(klass.get_initial_state(**props), State)
49
+ @next_state = T.let(@state.dup, State)
50
+ @dirty = T.let(true, T::Boolean)
51
+ @instance = T.let(klass.new(self), Base)
52
+ @barrier = T.let(Async::Barrier.new, Async::Barrier)
53
+ @helpers = T.let(Helpers.new(self), Helpers)
54
+ end
55
+
56
+ sig { returns(T::Array[String]) }
57
+ def assets
58
+ @instance.class.assets
59
+ end
60
+
61
+ sig { returns(T.nilable(Resources::Resource)) }
62
+ def resource
63
+ if @instance.class.respond_to?(:__resource)
64
+ @instance.class.send(:__resource)
65
+ end
66
+ end
67
+
68
+ sig { void }
69
+ def mount
70
+ async { @instance.mount }
71
+ end
72
+
73
+ sig { params(prev_props: Props, prev_state: State).void }
74
+ def did_update(prev_props, prev_state)
75
+ async { @instance.did_update(prev_props, prev_state) }
76
+ end
77
+
78
+ sig { void }
79
+ def unmount
80
+ @instance.unmount
81
+ ensure
82
+ @barrier.stop
83
+ end
84
+
85
+ sig { returns(ChildType) }
86
+ def render
87
+ if derived_state =
88
+ @instance.class.get_derived_state_from_props(props, state)
89
+ @state = @state.merge(derived_state)
90
+ end
91
+
92
+ @instance.render
93
+ rescue NotImplementedError => e
94
+ raise NotImplementedError, "#{@instance} should implement #render"
95
+ ensure
96
+ @dirty = false
97
+ end
98
+
99
+ sig { params(next_props: Props, next_state: State).returns(T::Boolean) }
100
+ def should_update?(next_props, next_state)
101
+ @dirty || @instance.should_update?(next_props, next_state)
102
+ end
103
+
104
+ sig { params(blk: T.proc.void).void }
105
+ def async(&blk)
106
+ @barrier.async(&blk)
107
+ end
108
+
109
+ sig do
110
+ params(new_state: T.nilable(State), block: T.nilable(UpdateProc)).void
111
+ end
112
+ def update(new_state = nil, &block)
113
+ if new_state
114
+ @next_state = @next_state.merge(new_state)
115
+ enqueue_update!
116
+ end
117
+
118
+ return unless block
119
+
120
+ if block.parameters in [[:opt, var]]
121
+ Console.logger.warn(self, <<~EOF) unless var == :state
122
+ update do |#{var}|
123
+ # Are you sure you didn't misspell `#{var}`?
124
+ # Usually it should be called `state`.
125
+ end
126
+ EOF
127
+
128
+ update(block.call(@next_state))
129
+ else
130
+ if block.parameters.all? { _1 in [:key | :keyreq, key] }
131
+ keys = block.parameters.map(&:last)
132
+ sliced_state = T.unsafe(@next_state).slice(*keys)
133
+ update(block.call(**sliced_state))
134
+ else
135
+ raise ArgumentError, "All arguments to #update are not keys."
136
+ end
137
+ end
138
+ end
139
+
140
+ sig { returns(T.untyped) }
141
+ def marshal_dump
142
+ [
143
+ VDOM::Marshalling.dump_props(@props),
144
+ VDOM::Marshalling.dump_state(@state)
145
+ ]
146
+ end
147
+
148
+ sig { params(a: T.untyped).void }
149
+ def marshal_load(a)
150
+ @props, @state = a
151
+ @next_state = @state.clone
152
+ @dirty = true
153
+ @barrier = Async::Barrier.new
154
+ end
155
+
156
+ private
157
+
158
+ sig { void }
159
+ def enqueue_update!
160
+ @vnode.enqueue_update!
161
+ @dirty = true
162
+ end
163
+ end
164
+ end
165
+ end