@typed/template 0.1.4 → 0.3.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 (234) hide show
  1. package/Directive/package.json +6 -0
  2. package/ElementRef/package.json +6 -0
  3. package/ElementSource/package.json +6 -0
  4. package/Entry/package.json +6 -0
  5. package/EventHandler/package.json +6 -0
  6. package/Html/package.json +6 -0
  7. package/HtmlChunk/package.json +6 -0
  8. package/Hydrate/package.json +6 -0
  9. package/Many/package.json +6 -0
  10. package/Meta/package.json +6 -0
  11. package/Parser/package.json +6 -0
  12. package/Part/package.json +6 -0
  13. package/Placeholder/package.json +6 -0
  14. package/Platform/package.json +6 -0
  15. package/Render/package.json +6 -0
  16. package/RenderContext/package.json +6 -0
  17. package/RenderEvent/package.json +6 -0
  18. package/RenderTemplate/package.json +6 -0
  19. package/Renderable/package.json +6 -0
  20. package/Template/package.json +6 -0
  21. package/TemplateInstance/package.json +6 -0
  22. package/Test/package.json +6 -0
  23. package/Vitest/package.json +6 -0
  24. package/dist/cjs/Directive.js +1 -1
  25. package/dist/cjs/Directive.js.map +1 -1
  26. package/dist/cjs/ElementRef.js +23 -13
  27. package/dist/cjs/ElementRef.js.map +1 -1
  28. package/dist/cjs/ElementSource.js +16 -18
  29. package/dist/cjs/ElementSource.js.map +1 -1
  30. package/dist/cjs/EventHandler.js +1 -1
  31. package/dist/cjs/EventHandler.js.map +1 -1
  32. package/dist/cjs/Html.js +31 -32
  33. package/dist/cjs/Html.js.map +1 -1
  34. package/dist/cjs/HtmlChunk.js +4 -1
  35. package/dist/cjs/HtmlChunk.js.map +1 -1
  36. package/dist/cjs/Hydrate.js +1 -1
  37. package/dist/cjs/Hydrate.js.map +1 -1
  38. package/dist/cjs/Many.js +14 -13
  39. package/dist/cjs/Many.js.map +1 -1
  40. package/dist/cjs/Parser.js +11 -323
  41. package/dist/cjs/Parser.js.map +1 -1
  42. package/dist/cjs/Placeholder.js +3 -3
  43. package/dist/cjs/Placeholder.js.map +1 -1
  44. package/dist/cjs/Platform.js +4 -4
  45. package/dist/cjs/Platform.js.map +1 -1
  46. package/dist/cjs/Render.js +1 -1
  47. package/dist/cjs/Render.js.map +1 -1
  48. package/dist/cjs/RenderContext.js +48 -27
  49. package/dist/cjs/RenderContext.js.map +1 -1
  50. package/dist/cjs/RenderTemplate.js +2 -17
  51. package/dist/cjs/RenderTemplate.js.map +1 -1
  52. package/dist/cjs/Template.js +27 -1
  53. package/dist/cjs/Template.js.map +1 -1
  54. package/dist/cjs/TemplateInstance.js +2 -2
  55. package/dist/cjs/TemplateInstance.js.map +1 -1
  56. package/dist/cjs/Test.js +20 -7
  57. package/dist/cjs/Test.js.map +1 -1
  58. package/dist/cjs/index.js +0 -12
  59. package/dist/cjs/index.js.map +1 -1
  60. package/dist/cjs/internal/EventSource.js +95 -0
  61. package/dist/cjs/internal/EventSource.js.map +1 -0
  62. package/dist/cjs/internal/browser.js +11 -11
  63. package/dist/cjs/internal/browser.js.map +1 -1
  64. package/dist/cjs/internal/hydrate.js +49 -50
  65. package/dist/cjs/internal/hydrate.js.map +1 -1
  66. package/dist/cjs/internal/indexRefCounter.js +49 -2
  67. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  68. package/dist/cjs/internal/parser.js +60 -17
  69. package/dist/cjs/internal/parser.js.map +1 -1
  70. package/dist/cjs/internal/parts.js +128 -28
  71. package/dist/cjs/internal/parts.js.map +1 -1
  72. package/dist/cjs/internal/render.js +486 -53
  73. package/dist/cjs/internal/render.js.map +1 -1
  74. package/dist/cjs/internal/server.js +5 -2
  75. package/dist/cjs/internal/server.js.map +1 -1
  76. package/dist/dts/Directive.d.ts.map +1 -1
  77. package/dist/dts/ElementRef.d.ts +4 -2
  78. package/dist/dts/ElementRef.d.ts.map +1 -1
  79. package/dist/dts/ElementSource.d.ts +10 -5
  80. package/dist/dts/ElementSource.d.ts.map +1 -1
  81. package/dist/dts/EventHandler.d.ts.map +1 -1
  82. package/dist/dts/Html.d.ts +1 -1
  83. package/dist/dts/Html.d.ts.map +1 -1
  84. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  85. package/dist/dts/Many.d.ts +13 -11
  86. package/dist/dts/Many.d.ts.map +1 -1
  87. package/dist/dts/Parser.d.ts +3 -6
  88. package/dist/dts/Parser.d.ts.map +1 -1
  89. package/dist/dts/Part.d.ts +13 -3
  90. package/dist/dts/Part.d.ts.map +1 -1
  91. package/dist/dts/Placeholder.d.ts +2 -2
  92. package/dist/dts/Placeholder.d.ts.map +1 -1
  93. package/dist/dts/Render.d.ts +2 -1
  94. package/dist/dts/Render.d.ts.map +1 -1
  95. package/dist/dts/RenderContext.d.ts +5 -4
  96. package/dist/dts/RenderContext.d.ts.map +1 -1
  97. package/dist/dts/RenderTemplate.d.ts +2 -16
  98. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  99. package/dist/dts/Renderable.d.ts +2 -2
  100. package/dist/dts/Renderable.d.ts.map +1 -1
  101. package/dist/dts/Template.d.ts +21 -3
  102. package/dist/dts/Template.d.ts.map +1 -1
  103. package/dist/dts/TemplateInstance.d.ts +3 -2
  104. package/dist/dts/TemplateInstance.d.ts.map +1 -1
  105. package/dist/dts/Test.d.ts +4 -6
  106. package/dist/dts/Test.d.ts.map +1 -1
  107. package/dist/dts/index.d.ts +0 -4
  108. package/dist/dts/index.d.ts.map +1 -1
  109. package/dist/dts/internal/EventSource.d.ts +12 -0
  110. package/dist/dts/internal/EventSource.d.ts.map +1 -0
  111. package/dist/dts/internal/browser.d.ts.map +1 -1
  112. package/dist/dts/internal/hydrate.d.ts +5 -5
  113. package/dist/dts/internal/hydrate.d.ts.map +1 -1
  114. package/dist/dts/internal/indexRefCounter.d.ts +5 -0
  115. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  116. package/dist/dts/internal/module-augmentation.d.ts +0 -4
  117. package/dist/dts/internal/module-augmentation.d.ts.map +1 -1
  118. package/dist/dts/internal/parser.d.ts +8 -0
  119. package/dist/dts/internal/parser.d.ts.map +1 -1
  120. package/dist/dts/internal/parts.d.ts +66 -56
  121. package/dist/dts/internal/parts.d.ts.map +1 -1
  122. package/dist/dts/internal/render.d.ts +7 -7
  123. package/dist/dts/internal/render.d.ts.map +1 -1
  124. package/dist/dts/internal/server.d.ts.map +1 -1
  125. package/dist/esm/Directive.js +1 -1
  126. package/dist/esm/Directive.js.map +1 -1
  127. package/dist/esm/ElementRef.js +12 -7
  128. package/dist/esm/ElementRef.js.map +1 -1
  129. package/dist/esm/ElementSource.js +16 -13
  130. package/dist/esm/ElementSource.js.map +1 -1
  131. package/dist/esm/EventHandler.js +1 -1
  132. package/dist/esm/EventHandler.js.map +1 -1
  133. package/dist/esm/Html.js +29 -24
  134. package/dist/esm/Html.js.map +1 -1
  135. package/dist/esm/HtmlChunk.js +6 -1
  136. package/dist/esm/HtmlChunk.js.map +1 -1
  137. package/dist/esm/Hydrate.js +1 -1
  138. package/dist/esm/Hydrate.js.map +1 -1
  139. package/dist/esm/Many.js +14 -10
  140. package/dist/esm/Many.js.map +1 -1
  141. package/dist/esm/Parser.js +6 -335
  142. package/dist/esm/Parser.js.map +1 -1
  143. package/dist/esm/Placeholder.js +2 -2
  144. package/dist/esm/Placeholder.js.map +1 -1
  145. package/dist/esm/Platform.js +2 -2
  146. package/dist/esm/Platform.js.map +1 -1
  147. package/dist/esm/Render.js +1 -1
  148. package/dist/esm/Render.js.map +1 -1
  149. package/dist/esm/RenderContext.js +38 -17
  150. package/dist/esm/RenderContext.js.map +1 -1
  151. package/dist/esm/RenderTemplate.js +2 -12
  152. package/dist/esm/RenderTemplate.js.map +1 -1
  153. package/dist/esm/Template.js +24 -0
  154. package/dist/esm/Template.js.map +1 -1
  155. package/dist/esm/TemplateInstance.js +2 -2
  156. package/dist/esm/TemplateInstance.js.map +1 -1
  157. package/dist/esm/Test.js +20 -7
  158. package/dist/esm/Test.js.map +1 -1
  159. package/dist/esm/index.js +0 -4
  160. package/dist/esm/index.js.map +1 -1
  161. package/dist/esm/internal/EventSource.js +91 -0
  162. package/dist/esm/internal/EventSource.js.map +1 -0
  163. package/dist/esm/internal/browser.js +12 -12
  164. package/dist/esm/internal/browser.js.map +1 -1
  165. package/dist/esm/internal/hydrate.js +45 -46
  166. package/dist/esm/internal/hydrate.js.map +1 -1
  167. package/dist/esm/internal/indexRefCounter.js +50 -2
  168. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  169. package/dist/esm/internal/parser.js +84 -17
  170. package/dist/esm/internal/parser.js.map +1 -1
  171. package/dist/esm/internal/parts.js +110 -27
  172. package/dist/esm/internal/parts.js.map +1 -1
  173. package/dist/esm/internal/render.js +476 -58
  174. package/dist/esm/internal/render.js.map +1 -1
  175. package/dist/esm/internal/server.js +5 -4
  176. package/dist/esm/internal/server.js.map +1 -1
  177. package/package.json +10 -26
  178. package/src/Directive.ts +1 -1
  179. package/src/ElementRef.ts +18 -14
  180. package/src/ElementSource.ts +62 -47
  181. package/src/EventHandler.ts +1 -1
  182. package/src/Html.ts +58 -57
  183. package/src/HtmlChunk.ts +15 -1
  184. package/src/Hydrate.ts +1 -1
  185. package/src/Many.ts +53 -43
  186. package/src/Parser.ts +10 -453
  187. package/src/Part.ts +15 -3
  188. package/src/Placeholder.ts +4 -4
  189. package/src/Platform.ts +2 -2
  190. package/src/Render.ts +7 -2
  191. package/src/RenderContext.ts +49 -21
  192. package/src/RenderTemplate.ts +9 -54
  193. package/src/Renderable.ts +2 -1
  194. package/src/Template.ts +26 -0
  195. package/src/TemplateInstance.ts +9 -9
  196. package/src/Test.ts +40 -21
  197. package/src/index.ts +0 -4
  198. package/src/internal/EventSource.ts +153 -0
  199. package/src/internal/browser.ts +26 -25
  200. package/src/internal/hydrate.ts +68 -61
  201. package/src/internal/indexRefCounter.ts +63 -2
  202. package/src/internal/module-augmentation.ts +0 -4
  203. package/src/internal/parser.ts +92 -19
  204. package/src/internal/parts.ts +158 -73
  205. package/src/internal/render.ts +701 -89
  206. package/src/internal/server.ts +5 -3
  207. package/dist/cjs/Token.js +0 -270
  208. package/dist/cjs/Token.js.map +0 -1
  209. package/dist/cjs/Tokenizer.js +0 -18
  210. package/dist/cjs/Tokenizer.js.map +0 -1
  211. package/dist/cjs/internal/readAttribute.js +0 -34
  212. package/dist/cjs/internal/readAttribute.js.map +0 -1
  213. package/dist/cjs/internal/tokenizer.js +0 -264
  214. package/dist/cjs/internal/tokenizer.js.map +0 -1
  215. package/dist/dts/Token.d.ts +0 -202
  216. package/dist/dts/Token.d.ts.map +0 -1
  217. package/dist/dts/Tokenizer.d.ts +0 -6
  218. package/dist/dts/Tokenizer.d.ts.map +0 -1
  219. package/dist/dts/internal/readAttribute.d.ts +0 -9
  220. package/dist/dts/internal/readAttribute.d.ts.map +0 -1
  221. package/dist/dts/internal/tokenizer.d.ts +0 -3
  222. package/dist/dts/internal/tokenizer.d.ts.map +0 -1
  223. package/dist/esm/Token.js +0 -264
  224. package/dist/esm/Token.js.map +0 -1
  225. package/dist/esm/Tokenizer.js +0 -9
  226. package/dist/esm/Tokenizer.js.map +0 -1
  227. package/dist/esm/internal/readAttribute.js +0 -24
  228. package/dist/esm/internal/readAttribute.js.map +0 -1
  229. package/dist/esm/internal/tokenizer.js +0 -296
  230. package/dist/esm/internal/tokenizer.js.map +0 -1
  231. package/src/Token.ts +0 -269
  232. package/src/Tokenizer.ts +0 -10
  233. package/src/internal/readAttribute.ts +0 -28
  234. package/src/internal/tokenizer.ts +0 -338
