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,54 @@
1
+ # typed: strict
2
+
3
+ require_relative "vdom/interfaces"
4
+ require_relative "component/wrapper"
5
+ require_relative "component/base"
6
+
7
+ module Mayu
8
+ module Component
9
+ extend T::Sig
10
+
11
+ Props = T.type_alias { T::Hash[Symbol, T.untyped] }
12
+ State = T.type_alias { T::Hash[Symbol, T.untyped] }
13
+
14
+ LambdaComponent =
15
+ T.type_alias do
16
+ T
17
+ .proc
18
+ .params(kwargs: Props)
19
+ .returns(T.nilable(VDOM::Interfaces::Descriptor))
20
+ end
21
+
22
+ ComponentType = T.type_alias { T.any(T.class_of(Base), LambdaComponent) }
23
+
24
+ Children = T.type_alias { T.any(ChildType, T::Array[ChildType]) }
25
+
26
+ ChildType =
27
+ T.type_alias do
28
+ T.nilable(
29
+ T.any(VDOM::Interfaces::Descriptor, T::Boolean, String, Numeric)
30
+ )
31
+ end
32
+
33
+ ElementType = T.type_alias { T.any(Symbol, ComponentType) }
34
+
35
+ sig { params(other: T.untyped).returns(T::Boolean) }
36
+ def self.===(other)
37
+ component_class?(other)
38
+ end
39
+
40
+ sig { params(klass: T.untyped).returns(T::Boolean) }
41
+ def self.component_class?(klass)
42
+ !!(klass.is_a?(Class) && klass < Base)
43
+ end
44
+
45
+ sig do
46
+ params(vnode: VDOM::VNode, type: T.untyped, props: Props).returns(
47
+ T.nilable(Wrapper)
48
+ )
49
+ end
50
+ def self.wrap(vnode, type, props)
51
+ component_class?(type) ? Wrapper.new(vnode, type, props) : nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,195 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "toml-rb"
5
+ require "async/container"
6
+
7
+ module Mayu
8
+ class Configuration < T::Struct
9
+ extend T::Sig
10
+
11
+ CONFIG_FILE = "mayu.toml"
12
+
13
+ class Server < T::Struct
14
+ extend T::Sig
15
+
16
+ const :scheme, String, default: "https"
17
+ const :host, String, default: "127.0.0.1"
18
+ const :port, Integer, default: 9292
19
+
20
+ const :hot_swap, T::Boolean, default: false
21
+ const :self_signed_cert, T::Boolean, default: false
22
+
23
+ const :generate_assets, T::Boolean, default: false
24
+
25
+ const :render_exceptions, T::Boolean, default: false
26
+
27
+ const :count, Integer, default: Async::Container.processor_count
28
+ const :forks, T.nilable(Integer)
29
+ const :threads, T.nilable(Integer)
30
+
31
+ sig { returns(URI::HTTP) }
32
+ def uri
33
+ URI.for(scheme, nil, host, port, nil, "/", nil, nil, nil).normalize
34
+ end
35
+ end
36
+
37
+ class Instance < T::Struct
38
+ const :app_name, String, default: ENV.fetch("FLY_APP_NAME", "mayu-live")
39
+ const :region, String, default: ENV.fetch("FLY_REGION", "dev")
40
+ const :alloc_id,
41
+ String,
42
+ default:
43
+ ENV.fetch("FLY_ALLOC_ID", "00000000-0000-0000-0000-000000000000")
44
+ end
45
+
46
+ class Paths < T::Struct
47
+ const :components, String, default: "components"
48
+ const :pages, String, default: "pages"
49
+ const :stores, String, default: "stores"
50
+ const :public, String, default: "public"
51
+ const :assets, String, default: ".assets"
52
+ const :dist, String, default: "dist"
53
+ const :bundle_filename, String, default: "app.mayu-bundle"
54
+ end
55
+
56
+ class Metrics < T::Struct
57
+ const :enabled, T::Boolean, default: false
58
+ const :port, Integer, default: 9091
59
+ const :host, String, default: "127.0.0.1"
60
+ const :path, String, default: "/metrics"
61
+ end
62
+
63
+ const :mode, Symbol
64
+ const :root, String
65
+ const :secret_key, String
66
+ const :use_bundle, T::Boolean, default: false
67
+
68
+ const :server, Server, default: Server.new
69
+ const :metrics, Metrics, default: Metrics.new
70
+ const :paths, Paths, default: Paths.new
71
+ const :instance, Instance, default: Instance.new
72
+
73
+ sig { params(dir: String).returns(String) }
74
+ def self.resolve_config_file(dir = Dir.pwd)
75
+ path = File.join(dir, CONFIG_FILE)
76
+
77
+ return path if File.file?(path)
78
+
79
+ parent = File.expand_path("..", dir)
80
+
81
+ if dir == parent
82
+ raise "Could not find #{CONFIG_FILE} in any parent directory."
83
+ end
84
+
85
+ resolve_config_file(parent)
86
+ end
87
+
88
+ sig do
89
+ params(
90
+ mode: Symbol,
91
+ pwd: String,
92
+ overrides: T::Hash[String, T.untyped]
93
+ ).returns(T.attached_class)
94
+ end
95
+ def self.load_config(mode, pwd: Dir.pwd, overrides: {})
96
+ file = resolve_config_file(pwd)
97
+ root = File.dirname(file)
98
+
99
+ config =
100
+ T.cast(
101
+ TomlRB.load_file(file),
102
+ T::Hash[String, T::Hash[String, T.untyped]]
103
+ )
104
+
105
+ base_config = config.dig("base") || {}
106
+ env_config = config.dig(mode.to_s) || {}
107
+
108
+ merged_config = base_config.merge(env_config).merge(overrides)
109
+
110
+ secret_key =
111
+ merged_config.fetch("secret_key") do
112
+ ENV.fetch("MAYU_SECRET_KEY") do
113
+ raise "secret_key is not configured (can be set with env var MAYU_SECRET_KEY)"
114
+ end
115
+ end
116
+
117
+ from_hash!(
118
+ {
119
+ **merged_config,
120
+ "root" => root,
121
+ "secret_key" => secret_key,
122
+ "mode" => mode
123
+ }
124
+ )
125
+ end
126
+
127
+ sig { params(configuration: T::Struct).void }
128
+ def self.log_config(configuration)
129
+ Console.logger.info(self) { make_table(configuration) }
130
+ end
131
+
132
+ sig do
133
+ params(
134
+ configuration: T::Struct,
135
+ style: T::Hash[Symbol, T.untyped]
136
+ ).returns(String)
137
+ end
138
+ def self.make_table(
139
+ configuration,
140
+ style: { all_separators: true, border: :unicode }
141
+ )
142
+ Terminal::Table
143
+ .new do |t|
144
+ t.headings = %w[key value type].map { "\e[1m#{_1}\e[0m" }
145
+ t.style = style
146
+
147
+ configuration.class.props.each do |prop, opts|
148
+ value =
149
+ if prop.to_s.start_with?("secret_")
150
+ "\e[2m***hidden***\e[0m"
151
+ else
152
+ case configuration.send(prop)
153
+ in T::Struct => struct
154
+ make_table(struct, style: { border: :unicode_round })
155
+ in other
156
+ colorize_value(other)
157
+ end
158
+ end
159
+
160
+ t.add_row([prop, value, opts[:type].to_s.delete_prefix("Mayu::")])
161
+ end
162
+ end
163
+ .to_s
164
+ end
165
+
166
+ sig { params(value: T.untyped).returns(String) }
167
+ def self.colorize_value(value)
168
+ if color = value_color(value).nonzero?
169
+ "\e[#{color}m#{value.inspect}\e[0m"
170
+ else
171
+ value.inspect
172
+ end
173
+ end
174
+
175
+ sig { params(value: T.untyped).returns(Integer) }
176
+ def self.value_color(value)
177
+ case value
178
+ when FalseClass
179
+ 31
180
+ when TrueClass
181
+ 32
182
+ when String
183
+ 33
184
+ when Numeric
185
+ 34
186
+ when Symbol
187
+ 36
188
+ when nil
189
+ 2
190
+ else
191
+ 0
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,23 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Mayu
7
+ module DisableSorbet
8
+ def self.disable_sorbet!
9
+ # https://github.com/sorbet/sorbet/issues/3279#issuecomment-679154712
10
+ T::Configuration.default_checked_level = :never
11
+
12
+ error_handler =
13
+ lambda do |error, *_|
14
+ # Log error somewhere
15
+ end
16
+
17
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
18
+ T::Configuration.inline_type_error_handler = error_handler
19
+ # Suppresses errors caused by incorrect parameter ordering
20
+ T::Configuration.sig_validation_error_handler = error_handler
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,151 @@
1
+ # typed: strict
2
+
3
+ require "async"
4
+ require "async/http/internet"
5
+ require_relative "vdom"
6
+ require_relative "state/store"
7
+ require_relative "state/loader"
8
+ require_relative "routes"
9
+ require_relative "metrics"
10
+ require_relative "app_metrics"
11
+ require_relative "resources/registry"
12
+ require_relative "fetch"
13
+ require_relative "message_cipher"
14
+ require_relative "configuration"
15
+
16
+ module Mayu
17
+ class Environment
18
+ # The Environment class is instantiated on startup and contains
19
+ # configuration and everything that should be shared.
20
+ extend T::Sig
21
+
22
+ PAGES_DIR = "pages"
23
+ STORE_DIR = "store"
24
+
25
+ sig { returns(String) }
26
+ attr_reader :root
27
+ sig { returns(Configuration) }
28
+ attr_reader :config
29
+ sig { returns(T::Array[Routes::Route]) }
30
+ attr_reader :routes
31
+ sig { returns(State::Store::Reducers) }
32
+ attr_reader :reducers
33
+ sig { returns(Resources::Registry) }
34
+ attr_reader :resources
35
+ sig { returns(Fetch) }
36
+ attr_reader :fetch
37
+ sig { returns(MessageCipher) }
38
+ attr_reader :message_cipher
39
+ sig { returns(AppMetrics) }
40
+ attr_reader :metrics
41
+
42
+ sig { params(config: Configuration, metrics: AppMetrics).void }
43
+ def initialize(config, metrics)
44
+ @root = T.let(config.root, String)
45
+ @app_root = T.let(File.join(config.root, "app"), String)
46
+ @config = config
47
+ @message_cipher =
48
+ T.let(MessageCipher.new(key: config.secret_key, ttl: 30), MessageCipher)
49
+ # TODO: Reload routes when things change in /pages...
50
+ # Should probably make routes into a resource type.
51
+ @routes =
52
+ T.let(
53
+ Routes.build_routes(File.join(@app_root, PAGES_DIR)),
54
+ T::Array[Routes::Route]
55
+ )
56
+ @reducers =
57
+ T.let(
58
+ State::Loader.new(File.join(@app_root, STORE_DIR)).load,
59
+ State::Store::Reducers
60
+ )
61
+ @resources =
62
+ T.let(
63
+ if @config.use_bundle
64
+ Resources::Registry.load(
65
+ File.read(@config.paths.bundle_filename, encoding: "binary"),
66
+ root:
67
+ )
68
+ else
69
+ Resources::Registry.new(root: @root)
70
+ end,
71
+ Resources::Registry
72
+ )
73
+ @metrics = metrics
74
+ @fetch = T.let(Fetch.new, Fetch)
75
+ @init_js = T.let(nil, T.nilable(String))
76
+ end
77
+
78
+ sig { returns(String) }
79
+ def init_js
80
+ @init_js ||=
81
+ JSON.parse(File.read(File.join(js_runtime_path, "entries.json"))).fetch(
82
+ "main"
83
+ )
84
+ end
85
+
86
+ sig { params(name: Symbol).returns(String) }
87
+ def path(name)
88
+ File.join(@root, @config.paths.send(name))
89
+ end
90
+
91
+ sig { returns(String) }
92
+ def js_runtime_path
93
+ File.join(__dir__, "client", "dist")
94
+ end
95
+
96
+ sig do
97
+ params(initial_state: T::Hash[Symbol, T.untyped]).returns(State::Store)
98
+ end
99
+ def create_store(initial_state: {})
100
+ State::Store.new(initial_state, reducers:)
101
+ end
102
+
103
+ sig do
104
+ params(request_path: String, headers: T::Hash[String, String]).returns(
105
+ VDOM::Descriptor
106
+ )
107
+ end
108
+ def load_root(request_path, headers: {})
109
+ path, search = request_path.split("?", 2)
110
+ # We should match the route earlier, so that we don't have to get this
111
+ # far in case it doesn't match...
112
+ route_match = match_route(path.to_s)
113
+ query = Rack::Utils.parse_nested_query(search).transform_keys(&:to_sym)
114
+ params = route_match.params
115
+
116
+ # Load the page component.
117
+ component_path = File.join("/", "app", "pages", route_match.template)
118
+ resources.load_resource(component_path).type =>
119
+ Resources::Types::Component => mod_type
120
+
121
+ page_component = mod_type.component
122
+
123
+ resources.load_resource(File.join("/", "app", "root")).type =>
124
+ Resources::Types::Component => root
125
+
126
+ request_info = { path:, params:, query:, headers: }.freeze
127
+
128
+ # Apply the layouts.
129
+ # NOTE: Pages should probably be their own
130
+ # resource type and load their layouts.
131
+ route_match
132
+ .layouts
133
+ .reverse
134
+ .reduce(VDOM::H[page_component, request: request_info]) do |app, layout|
135
+ Console.logger.info(self, "Applying layout #{layout.inspect}")
136
+
137
+ resources.load_resource(
138
+ File.join("/", "app", "pages", layout)
139
+ ).type => Resources::Types::Component => layout
140
+
141
+ VDOM::H[layout.component, app, request: request_info]
142
+ end
143
+ .then { VDOM::H[root.component, _1] }
144
+ end
145
+
146
+ sig { params(request_path: String).returns(Routes::RouteMatch) }
147
+ def match_route(request_path)
148
+ Routes.match_route(@routes, request_path)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,158 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "nanoid"
5
+ require "msgpack"
6
+ require "zlib"
7
+
8
+ module Mayu
9
+ module EventStream
10
+ class Writable
11
+ extend T::Sig
12
+
13
+ sig { params(body: Async::HTTP::Body::Writable).void }
14
+ def initialize(body)
15
+ @body = body
16
+ @deflate =
17
+ T.let(
18
+ Zlib::Deflate.new(
19
+ Zlib::BEST_COMPRESSION,
20
+ -Zlib::MAX_WBITS,
21
+ Zlib::MAX_MEM_LEVEL,
22
+ Zlib::HUFFMAN_ONLY
23
+ ),
24
+ Zlib::Deflate
25
+ )
26
+
27
+ @wrapper = T.let(EventStream::Wrapper.new, EventStream::Wrapper)
28
+ end
29
+
30
+ sig { params(obj: T.untyped).void }
31
+ def write(obj)
32
+ obj
33
+ .then { @wrapper.pack(_1.to_a) }
34
+ .then { @deflate.deflate(_1, Zlib::SYNC_FLUSH) }
35
+ .then { @body.write(_1) }
36
+ end
37
+
38
+ sig { returns(T::Boolean) }
39
+ def closed?
40
+ @body.closed?
41
+ end
42
+
43
+ sig { void }
44
+ def close
45
+ @body.write(@deflate.flush(Zlib::FINISH))
46
+ @deflate.close
47
+ @body.close
48
+ end
49
+ end
50
+
51
+ class Blob
52
+ extend T::Sig
53
+
54
+ sig { params(data: String).void }
55
+ def initialize(data)
56
+ @data = data
57
+ end
58
+
59
+ sig { params(data: String).returns(T.attached_class) }
60
+ def self.from_msgpack_ext(data)
61
+ new(data)
62
+ end
63
+
64
+ sig { returns(String) }
65
+ def to_msgpack_ext
66
+ @data
67
+ end
68
+ end
69
+
70
+ class Wrapper < MessagePack::Factory
71
+ extend T::Sig
72
+
73
+ sig { void }
74
+ def initialize
75
+ super()
76
+
77
+ self.register_type(0x01, Blob)
78
+ end
79
+ end
80
+
81
+ class Message
82
+ extend T::Sig
83
+
84
+ sig { returns(String) }
85
+ attr_reader :id
86
+ sig { returns(String) }
87
+ attr_reader :event
88
+ sig { returns(T.untyped) }
89
+ attr_reader :data
90
+
91
+ sig { params(event: T.any(String, Symbol), data: T.untyped).void }
92
+ def initialize(event, data = {})
93
+ @id = T.let(Nanoid.generate, String)
94
+ @event = T.let(event.to_s, String)
95
+ @data = data
96
+ end
97
+
98
+ sig { returns([String, String, T.untyped]) }
99
+ def to_a
100
+ [@id, @event, @data]
101
+ end
102
+ end
103
+
104
+ class Log
105
+ extend T::Sig
106
+
107
+ sig { void }
108
+ def initialize
109
+ @history = T.let([], T::Array[Message])
110
+ @queue = T.let(Async::Queue.new, Async::Queue)
111
+ @wrapper = T.let(Wrapper.new, Wrapper)
112
+ end
113
+
114
+ sig { returns(T::Boolean) }
115
+ def empty? = @queue.empty?
116
+
117
+ sig { returns(Integer) }
118
+ def size = @queue.size
119
+
120
+ sig { params(event: Symbol, data: T.untyped).void }
121
+ def push(event, data = {})
122
+ @queue.enqueue(Message.new(event, data))
123
+ end
124
+
125
+ sig { params(id: String).void }
126
+ def ack(id)
127
+ if index = @history.map(&:id).index(id)
128
+ @history.slice!(0..index)
129
+ end
130
+ end
131
+
132
+ sig { params(last_id: String).returns(T::Array[Message]) }
133
+ def replay(last_id)
134
+ ack(last_id)
135
+ @history.dup
136
+ end
137
+
138
+ sig { returns(Message) }
139
+ def pop
140
+ message = @queue.dequeue
141
+ # There is no ack-functionality in the client so this will just grow anyways..
142
+ # @history.push(message)
143
+ message
144
+ end
145
+
146
+ sig { params(message: Message).returns(String) }
147
+ def pack(message)
148
+ data = @wrapper.pack(message.to_a)
149
+ # N = 32-bit unsigned, network (big-endian) byte order
150
+ # a = arbitrary binary string (null padded, count is width)
151
+ [data.bytesize, data].pack("N a*")
152
+ end
153
+ end
154
+
155
+ class Stream
156
+ end
157
+ end
158
+ end
data/lib/mayu/fetch.rb ADDED
@@ -0,0 +1,88 @@
1
+ # typed: strict
2
+
3
+ require "bundler/setup"
4
+ require "sorbet-runtime"
5
+ require "async"
6
+ require "async/http/internet"
7
+ require "rack/utils"
8
+ require "pry"
9
+ require "uri"
10
+
11
+ module Mayu
12
+ # This class implements a simplified version of the Fetch API.
13
+ # https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
14
+ class Fetch
15
+ class Response < T::Struct
16
+ extend T::Sig
17
+
18
+ const :url, String
19
+ const :body, String
20
+ const :headers, T::Hash[String, T.any(String, T::Array[String])]
21
+ const :status, Integer
22
+ const :status_text, String
23
+ const :ok, T::Boolean
24
+ const :redirected, T::Boolean
25
+
26
+ alias ok? ok
27
+ alias redirected? redirected
28
+
29
+ sig { params(symbolize_names: T::Boolean).returns(T.untyped) }
30
+ def json(symbolize_names: false)
31
+ JSON.parse(body, symbolize_names:)
32
+ end
33
+
34
+ sig { returns(String) }
35
+ def content_type
36
+ headers.fetch("content-type").to_s
37
+ end
38
+
39
+ sig { returns(String) }
40
+ def inspect
41
+ "<##{self.class.name} url=#{url.inspect} status=#{status.inspect} status_text=#{status_text.inspect} content_type=#{content_type.inspect} body=#{body.bytesize}b>"
42
+ end
43
+ end
44
+
45
+ extend T::Sig
46
+
47
+ sig { void }
48
+ def initialize
49
+ @internet = T.let(Async::HTTP::Internet.new, Async::HTTP::Internet)
50
+ end
51
+
52
+ sig do
53
+ params(
54
+ url: String,
55
+ method: Symbol,
56
+ headers: T::Hash[String, String],
57
+ body: T.nilable(String)
58
+ ).returns(Response)
59
+ end
60
+ def fetch(url, method: :GET, headers: {}, body: nil)
61
+ puts "\e[35mFETCHING #{url}\e[0m"
62
+ res = @internet.call(method, url, headers.to_a, body)
63
+ puts "\e[34mFETCHED #{url}\e[0m"
64
+
65
+ Response.new(
66
+ url:,
67
+ body: res.read,
68
+ headers: res.headers.to_h,
69
+ status: res.status,
70
+ status_text: Rack::Utils::HTTP_STATUS_CODES[res.status],
71
+ ok: res.success?,
72
+ redirected: res.redirection?
73
+ )
74
+ rescue => e
75
+ puts "\e[32mFAILED ON #{url}\e[0m"
76
+ raise
77
+ end
78
+ end
79
+ end
80
+ #
81
+ # Async do
82
+ # fetch = Mayu::Fetch.new
83
+ # res =
84
+ # fetch.fetch(
85
+ # "https://raw.githubusercontent.com/rack/rack/main/lib/rack/utils.rb"
86
+ # )
87
+ # p res
88
+ # end