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,191 @@
1
+ # typed: strict
2
+
3
+ require "nanoid"
4
+ require_relative "../fetch"
5
+
6
+ module Mayu
7
+ module State
8
+ module ActionCreator
9
+ extend T::Sig
10
+
11
+ class Base
12
+ extend T::Sig
13
+
14
+ sig { returns(Symbol) }
15
+ attr_reader :type
16
+
17
+ sig { params(type: Symbol).void }
18
+ def initialize(type)
19
+ @type = type
20
+ end
21
+
22
+ sig do
23
+ params(payload: T.untyped).returns(
24
+ T.any(Store::ActionHash, Store::Thunk)
25
+ )
26
+ end
27
+ def call(payload = nil)
28
+ { type:, payload: }
29
+ end
30
+
31
+ sig { params(other: T.untyped).returns(T::Boolean) }
32
+ def ===(other)
33
+ case other
34
+ when Hash
35
+ @type == other[:type]
36
+ when ActionWrapper
37
+ @type == other.type
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+
44
+ class StaticActionCreator < Base
45
+ end
46
+
47
+ class PreparedActionCreator < Base
48
+ ProcType = T.type_alias { T.proc.returns(Store::ActionHash) }
49
+
50
+ sig do
51
+ params(type: Symbol, block: T.proc.returns(Store::ActionHash)).void
52
+ end
53
+ def initialize(type, &block)
54
+ super(type)
55
+ @prepare = block
56
+ end
57
+
58
+ sig do
59
+ params(args: T.untyped, kwargs: T.untyped).returns(Store::ActionHash)
60
+ end
61
+ def call(*args, **kwargs)
62
+ { type: }.merge(T.unsafe(@prepare).call(*args, **kwargs))
63
+ end
64
+ end
65
+
66
+ class AsyncActionCreator < Base
67
+ ProcType =
68
+ T.type_alias do
69
+ T.proc.params(arg0: Store, args: T.untyped, kwargs: T.untyped).void
70
+ end
71
+
72
+ sig { returns(StaticActionCreator) }
73
+ attr_reader :pending
74
+ sig { returns(StaticActionCreator) }
75
+ attr_reader :fulfilled
76
+ sig { returns(StaticActionCreator) }
77
+ attr_reader :rejected
78
+
79
+ sig { params(type: Symbol, block: ProcType).void }
80
+ def initialize(type, &block)
81
+ super(type)
82
+ @block = block
83
+ @pending =
84
+ T.let(
85
+ StaticActionCreator.new(:"#{type}/pending"),
86
+ StaticActionCreator
87
+ )
88
+ @fulfilled =
89
+ T.let(
90
+ StaticActionCreator.new(:"#{type}/fulfilled"),
91
+ StaticActionCreator
92
+ )
93
+ @rejected =
94
+ T.let(
95
+ StaticActionCreator.new(:"#{type}/rejected"),
96
+ StaticActionCreator
97
+ )
98
+ end
99
+
100
+ sig do
101
+ override
102
+ .params(args: T.untyped, parent: Async::Task, kwargs: T.untyped)
103
+ .returns(Store::Thunk)
104
+ end
105
+ def call(*args, parent: Async::Task.current, **kwargs)
106
+ ->(store) do
107
+ parent.async do |task|
108
+ request_id = Nanoid.generate()
109
+ store.dispatch(pending, *args, **kwargs.merge(request_id:))
110
+ result =
111
+ T.unsafe(@block).call(
112
+ store,
113
+ *args,
114
+ **kwargs.merge(task:, request_id:)
115
+ )
116
+ store.dispatch(fulfilled, result)
117
+ rescue => error
118
+ store.dispatch(rejected, { error:, request_id: })
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ module ActionContext
125
+ extend T::Sig
126
+
127
+ FetchResponse = T.type_alias { String }
128
+ Header = T.type_alias { [String, String] }
129
+
130
+ sig do
131
+ params(
132
+ resource: T.any(String, URI),
133
+ method: Symbol,
134
+ headers: T::Array[Header],
135
+ body: T.nilable(String),
136
+ redirect: Symbol,
137
+ referrer: String,
138
+ keepalive: T::Boolean
139
+ ).returns(FetchResponse)
140
+ end
141
+ def fetch(
142
+ resource,
143
+ method: :GET,
144
+ headers: [],
145
+ body: nil,
146
+ redirect: :follow,
147
+ referrer: "mayu-live-fetch/#{Mayu::VERSION}",
148
+ keepalive: false
149
+ )
150
+ internet = Async::HTTP::Internet.new
151
+
152
+ case method
153
+ when :GET
154
+ internet.get(resource, headers)
155
+ when :POST
156
+ internet.post(resource, headers, body)
157
+ when :PUT
158
+ internet.put(resource, headers, body)
159
+ when :PATCH
160
+ internet.patch(resource, headers, body)
161
+ end
162
+ ensure
163
+ internet&.close
164
+ end
165
+ end
166
+
167
+ sig do
168
+ params(
169
+ type: Symbol,
170
+ block: T.nilable(PreparedActionCreator::ProcType)
171
+ ).returns(Base)
172
+ end
173
+ def self.create(type, &block)
174
+ if block_given?
175
+ PreparedActionCreator.new(type, &block)
176
+ else
177
+ StaticActionCreator.new(type)
178
+ end
179
+ end
180
+
181
+ sig do
182
+ params(type: Symbol, block: AsyncActionCreator::ProcType).returns(
183
+ AsyncActionCreator
184
+ )
185
+ end
186
+ def self.async(type, &block)
187
+ AsyncActionCreator.new(type, &block)
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+
3
+ module Mayu
4
+ module State
5
+ class ActionWrapper
6
+ extend T::Sig
7
+
8
+ sig { returns(Symbol) }
9
+ attr_reader :type
10
+ sig { returns(T.untyped) }
11
+ attr_reader :payload
12
+
13
+ sig { params(type: Symbol, payload: T.untyped).void }
14
+ def initialize(type:, payload:)
15
+ @type = type
16
+ @payload = payload
17
+ end
18
+
19
+ sig { params(key: T.untyped).returns(T.untyped) }
20
+ def [](key) = @payload[key]
21
+ sig { params(key: T.untyped, block: T.untyped).returns(T.untyped) }
22
+ def fetch(key, &block) = @payload.fetch(key, &block)
23
+
24
+ sig { returns(String) }
25
+ def inspect
26
+ "#<Action/#{type} payload=#{payload.inspect}>"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,220 @@
1
+ # typed: true
2
+
3
+ require_relative "store"
4
+
5
+ module Mayu
6
+ module State
7
+ class Selector
8
+ module SelectorModule
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ block: T.proc.params(state: T.untyped).returns(T.untyped)
14
+ ).returns(Selector)
15
+ end
16
+ def selector(&block)
17
+ Selector.new
18
+ end
19
+
20
+ sig do
21
+ params(
22
+ block: T.proc.params(state: T.untyped).returns(T.untyped)
23
+ ).returns(Module)
24
+ end
25
+ def self.build(&block)
26
+ mod = Module.new
27
+ mod.send(:extend, SelectorModule)
28
+ mod.class_eval(&block)
29
+ mod
30
+ end
31
+ end
32
+
33
+ extend T::Sig
34
+ end
35
+
36
+ module Actions
37
+ module ActionModule
38
+ extend T::Sig
39
+
40
+ sig do
41
+ params(
42
+ type: Symbol,
43
+ block: T.nilable(ActionCreator::PreparedActionCreator::ProcType)
44
+ ).returns(ActionCreator::Base)
45
+ end
46
+ def action(type, &block)
47
+ ActionCreator.create(type, &block)
48
+ end
49
+
50
+ sig do
51
+ params(
52
+ type: Symbol,
53
+ block: ActionCreator::AsyncActionCreator::ProcType
54
+ ).returns(ActionCreator::AsyncActionCreator)
55
+ end
56
+ def async(type, &block)
57
+ ActionCreator.async(type, &block)
58
+ end
59
+
60
+ sig do
61
+ params(
62
+ block: T.proc.params(state: T.untyped).returns(T.untyped)
63
+ ).returns(Module)
64
+ end
65
+ def self.build(&block)
66
+ mod = Module.new
67
+ mod.send(:extend, ActionModule)
68
+ mod.class_eval(&block)
69
+ mod
70
+ end
71
+ end
72
+
73
+ extend T::Sig
74
+ end
75
+
76
+ module ReducerDSL
77
+ extend T::Sig
78
+ extend T::Helpers
79
+ interface!
80
+
81
+ sig { abstract.params(kwargs: T.untyped).void }
82
+ def initial_state(**kwargs)
83
+ end
84
+
85
+ sig do
86
+ abstract
87
+ .params(action: ActionCreator::Base, reducer: Store::Reducer)
88
+ .void
89
+ end
90
+ def reducer(action, &reducer)
91
+ end
92
+
93
+ sig { abstract.params(url: String, options: T.untyped).void }
94
+ def fetch(url, **options)
95
+ end
96
+
97
+ sig do
98
+ abstract
99
+ .params(
100
+ block:
101
+ T.proc.bind(Selector::SelectorModule).params(arg0: T.untyped).void
102
+ )
103
+ .returns(Module)
104
+ end
105
+ def selectors(&block)
106
+ end
107
+
108
+ sig do
109
+ abstract
110
+ .params(
111
+ block:
112
+ T.proc.bind(Actions::ActionModule).params(arg0: T.untyped).void
113
+ )
114
+ .returns(Module)
115
+ end
116
+ def actions(&block)
117
+ end
118
+ end
119
+
120
+ class Loader
121
+ extend T::Sig
122
+
123
+ class ReducerBuilder
124
+ extend ::T::Sig
125
+ include ::Mayu::State::ReducerDSL
126
+
127
+ sig do
128
+ params(source: ::String, path: ::String).returns(
129
+ T::Hash[Symbol, T.untyped]
130
+ )
131
+ end
132
+ def self.build(source, path)
133
+ { initial_state: nil, reducers: [], actions: [] }.tap do
134
+ new(File.basename(path, ".*"), _1).instance_eval(source, path, 0)
135
+ end
136
+ end
137
+
138
+ sig { params(prefix: String, data: T::Hash[::Symbol, T.untyped]).void }
139
+ def initialize(prefix, data)
140
+ @prefix = prefix
141
+ @data = data
142
+ end
143
+
144
+ sig { override.params(kwargs: T.untyped).void }
145
+ def initial_state(**kwargs)
146
+ @data[:initial_state] = kwargs
147
+ end
148
+
149
+ sig { override.params(url: String, options: T.untyped).void }
150
+ def fetch(url, **options)
151
+ end
152
+
153
+ sig do
154
+ override
155
+ .params(
156
+ block:
157
+ T
158
+ .proc
159
+ .bind(Selector::SelectorModule)
160
+ .params(arg0: T.untyped)
161
+ .void
162
+ )
163
+ .returns(Module)
164
+ end
165
+ def selectors(&block)
166
+ @data[:selectors] = Selector::SelectorModule.build(&block)
167
+ end
168
+
169
+ sig do
170
+ override
171
+ .params(
172
+ block:
173
+ T.proc.bind(Actions::ActionModule).params(arg0: T.untyped).void
174
+ )
175
+ .returns(Module)
176
+ end
177
+ def actions(&block)
178
+ @data[:actions] = Actions::ActionModule.build(&block)
179
+ end
180
+
181
+ sig do
182
+ override
183
+ .params(action: ActionCreator::Base, reducer: Store::Reducer)
184
+ .void
185
+ end
186
+ def reducer(action, &reducer)
187
+ @data[:reducers].push([action, reducer])
188
+ end
189
+ end
190
+
191
+ sig { params(directory: String).void }
192
+ def initialize(directory)
193
+ @directory = directory
194
+ end
195
+
196
+ sig { returns(Store::Reducers) }
197
+ def load
198
+ Dir[File.join(@directory, "*.rb")]
199
+ .map do |path|
200
+ data = ReducerBuilder.build(File.read(path), path)
201
+ name = File.basename(path, ".*").capitalize.to_sym
202
+
203
+ [
204
+ name,
205
+ ->(state, action) do
206
+ state ||= data[:initial_state]
207
+
208
+ data[:reducers]
209
+ .filter { _1 === action }
210
+ .reduce(
211
+ state || data[:initial_state]
212
+ ) { |state, (_, reducer)| reducer.call(state, action) }
213
+ end
214
+ ]
215
+ end
216
+ .to_h
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,82 @@
1
+ # typed: strict
2
+
3
+ require "async"
4
+ require "async/semaphore"
5
+ require_relative "action_wrapper"
6
+ require_relative "action_creator"
7
+
8
+ module Mayu
9
+ module State
10
+ class Store
11
+ extend T::Sig
12
+
13
+ sig { returns(T.untyped) }
14
+ def marshal_dump
15
+ [@state]
16
+ end
17
+
18
+ sig { params(a: T.untyped).void }
19
+ def marshal_load(a)
20
+ @state = a.first
21
+ end
22
+
23
+ State = T.type_alias { T.untyped }
24
+ Reducer =
25
+ T.type_alias do
26
+ T.proc.params(arg0: State, arg1: ActionWrapper).returns(State)
27
+ end
28
+ Thunk = T.type_alias { T.proc.params(arg0: Store).void }
29
+ Reducers = T.type_alias { T::Hash[Symbol, Store::Reducer] }
30
+
31
+ sig { returns(State) }
32
+ attr_reader :state
33
+
34
+ sig { params(initial_state: State, reducers: Reducers).void }
35
+ def initialize(initial_state, reducers:)
36
+ @state = T.let(initial_state, State)
37
+ @reducer = T.let(combine_reducers(reducers), Reducer)
38
+ @semaphore = T.let(Async::Semaphore.new, Async::Semaphore)
39
+
40
+ dispatch({ type: :__INIT__, payload: nil })
41
+ end
42
+
43
+ ActionHash = T.type_alias { T::Hash[Symbol, T.untyped] }
44
+
45
+ sig do
46
+ params(
47
+ action: T.any(ActionHash, ActionCreator::Base, Thunk),
48
+ args: T.untyped,
49
+ kwargs: T.untyped
50
+ ).void
51
+ end
52
+ def dispatch(action, *args, **kwargs)
53
+ if action.is_a?(ActionCreator::Base)
54
+ return dispatch(T.unsafe(action).call(*args, **kwargs))
55
+ end
56
+
57
+ return action.call(self) if action.is_a?(Proc)
58
+
59
+ @semaphore.async do
60
+ new_state =
61
+ @reducer.call(@state.dup, T.unsafe(ActionWrapper).new(**action))
62
+ @state = new_state
63
+ rescue => e
64
+ Console.logger.error(self) { "Reducer crashed" }
65
+ end
66
+
67
+ nil
68
+ end
69
+
70
+ private
71
+
72
+ sig { params(reducers: T::Hash[Symbol, Reducer]).returns(Reducer) }
73
+ def combine_reducers(reducers)
74
+ ->(state, action) do
75
+ reducers.reduce(state) do |new_state, (name, reducer)|
76
+ new_state.merge(name => reducer.call(state[name], action))
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
data/lib/mayu/state.rb ADDED
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+
3
+ require_relative "state/store"
4
+
5
+ module Mayu
6
+ module State
7
+ end
8
+ end
@@ -0,0 +1,97 @@
1
+ # typed: true
2
+
3
+ require "minitest/autorun"
4
+ require "test_helper"
5
+
6
+ require_relative "vdom/descriptor"
7
+ require_relative "state"
8
+
9
+ class TestState < Minitest::Test
10
+ class MyComponent < Mayu::Component::Base
11
+ def render
12
+ end
13
+ end
14
+
15
+ State = Mayu::State
16
+
17
+ INCREMENT = State::ActionCreator.create(:increment)
18
+ DECREMENT = State::ActionCreator.create(:decrement)
19
+ ADD_ITEM = State::ActionCreator.create(:add_item)
20
+ REMOVE_ITEM = State::ActionCreator.create(:remove_item)
21
+ THUNK =
22
+ State::ActionCreator.async(:hello) do |store|
23
+ store.dispatch(INCREMENT)
24
+ sleep 1
25
+ store.dispatch(INCREMENT)
26
+ sleep 1
27
+ store.dispatch(DECREMENT)
28
+ end
29
+
30
+ def test_initialize_descriptor
31
+ Sync do
32
+ count_reducer = ->(state, action) do
33
+ state ||= { count: 0 }
34
+
35
+ case action
36
+ when INCREMENT
37
+ state.merge(count: state[:count].succ)
38
+ when DECREMENT
39
+ state.merge(count: state[:count].pred)
40
+ else
41
+ state
42
+ end
43
+ end
44
+
45
+ totals_reducer = ->(state, action) do
46
+ state ||= 0
47
+
48
+ case action
49
+ in ADD_ITEM
50
+ state += 1
51
+ in REMOVE_ITEM
52
+ state -= 1
53
+ else
54
+ state
55
+ end
56
+ end
57
+
58
+ items_reducer = ->(state, action) do
59
+ state ||= []
60
+ p action
61
+
62
+ case action
63
+ in ADD_ITEM
64
+ state + [action[:item]]
65
+ in REMOVE_ITEM
66
+ state - [action[:item]]
67
+ else
68
+ state
69
+ end
70
+ end
71
+
72
+ @store =
73
+ State::Store.new(
74
+ { total: 0, items: [] },
75
+ reducers: {
76
+ count1: count_reducer,
77
+ count2: count_reducer,
78
+ items: items_reducer,
79
+ totals: totals_reducer
80
+ }
81
+ )
82
+
83
+ @store.dispatch(ADD_ITEM, item: "Apple")
84
+ @store.dispatch(ADD_ITEM, item: "Banana")
85
+ @store.dispatch(REMOVE_ITEM, item: "Apple")
86
+ @store.dispatch(ADD_ITEM, item: "Papaya")
87
+ @store.dispatch(INCREMENT)
88
+
89
+ puts @store.state
90
+ @store.dispatch(THUNK)
91
+ sleep 0.1
92
+ @store.dispatch(THUNK)
93
+ puts "hello"
94
+ puts @store.state
95
+ end
96
+ end
97
+ end