package/src/Html.ts CHANGED
@@ -3,14 +3,13 @@
3
3
  */
4
4
 
5
5
  import * as Fx from "@typed/fx/Fx"
6
- import { Sink } from "@typed/fx/Sink"
6
+ import * as Sink from "@typed/fx/Sink"
7
7
  import { TypeId } from "@typed/fx/TypeId"
8
- import type { Rendered } from "@typed/wire"
9
- import { Effect, Option } from "effect"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Option from "effect/Option"
10
10
  import { join } from "effect/ReadonlyArray"
11
11
  import type * as Scope from "effect/Scope"
12
12
  import { isDirective } from "./Directive.js"
13
- import * as ElementRef from "./ElementRef.js"
14
13
  import type { ServerEntry } from "./Entry.js"
15
14
  import type { HtmlChunk, PartChunk, SparsePartChunk, TextChunk } from "./HtmlChunk.js"
16
15
  import { templateToHtmlChunks } from "./HtmlChunk.js"
@@ -23,10 +22,11 @@ import { RenderContext } from "./RenderContext.js"
23
22
  import { HtmlRenderEvent, isRenderEvent } from "./RenderEvent.js"
24
23
  import type { RenderEvent } from "./RenderEvent.js"
25
24
  import { RenderTemplate } from "./RenderTemplate.js"
26
- import { TemplateInstance } from "./TemplateInstance.js"
27
25
 
28
26
  const toHtml = (r: RenderEvent) => (r as HtmlRenderEvent).html
29
27
 
28
+ const [padStart, padEnd] = [[TYPED_START], [TYPED_END]] as const
29
+
30
30
  /**
31
31
  * @since 1.0.0
32
32
  */
@@ -38,8 +38,7 @@ export function renderToHtml<R, E>(
38
38
  fx.pipe(
39
39
  Fx.provide(RenderTemplate.layer(renderHtml(ctx))),
40
40
  Fx.map(toHtml),
41
- Fx.startWith(TYPED_START),
42
- Fx.endWith(TYPED_END)
41
+ Fx.padWith(padStart, padEnd)
43
42
  )
44
43
  )
45
44
  )
@@ -55,41 +54,30 @@ export function renderToHtmlString<R, E>(
55
54
  }
56
55
 
57
56
  function renderHtml(ctx: RenderContext) {
58
- return <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
57
+ return <Values extends ReadonlyArray<Renderable<any, any>>>(
59
58
  templateStrings: TemplateStringsArray,
60
- values: Values,
61
- providedRef?: ElementRef.ElementRef<T>
62
- ): Effect.Effect<
59
+ values: Values
60
+ ): Fx.Fx<
63
61
  Scope.Scope | Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
64
- never,
65
- TemplateInstance<
66
- Placeholder.Error<Values[number]>,
67
- T
68
- >
62
+ Placeholder.Error<Values[number]>,
63
+ RenderEvent
69
64
  > => {
70
- return Effect.gen(function*(_) {
71
- const ref = providedRef || (yield* _(ElementRef.make()))
72
- const entry = getServerEntry(templateStrings, ctx.templateCache)
73
-
74
- if (values.length === 0) {
75
- return TemplateInstance(Fx.succeed(HtmlRenderEvent((entry.chunks[0] as TextChunk).value)), ref as any)
76
- } else {
77
- return TemplateInstance(
78
- Fx.filter(
79
- Fx.mergeBuffer(
80
- entry.chunks.map((chunk) =>
81
- renderChunk<
82
- Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
83
- Placeholder.Error<Values[number]>
84
- >(chunk, values)
85
- )
86
- ),
87
- (x) => (x.valueOf() as string).length > 0
88
- ) as any,
89
- ref as any
90
- )
91
- }
92
- })
65
+ const entry = getServerEntry(templateStrings, ctx.templateCache)
66
+ if (values.length === 0) {
67
+ return Fx.succeed(HtmlRenderEvent((entry.chunks[0] as TextChunk).value))
68
+ } else {
69
+ return Fx.filter(
70
+ Fx.mergeOrdered(
71
+ entry.chunks.map((chunk) =>
72
+ renderChunk<
73
+ Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
74
+ Placeholder.Error<Values[number]>
75
+ >(chunk, values)
76
+ )
77
+ ),
78
+ (x) => (x.valueOf() as string).length > 0
79
+ )
80
+ }
93
81
  }
94
82
  }
95
83
 
@@ -125,7 +113,7 @@ function renderObject<R, E>(renderable: object | null | undefined) {
125
113
  if (renderable === null || renderable === undefined) {
126
114
  return Fx.succeed(HtmlRenderEvent(TEXT_START))
127
115
  } else if (Array.isArray(renderable)) {
128
- return Fx.mergeBuffer(renderable.map(renderNode)) as any
116
+ return Fx.mergeOrdered(renderable.map(renderNode)) as any
129
117
  } else if (Fx.isFx<R, E, Renderable>(renderable)) {
130
118
  return Fx.concatMap(takeOneIfNotRenderEvent(renderable), renderNode as any)
131
119
  } else if (Effect.isEffect(renderable)) {
@@ -146,7 +134,7 @@ function renderPart<R, E>(
146
134
 
147
135
  // Refs and events are not rendered into HTML
148
136
  if (isDirective<R, E>(renderable)) {
149
- return Fx.fromSink((sink: Sink<E, RenderEvent>) => {
137
+ return Fx.make((sink: Sink.Sink<never, E, RenderEvent>) => {
150
138
  const part = partNodeToPart(
151
139
  node,
152
140
  (value) => sink.onSuccess(HtmlRenderEvent(render(value)))
@@ -155,8 +143,21 @@ function renderPart<R, E>(
155
143
  return Effect.catchAllCause(renderable(part), sink.onFailure)
156
144
  })
157
145
  } else if (node._tag === "node") {
158
- return Fx.endWith(renderNode<R, E>(renderable), HtmlRenderEvent(TYPED_HOLE(node.index)))
146
+ return Fx.append(renderNode<R, E>(renderable), HtmlRenderEvent(TYPED_HOLE(node.index)))
147
+ } else if (node._tag === "properties") {
148
+ if (renderable == null) return Fx.empty
149
+ return Fx.map(
150
+ Fx.take(
151
+ Fx.struct(
152
+ Object.fromEntries(Object.entries(renderable).map(([k, v]) => [k, unwrapRenderable(v)] as const))
153
+ ),
154
+ 1
155
+ ),
156
+ render
157
+ ) as any
159
158
  } else {
159
+ if (renderable === null) return Fx.succeed(HtmlRenderEvent(render(renderable)))
160
+
160
161
  const html = Fx.filterMap(Fx.take(unwrapRenderable<R, E>(renderable), 1), (value) => {
161
162
  const s = render(value)
162
163
 
@@ -164,7 +165,7 @@ function renderPart<R, E>(
164
165
  })
165
166
 
166
167
  if (node._tag === "text-part") {
167
- return Fx.endWith(Fx.startWith(html, HtmlRenderEvent(TEXT_START)), HtmlRenderEvent(TYPED_HOLE(node.index)))
168
+ return Fx.append(Fx.prepend(html, HtmlRenderEvent(TEXT_START)), HtmlRenderEvent(TYPED_HOLE(node.index)))
168
169
  }
169
170
 
170
171
  return html
@@ -179,14 +180,14 @@ function renderSparsePart<R, E>(
179
180
 
180
181
  return Fx.map(
181
182
  Fx.take(
182
- Fx.combine(
183
+ Fx.tuple(
183
184
  node.nodes.map((node) => {
184
185
  if (node._tag === "text") return Fx.succeed(node.value)
185
186
 
186
187
  const renderable: Renderable<any, any> = (values as any)[node.index]
187
188
 
188
189
  if (isDirective<R, E>(renderable)) {
189
- return Fx.fromSink<R, E, unknown>((sink: Sink<E, unknown>) =>
190
+ return Fx.make<R, E, unknown>((sink: Sink.Sink<never, E, unknown>) =>
190
191
  Effect.catchAllCause(
191
192
  renderable(partNodeToPart(node, (value) => sink.onSuccess(value))),
192
193
  sink.onFailure
@@ -204,17 +205,15 @@ function renderSparsePart<R, E>(
204
205
  }
205
206
 
206
207
  function takeOneIfNotRenderEvent<R, E, A>(fx: Fx.Fx<R, E, A>): Fx.Fx<R, E, A> {
207
- return Fx.withEarlyExit(({ fork, sink }) =>
208
- Fx.run(
209
- fx,
210
- Sink(
211
- sink.onFailure,
212
- (event) => isRenderEvent(event) ? sink.onSuccess(event) : Effect.zipRight(sink.onSuccess(event), sink.earlyExit)
213
- )
214
- ).pipe(
215
- fork,
216
- Effect.fromFiberEffect
217
- )
208
+ return Fx.make<R, E, A>((sink) =>
209
+ Sink.withEarlyExit(sink, (sink) =>
210
+ fx.run(
211
+ Sink.make(
212
+ sink.onFailure,
213
+ (event) =>
214
+ isRenderEvent(event) ? sink.onSuccess(event) : Effect.zipRight(sink.onSuccess(event), sink.earlyExit)
215
+ )
216
+ ))
218
217
  )
219
218
  }
220
219
 
@@ -233,6 +232,8 @@ function getServerEntry(
233
232
  chunks: templateToHtmlChunks(template)
234
233
  }
235
234
 
235
+ templateCache.set(templateStrings, entry)
236
+
236
237
  return entry
237
238
  } else {
238
239
  return cached
@@ -245,7 +246,7 @@ function unwrapRenderable<R, E>(renderable: Renderable<any, any>): Fx.Fx<R, E, a
245
246
  case "object": {
246
247
  if (renderable === null || renderable === undefined) return Fx.succeed(null)
247
248
  else if (Array.isArray(renderable)) {
248
- return Fx.combine(renderable.map(unwrapRenderable)) as any
249
+ return Fx.tuple(renderable.map(unwrapRenderable)) as any
249
250
  } else if (TypeId in renderable) {
250
251
  return renderable as any
251
252
  } else if (Effect.EffectTypeId in renderable) {
package/src/HtmlChunk.ts CHANGED
@@ -64,7 +64,9 @@ export type AttrValue = string | null | undefined | ReadonlyArray<AttrValue>
64
64
  * @since 1.0.0
65
65
  */
66
66
  export function templateToHtmlChunks({ hash, nodes }: Template) {
67
- return fuseTextChunks(nodes.flatMap((node) => nodeToHtmlChunk(node, hash)))
67
+ const chunks = fuseTextChunks(nodes.flatMap((node) => nodeToHtmlChunk(node, hash)))
68
+
69
+ return chunks
68
70
  }
69
71
 
70
72
  function fuseTextChunks(chunks: Array<HtmlChunk>): ReadonlyArray<HtmlChunk> {
@@ -94,6 +96,7 @@ type NodeMap = {
94
96
  }
95
97
 
96
98
  const nodeMap: NodeMap = {
99
+ doctype: (node) => [new TextChunk(`<!DOCTYPE ${node.name}>`)],
97
100
  element: elementToHtmlChunks,
98
101
  node: (node) => [new PartChunk(node, String)],
99
102
  "self-closing-element": selfClosingElementToHtmlChunks,
@@ -201,6 +204,17 @@ const attrMap: AttrMap = {
201
204
  new PartChunk(attr, (value) => value == null ? `` : datasetToString(value as Readonly<Record<string, string>>)),
202
205
  event: () => new TextChunk(""),
203
206
  property: (attr) => new PartChunk(attr, (value) => (value == null ? `` : ` ${attr.name}="${escape(value)}"`)),
207
+ properties: (attr) =>
208
+ new PartChunk(
209
+ attr,
210
+ (
211
+ value
212
+ ) => (value == null
213
+ ? ``
214
+ : " " + Object.entries(value).map(([key, value]) =>
215
+ value === true ? key : value === false ? "" : `${key}="${escape(value)}"`
216
+ ).join(" "))
217
+ ),
204
218
  ref: () => new TextChunk(""),
205
219
  "sparse-attr": (attr) =>
206
220
  new SparsePartChunk(attr, (values) => {
package/src/Hydrate.ts CHANGED
@@ -49,5 +49,5 @@ export function hydrate<R, E, T extends RenderEvent | null>(
49
49
  export function hydrateLayer<R, E, T extends RenderEvent | null>(
50
50
  rendered: Fx.Fx<R, E, T>
51
51
  ) {
52
- return Fx.drainLayer(Fx.switchMapCause(hydrate(rendered), Effect.logError))
52
+ return Fx.drainLayer(Fx.switchMapCause(hydrate(rendered), (cause) => Fx.fromEffect(Effect.logError(cause))))
53
53
  }
package/src/Many.ts CHANGED
@@ -2,44 +2,54 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import type * as AsyncData from "@typed/async-data/AsyncData"
5
- import type * as Computed from "@typed/fx/Computed"
5
+ import * as RefAsyncData from "@typed/fx/AsyncData"
6
6
  import * as Fx from "@typed/fx/Fx"
7
- import * as RefAsyncData from "@typed/fx/RefAsyncData"
8
7
  import * as RefSubject from "@typed/fx/RefSubject"
9
- import { makeHold } from "@typed/fx/Subject"
8
+ import type { Scope } from "effect"
10
9
  import * as Effect from "effect/Effect"
11
10
  import { dual } from "effect/Function"
11
+ import type { NoInfer } from "effect/Types"
12
12
  import { RenderContext } from "./RenderContext.js"
13
13
  import { type RenderEvent } from "./RenderEvent.js"
14
14
 
15
15
  /**
16
16
  * @since 1.0.0
17
17
  */
18
- export function many<R, E, A, B, R2, E2>(
18
+ export function many<R, E, A, B extends PropertyKey, R2, E2>(
19
19
  values: Fx.Fx<R, E, ReadonlyArray<A>>,
20
- getKey: (a: A) => B,
21
- f: (a: RefSubject.RefSubject<never, never, A>, key: B) => Fx.Fx<R2, E2, RenderEvent>
22
- ): Fx.Fx<R | R2 | RenderContext, E | E2, RenderEvent | ReadonlyArray<RenderEvent>> {
20
+ getKey: (a: NoInfer<A>) => B,
21
+ f: (a: RefSubject.RefSubject<never, never, NoInfer<A>>, key: B) => Fx.Fx<R2, E2, RenderEvent>
22
+ ): Fx.Fx<R | R2 | Scope.Scope | RenderContext, E | E2, RenderEvent | ReadonlyArray<RenderEvent>> {
23
23
  return Fx.fromFxEffect(
24
- RenderContext.with((ctx): Fx.Fx<R | R2 | RenderContext, E | E2, RenderEvent | ReadonlyArray<RenderEvent>> => {
25
- if (ctx.environment === "browser") {
26
- return Fx.keyed(values, getKey, f)
27
- }
28
-
29
- return Fx.fromFxEffect(
30
- Effect.map(Fx.first(values), (values) =>
31
- values._tag === "None" ? Fx.empty : Fx.mergeBuffer(
32
- values.value.map((value) => {
33
- const ref = RefSubject.unsafeMake(Effect.succeed(value), makeHold())
24
+ RenderContext.with(
25
+ (ctx): Fx.Fx<R | R2 | RenderContext | Scope.Scope, E | E2, RenderEvent | ReadonlyArray<RenderEvent>> => {
26
+ // When rendering HTML, we want to ensure that we order our HTML events in the same order
27
+ // as the templates are defined which is why we use mergeOrdered. We also want to ensure that
28
+ // our templates end, so we take only the first of our source values and also ensure that a subscription
29
+ // to our RefSubjects only include its first event.
30
+ if (ctx.environment === "server" || ctx.environment === "static") {
31
+ return Fx.fromFxEffect(
32
+ Effect.map(Fx.first(values), (values) =>
33
+ Fx.mergeOrdered(
34
+ values.map((value) =>
35
+ Fx.fromFxEffect(Effect.map(RefSubject.of(value), (ref) => f(RefSubject.take(ref, 1), getKey(value))))
36
+ )
37
+ ))
38
+ )
39
+ }
34
40
 
35
- return f({ ...ref, ...Fx.take(ref, 1) } as RefSubject.RefSubject<never, never, A>, getKey(value))
36
- })
37
- ))
38
- )
39
- })
41
+ // In other environments we just used Fx.keyed to allow indefinite subscriptions to RefSubjects
42
+ return Fx.keyed(values, {
43
+ getKey,
44
+ onValue: f
45
+ })
46
+ }
47
+ )
40
48
  )
41
49
  }
42
50
 
51
+ type TODO = any
52
+
43
53
  /**
44
54
  * @since 1.0.0
45
55
  */
@@ -47,18 +57,18 @@ export const manyAsyncData: {
47
57
  <
48
58
  E1,
49
59
  A,
50
- B,
51
- NoData extends Fx.FxInput<any, any, any>,
52
- Loading extends Fx.FxInput<any, any, any>,
53
- Failure extends Fx.FxInput<any, any, any>,
54
- Success extends Fx.FxInput<any, any, any>
60
+ B extends PropertyKey,
61
+ NoData extends Fx.Fx<any, any, any>,
62
+ Loading extends Fx.Fx<any, any, any>,
63
+ Failure extends Fx.Fx<any, any, any>,
64
+ Success extends Fx.Fx<any, any, any>
55
65
  >(
56
66
  getKey: (a: A) => B,
57
67
  matchers: {
58
68
  NoData: () => NoData
59
- Loading: (data: RefAsyncData.LoadingComputed) => Loading
60
- Failure: (data: Computed.Computed<never, never, E1>, computed: RefAsyncData.FailureComputed<E1>) => Failure
61
- Success: (value: Computed.Computed<never, never, A>, computed: RefAsyncData.SuccessComputed) => Success
69
+ Loading: (todo: TODO) => Loading
70
+ Failure: (data: RefSubject.Computed<never, never, E1>, computed: TODO) => Failure
71
+ Success: (value: RefSubject.Computed<never, never, A>, computed: TODO) => Success
62
72
  }
63
73
  ): <R, E>(
64
74
  fx: Fx.Fx<R, E, AsyncData.AsyncData<E1, ReadonlyArray<A>>>
@@ -73,7 +83,7 @@ export const manyAsyncData: {
73
83
  E,
74
84
  E1,
75
85
  A,
76
- B,
86
+ B extends PropertyKey,
77
87
  NoData extends Fx.Fx<any, any, any>,
78
88
  Loading extends Fx.Fx<any, any, any>,
79
89
  Failure extends Fx.Fx<any, any, any>,
@@ -83,9 +93,9 @@ export const manyAsyncData: {
83
93
  getKey: (a: A) => B,
84
94
  matchers: {
85
95
  NoData: () => NoData
86
- Loading: (data: RefAsyncData.LoadingComputed) => Loading
87
- Failure: (data: Computed.Computed<never, never, E1>, computed: RefAsyncData.FailureComputed<E1>) => Failure
88
- Success: (value: Computed.Computed<never, never, A>, computed: RefAsyncData.SuccessComputed) => Success
96
+ Loading: (data: TODO) => Loading
97
+ Failure: (data: RefSubject.Computed<never, never, E1>, computed: TODO) => Failure
98
+ Success: (value: RefSubject.Computed<never, never, A>, computed: TODO) => Success
89
99
  }
90
100
  ): Fx.Fx<
91
101
  R | Fx.Fx.Context<NoData | Loading | Failure | Success>,
@@ -99,26 +109,26 @@ export const manyAsyncData: {
99
109
  E,
100
110
  E1,
101
111
  A,
112
+ B extends PropertyKey,
102
113
  NoData extends Fx.Fx<any, any, RenderEvent>,
103
114
  Loading extends Fx.Fx<any, any, RenderEvent>,
104
115
  Failure extends Fx.Fx<any, any, RenderEvent>,
105
- Success extends Fx.Fx<any, any, RenderEvent>,
106
- B
116
+ Success extends Fx.Fx<any, any, RenderEvent>
107
117
  >(
108
118
  fx: Fx.Fx<R, E, AsyncData.AsyncData<E1, ReadonlyArray<A>>>,
119
+ getKey: (a: A) => B,
109
120
  matchers: {
110
- NoData: () => NoData
111
- Loading: (data: RefAsyncData.LoadingComputed) => Loading
112
- Failure: (data: Computed.Computed<never, never, E1>, computed: RefAsyncData.FailureComputed<E1>) => Failure
113
- Success: (value: Computed.Computed<never, never, A>, computed: RefAsyncData.SuccessComputed) => Success
114
- },
115
- getKey: (a: A) => B
121
+ NoData: NoData
122
+ Loading: (data: TODO) => Loading
123
+ Failure: (data: RefSubject.Computed<never, never, E1>, computed: TODO) => Failure
124
+ Success: (value: RefSubject.Computed<never, never, A>, computed: TODO) => Success
125
+ }
116
126
  ): Fx.Fx<
117
127
  R | Fx.Fx.Context<NoData | Loading | Failure | Success>,
118
128
  E | Fx.Fx.Error<NoData | Loading | Failure | Success>,
119
129
  Fx.Fx.Success<NoData | Loading | Failure | Success>
120
130
  > => {
121
- return RefAsyncData.matchKeyed(fx, {
131
+ return RefAsyncData.matchAsyncData(fx, {
122
132
  NoData: matchers.NoData,
123
133
  Loading: matchers.Loading,
124
134
  Failure: matchers.Failure,