@typed/template 0.9.6 → 0.10.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 (300) hide show
  1. package/RenderQueue/package.json +6 -0
  2. package/dist/cjs/Directive.js +1 -1
  3. package/dist/cjs/Directive.js.map +1 -1
  4. package/dist/cjs/ElementRef.js +1 -1
  5. package/dist/cjs/ElementRef.js.map +1 -1
  6. package/dist/cjs/ElementSource.js +1 -1
  7. package/dist/cjs/ElementSource.js.map +1 -1
  8. package/dist/cjs/EventHandler.js +11 -4
  9. package/dist/cjs/EventHandler.js.map +1 -1
  10. package/dist/cjs/Html.js +84 -44
  11. package/dist/cjs/Html.js.map +1 -1
  12. package/dist/cjs/HtmlChunk.js +67 -21
  13. package/dist/cjs/HtmlChunk.js.map +1 -1
  14. package/dist/cjs/Hydrate.js +6 -6
  15. package/dist/cjs/Hydrate.js.map +1 -1
  16. package/dist/cjs/Many.js +4 -4
  17. package/dist/cjs/Many.js.map +1 -1
  18. package/dist/cjs/Meta.js +10 -3
  19. package/dist/cjs/Meta.js.map +1 -1
  20. package/dist/cjs/Parser.js +1 -1
  21. package/dist/cjs/Placeholder.js +5 -9
  22. package/dist/cjs/Placeholder.js.map +1 -1
  23. package/dist/cjs/Platform.js +7 -5
  24. package/dist/cjs/Platform.js.map +1 -1
  25. package/dist/cjs/Render.js +8 -7
  26. package/dist/cjs/Render.js.map +1 -1
  27. package/dist/cjs/RenderContext.js +8 -92
  28. package/dist/cjs/RenderContext.js.map +1 -1
  29. package/dist/cjs/RenderEvent.js +9 -1
  30. package/dist/cjs/RenderEvent.js.map +1 -1
  31. package/dist/cjs/RenderQueue.js +341 -0
  32. package/dist/cjs/RenderQueue.js.map +1 -0
  33. package/dist/cjs/RenderTemplate.js +1 -1
  34. package/dist/cjs/RenderTemplate.js.map +1 -1
  35. package/dist/cjs/Template.js +12 -0
  36. package/dist/cjs/Template.js.map +1 -1
  37. package/dist/cjs/Test.js +64 -33
  38. package/dist/cjs/Test.js.map +1 -1
  39. package/dist/cjs/Vitest.js +12 -20
  40. package/dist/cjs/Vitest.js.map +1 -1
  41. package/dist/cjs/index.js +6 -3
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/internal/EventSource.js +16 -9
  44. package/dist/cjs/internal/EventSource.js.map +1 -1
  45. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  46. package/dist/cjs/internal/browser.js +11 -10
  47. package/dist/cjs/internal/browser.js.map +1 -1
  48. package/dist/cjs/internal/character-entities.js +2141 -0
  49. package/dist/cjs/internal/character-entities.js.map +1 -0
  50. package/dist/cjs/internal/errors.js +19 -2
  51. package/dist/cjs/internal/errors.js.map +1 -1
  52. package/dist/cjs/internal/indexRefCounter.js +36 -63
  53. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  54. package/dist/cjs/internal/parser.js +18 -17
  55. package/dist/cjs/internal/parser.js.map +1 -1
  56. package/dist/cjs/internal/parser2.js +382 -0
  57. package/dist/cjs/internal/parser2.js.map +1 -0
  58. package/dist/cjs/internal/server-parts.js +124 -0
  59. package/dist/cjs/internal/server-parts.js.map +1 -0
  60. package/dist/cjs/internal/server.js +15 -185
  61. package/dist/cjs/internal/server.js.map +1 -1
  62. package/dist/cjs/internal/utils.js +73 -9
  63. package/dist/cjs/internal/utils.js.map +1 -1
  64. package/dist/cjs/internal/v2/SyncPart.js +6 -0
  65. package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
  66. package/dist/cjs/internal/v2/helpers.js +15 -0
  67. package/dist/cjs/internal/v2/helpers.js.map +1 -0
  68. package/dist/cjs/internal/v2/hydrate.js +202 -0
  69. package/dist/cjs/internal/v2/hydrate.js.map +1 -0
  70. package/dist/cjs/internal/v2/hydration-template.js +269 -0
  71. package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
  72. package/dist/cjs/internal/v2/parts.js +169 -0
  73. package/dist/cjs/internal/v2/parts.js.map +1 -0
  74. package/dist/cjs/internal/v2/render-entry.js +110 -0
  75. package/dist/cjs/internal/v2/render-entry.js.map +1 -0
  76. package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
  77. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
  78. package/dist/cjs/internal/v2/render.js +417 -0
  79. package/dist/cjs/internal/v2/render.js.map +1 -0
  80. package/dist/cjs/internal/v2/sync-parts.js +115 -0
  81. package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
  82. package/dist/dts/ElementRef.d.ts +1 -1
  83. package/dist/dts/ElementRef.d.ts.map +1 -1
  84. package/dist/dts/ElementSource.d.ts +1 -1
  85. package/dist/dts/ElementSource.d.ts.map +1 -1
  86. package/dist/dts/EventHandler.d.ts +12 -8
  87. package/dist/dts/EventHandler.d.ts.map +1 -1
  88. package/dist/dts/Html.d.ts +6 -5
  89. package/dist/dts/Html.d.ts.map +1 -1
  90. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  91. package/dist/dts/Hydrate.d.ts +1 -3
  92. package/dist/dts/Hydrate.d.ts.map +1 -1
  93. package/dist/dts/Many.d.ts +9 -11
  94. package/dist/dts/Many.d.ts.map +1 -1
  95. package/dist/dts/Meta.d.ts +5 -1
  96. package/dist/dts/Meta.d.ts.map +1 -1
  97. package/dist/dts/Parser.d.ts +1 -1
  98. package/dist/dts/Parser.d.ts.map +1 -1
  99. package/dist/dts/Part.d.ts +20 -56
  100. package/dist/dts/Part.d.ts.map +1 -1
  101. package/dist/dts/Placeholder.d.ts +6 -10
  102. package/dist/dts/Placeholder.d.ts.map +1 -1
  103. package/dist/dts/Platform.d.ts +2 -4
  104. package/dist/dts/Platform.d.ts.map +1 -1
  105. package/dist/dts/Render.d.ts +4 -8
  106. package/dist/dts/Render.d.ts.map +1 -1
  107. package/dist/dts/RenderContext.d.ts +3 -22
  108. package/dist/dts/RenderContext.d.ts.map +1 -1
  109. package/dist/dts/RenderEvent.d.ts +6 -1
  110. package/dist/dts/RenderEvent.d.ts.map +1 -1
  111. package/dist/dts/RenderQueue.d.ts +103 -0
  112. package/dist/dts/RenderQueue.d.ts.map +1 -0
  113. package/dist/dts/RenderTemplate.d.ts +3 -2
  114. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  115. package/dist/dts/Renderable.d.ts +1 -1
  116. package/dist/dts/Template.d.ts +14 -1
  117. package/dist/dts/Template.d.ts.map +1 -1
  118. package/dist/dts/Test.d.ts +14 -1
  119. package/dist/dts/Test.d.ts.map +1 -1
  120. package/dist/dts/Vitest.d.ts +11 -8
  121. package/dist/dts/Vitest.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/EventSource.d.ts +2 -1
  125. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  126. package/dist/dts/internal/browser.d.ts +3 -3
  127. package/dist/dts/internal/browser.d.ts.map +1 -1
  128. package/dist/dts/internal/character-entities.d.ts +2133 -0
  129. package/dist/dts/internal/character-entities.d.ts.map +1 -0
  130. package/dist/dts/internal/errors.d.ts +9 -1
  131. package/dist/dts/internal/errors.d.ts.map +1 -1
  132. package/dist/dts/internal/indexRefCounter.d.ts +0 -4
  133. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  134. package/dist/dts/internal/parser.d.ts +13 -0
  135. package/dist/dts/internal/parser.d.ts.map +1 -1
  136. package/dist/dts/internal/parser2.d.ts +12 -0
  137. package/dist/dts/internal/parser2.d.ts.map +1 -0
  138. package/dist/dts/internal/server-parts.d.ts +223 -0
  139. package/dist/dts/internal/server-parts.d.ts.map +1 -0
  140. package/dist/dts/internal/server.d.ts +2 -28
  141. package/dist/dts/internal/server.d.ts.map +1 -1
  142. package/dist/dts/internal/utils.d.ts +4 -1
  143. package/dist/dts/internal/utils.d.ts.map +1 -1
  144. package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
  145. package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
  146. package/dist/dts/internal/v2/helpers.d.ts +3 -0
  147. package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
  148. package/dist/dts/internal/v2/hydrate.d.ts +7 -0
  149. package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
  150. package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
  151. package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
  152. package/dist/dts/internal/v2/parts.d.ts +245 -0
  153. package/dist/dts/internal/v2/parts.d.ts.map +1 -0
  154. package/dist/dts/internal/v2/render-entry.d.ts +6 -0
  155. package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
  156. package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
  157. package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
  158. package/dist/dts/internal/v2/render.d.ts +62 -0
  159. package/dist/dts/internal/v2/render.d.ts.map +1 -0
  160. package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
  161. package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
  162. package/dist/esm/ElementRef.js.map +1 -1
  163. package/dist/esm/EventHandler.js +14 -4
  164. package/dist/esm/EventHandler.js.map +1 -1
  165. package/dist/esm/Html.js +91 -50
  166. package/dist/esm/Html.js.map +1 -1
  167. package/dist/esm/HtmlChunk.js +75 -24
  168. package/dist/esm/HtmlChunk.js.map +1 -1
  169. package/dist/esm/Hydrate.js +5 -5
  170. package/dist/esm/Hydrate.js.map +1 -1
  171. package/dist/esm/Many.js +3 -3
  172. package/dist/esm/Many.js.map +1 -1
  173. package/dist/esm/Meta.js +7 -1
  174. package/dist/esm/Meta.js.map +1 -1
  175. package/dist/esm/Parser.js +1 -1
  176. package/dist/esm/Parser.js.map +1 -1
  177. package/dist/esm/Placeholder.js +4 -8
  178. package/dist/esm/Placeholder.js.map +1 -1
  179. package/dist/esm/Platform.js +3 -1
  180. package/dist/esm/Platform.js.map +1 -1
  181. package/dist/esm/Render.js +6 -5
  182. package/dist/esm/Render.js.map +1 -1
  183. package/dist/esm/RenderContext.js +5 -85
  184. package/dist/esm/RenderContext.js.map +1 -1
  185. package/dist/esm/RenderEvent.js +8 -1
  186. package/dist/esm/RenderEvent.js.map +1 -1
  187. package/dist/esm/RenderQueue.js +336 -0
  188. package/dist/esm/RenderQueue.js.map +1 -0
  189. package/dist/esm/RenderTemplate.js.map +1 -1
  190. package/dist/esm/Template.js +12 -0
  191. package/dist/esm/Template.js.map +1 -1
  192. package/dist/esm/Test.js +71 -30
  193. package/dist/esm/Test.js.map +1 -1
  194. package/dist/esm/Vitest.js +11 -8
  195. package/dist/esm/Vitest.js.map +1 -1
  196. package/dist/esm/index.js +4 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/internal/EventSource.js +12 -7
  199. package/dist/esm/internal/EventSource.js.map +1 -1
  200. package/dist/esm/internal/HydrateContext.js.map +1 -1
  201. package/dist/esm/internal/browser.js +10 -9
  202. package/dist/esm/internal/browser.js.map +1 -1
  203. package/dist/esm/internal/character-entities.js +2134 -0
  204. package/dist/esm/internal/character-entities.js.map +1 -0
  205. package/dist/esm/internal/errors.js +22 -2
  206. package/dist/esm/internal/errors.js.map +1 -1
  207. package/dist/esm/internal/indexRefCounter.js +36 -61
  208. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  209. package/dist/esm/internal/parser.js +18 -18
  210. package/dist/esm/internal/parser.js.map +1 -1
  211. package/dist/esm/internal/parser2.js +393 -0
  212. package/dist/esm/internal/parser2.js.map +1 -0
  213. package/dist/esm/internal/server-parts.js +109 -0
  214. package/dist/esm/internal/server-parts.js.map +1 -0
  215. package/dist/esm/internal/server.js +12 -161
  216. package/dist/esm/internal/server.js.map +1 -1
  217. package/dist/esm/internal/utils.js +71 -7
  218. package/dist/esm/internal/utils.js.map +1 -1
  219. package/dist/esm/internal/v2/SyncPart.js +5 -0
  220. package/dist/esm/internal/v2/SyncPart.js.map +1 -0
  221. package/dist/esm/internal/v2/helpers.js +12 -0
  222. package/dist/esm/internal/v2/helpers.js.map +1 -0
  223. package/dist/esm/internal/v2/hydrate.js +195 -0
  224. package/dist/esm/internal/v2/hydrate.js.map +1 -0
  225. package/dist/esm/internal/v2/hydration-template.js +265 -0
  226. package/dist/esm/internal/v2/hydration-template.js.map +1 -0
  227. package/dist/esm/internal/v2/parts.js +150 -0
  228. package/dist/esm/internal/v2/parts.js.map +1 -0
  229. package/dist/esm/internal/v2/render-entry.js +102 -0
  230. package/dist/esm/internal/v2/render-entry.js.map +1 -0
  231. package/dist/esm/internal/v2/render-sync-parts.js +265 -0
  232. package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
  233. package/dist/esm/internal/v2/render.js +353 -0
  234. package/dist/esm/internal/v2/render.js.map +1 -0
  235. package/dist/esm/internal/v2/sync-parts.js +102 -0
  236. package/dist/esm/internal/v2/sync-parts.js.map +1 -0
  237. package/package.json +20 -13
  238. package/src/ElementRef.ts +1 -1
  239. package/src/ElementSource.ts +1 -1
  240. package/src/EventHandler.ts +29 -11
  241. package/src/Html.ts +199 -90
  242. package/src/HtmlChunk.ts +77 -30
  243. package/src/Hydrate.ts +20 -14
  244. package/src/Many.ts +17 -14
  245. package/src/Meta.ts +8 -1
  246. package/src/Parser.ts +1 -1
  247. package/src/Part.ts +22 -66
  248. package/src/Placeholder.ts +17 -15
  249. package/src/Platform.ts +5 -5
  250. package/src/Render.ts +23 -26
  251. package/src/RenderContext.ts +14 -142
  252. package/src/RenderEvent.ts +10 -1
  253. package/src/RenderQueue.ts +445 -0
  254. package/src/RenderTemplate.ts +7 -2
  255. package/src/Renderable.ts +1 -1
  256. package/src/Template.ts +15 -1
  257. package/src/Test.ts +122 -38
  258. package/src/Vitest.ts +20 -10
  259. package/src/index.ts +4 -0
  260. package/src/internal/EventSource.ts +14 -8
  261. package/src/internal/HydrateContext.ts +3 -4
  262. package/src/internal/browser.ts +26 -21
  263. package/src/internal/character-entities.ts +2136 -0
  264. package/src/internal/errors.ts +30 -3
  265. package/src/internal/indexRefCounter.ts +38 -70
  266. package/src/internal/parser.ts +19 -19
  267. package/src/internal/parser2.ts +468 -0
  268. package/src/internal/server-parts.ts +161 -0
  269. package/src/internal/server.ts +16 -272
  270. package/src/internal/utils.ts +83 -7
  271. package/src/internal/v2/SyncPart.ts +112 -0
  272. package/src/internal/v2/helpers.ts +13 -0
  273. package/src/internal/v2/hydrate.ts +289 -0
  274. package/src/internal/v2/hydration-template.ts +308 -0
  275. package/src/internal/v2/parts.ts +254 -0
  276. package/src/internal/v2/render-entry.ts +131 -0
  277. package/src/internal/v2/render-sync-parts.ts +440 -0
  278. package/src/internal/v2/render.ts +588 -0
  279. package/src/internal/v2/sync-parts.ts +133 -0
  280. package/dist/cjs/internal/hydrate.js +0 -274
  281. package/dist/cjs/internal/hydrate.js.map +0 -1
  282. package/dist/cjs/internal/parts.js +0 -451
  283. package/dist/cjs/internal/parts.js.map +0 -1
  284. package/dist/cjs/internal/render.js +0 -704
  285. package/dist/cjs/internal/render.js.map +0 -1
  286. package/dist/dts/internal/hydrate.d.ts +0 -33
  287. package/dist/dts/internal/hydrate.d.ts.map +0 -1
  288. package/dist/dts/internal/parts.d.ts +0 -314
  289. package/dist/dts/internal/parts.d.ts.map +0 -1
  290. package/dist/dts/internal/render.d.ts +0 -16
  291. package/dist/dts/internal/render.d.ts.map +0 -1
  292. package/dist/esm/internal/hydrate.js +0 -239
  293. package/dist/esm/internal/hydrate.js.map +0 -1
  294. package/dist/esm/internal/parts.js +0 -373
  295. package/dist/esm/internal/parts.js.map +0 -1
  296. package/dist/esm/internal/render.js +0 -689
  297. package/dist/esm/internal/render.js.map +0 -1
  298. package/src/internal/hydrate.ts +0 -366
  299. package/src/internal/parts.ts +0 -609
  300. package/src/internal/render.ts +0 -971
@@ -0,0 +1,289 @@
1
+ import * as Fx from "@typed/fx"
2
+ import { isText } from "@typed/wire"
3
+ import type * as Chunk from "effect/Chunk"
4
+ import { unsafeGet } from "effect/Context"
5
+ import * as Effect from "effect/Effect"
6
+ import { flow } from "effect/Function"
7
+ import * as Scope from "effect/Scope"
8
+ import type { BrowserEntry } from "../../Entry.js"
9
+ import type { Placeholder } from "../../Placeholder.js"
10
+ import type { Renderable } from "../../Renderable.js"
11
+ import type { RenderContext } from "../../RenderContext.js"
12
+ import { DomRenderEvent, type RenderEvent } from "../../RenderEvent.js"
13
+ import type { RenderQueue } from "../../RenderQueue.js"
14
+ import type { RenderTemplate } from "../../RenderTemplate.js"
15
+ import type * as Template from "../../Template.js"
16
+ import { CouldNotFindCommentError, isHydrationError } from "../errors.js"
17
+ import { HydrateContext } from "../HydrateContext.js"
18
+ import { findHydratePath, isCommentWithValue, type ParentChildNodes } from "../utils.js"
19
+ import type { HydrationHole, HydrationNode, HydrationTemplate } from "./hydration-template.js"
20
+ import {
21
+ findHydrationHole,
22
+ findHydrationMany,
23
+ findHydrationTemplate,
24
+ getChildNodes,
25
+ getNodes,
26
+ getPreviousNodes
27
+ } from "./hydration-template.js"
28
+ import { getBrowserEntry } from "./render-entry.js"
29
+ import type { TemplateContext } from "./render.js"
30
+ import {
31
+ makeTemplateContext,
32
+ renderTemplate,
33
+ setupAttrPart,
34
+ setupBooleanPart,
35
+ setupClassNamePart,
36
+ setupCommentPart,
37
+ setupDataPart,
38
+ setupEventPart,
39
+ setupNodePart,
40
+ setupPropertiesPart,
41
+ setupPropertyPart,
42
+ setupRefPart,
43
+ setupSparseAttrPart,
44
+ setupSparseClassNamePart,
45
+ setupSparseCommentPart,
46
+ setupTextPart
47
+ } from "./render.js"
48
+
49
+ type HydrateTemplateContext = TemplateContext & {
50
+ readonly where: HydrationNode
51
+ readonly manyKey: string | undefined
52
+ readonly makeHydrateContext: (where: HydrationNode, index: number) => HydrateContext
53
+ }
54
+
55
+ function findWhere(hydrateCtx: HydrateContext, entry: BrowserEntry): HydrationTemplate | null {
56
+ // If there is not a manyKey, we can just find the template by its hash
57
+ if (hydrateCtx.manyKey === undefined) {
58
+ return findHydrationTemplate(getChildNodes(hydrateCtx.where), entry.template.hash)
59
+ }
60
+
61
+ // If there is a manyKey, we need to find the many node first
62
+ const many = findHydrationMany(getChildNodes(hydrateCtx.where), hydrateCtx.manyKey)
63
+
64
+ if (many === null) return null
65
+
66
+ // Then we can find the template by its hash
67
+ return findHydrationTemplate(getChildNodes(many), entry.template.hash)
68
+ }
69
+
70
+ export const hydrateTemplate: (document: Document, ctx: RenderContext) => RenderTemplate = (
71
+ document,
72
+ renderContext
73
+ ) => {
74
+ const render_ = renderTemplate(document, renderContext)
75
+
76
+ return <Values extends ReadonlyArray<Renderable<any, any>>>(
77
+ templateStrings: TemplateStringsArray,
78
+ values: Values
79
+ ): Fx.Fx<
80
+ RenderEvent,
81
+ Placeholder.Error<Values[number]>,
82
+ Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>
83
+ > => {
84
+ const entry = getBrowserEntry(document, renderContext, templateStrings)
85
+
86
+ return Fx.make((sink) =>
87
+ Effect.catchAllDefect(
88
+ Effect.gen(function*() {
89
+ const ctx = yield* makeTemplateContext(entry.content, document, renderContext, values, sink.onFailure)
90
+ const hydrateCtx = unsafeGet(ctx.context, HydrateContext)
91
+
92
+ // If we're not longer hydrating, just render normally
93
+ if (hydrateCtx.hydrate === false) {
94
+ return yield* render_(templateStrings, values).run(sink)
95
+ }
96
+
97
+ const where: HydrationTemplate | null = findWhere(hydrateCtx, entry)
98
+
99
+ if (where === null) {
100
+ hydrateCtx.hydrate = false
101
+ return yield* render_(templateStrings, values).run(sink)
102
+ }
103
+
104
+ const wire = getWire(where)
105
+ if (entry.template.parts.length === 0) return yield* sink.onSuccess(DomRenderEvent(wire))
106
+
107
+ const makeHydrateContext = (where: HydrationNode): HydrateContext => ({
108
+ where,
109
+ parentTemplate: entry.template,
110
+ hydrate: true
111
+ })
112
+
113
+ const effects = setupParts(entry.template.parts, {
114
+ ...ctx,
115
+ where,
116
+ manyKey: hydrateCtx.manyKey,
117
+ makeHydrateContext
118
+ })
119
+ if (effects.length > 0) {
120
+ yield* Effect.forEach(effects, flow(Effect.catchAllCause(ctx.onCause), Effect.forkIn(ctx.scope)))
121
+ }
122
+
123
+ // Setup our event listeners for our wire.
124
+ // We use the parentScope to allow event listeners to exist
125
+ // beyond the lifetime of the current Fiber, but no further than its parent template.
126
+ yield* ctx.eventSource.setup(wire, ctx.parentScope)
127
+
128
+ // We're done setting up so we can resume in "regular" rendering mode when
129
+ // and new templates come in.
130
+ hydrateCtx.hydrate = false
131
+
132
+ // Emit our DomRenderEvent
133
+ yield* sink.onSuccess(DomRenderEvent(wire)).pipe(
134
+ // Ensure our templates last forever in the DOM environment
135
+ // so event listeners are kept attached to the current Scope.
136
+ Effect.zipRight(Effect.never),
137
+ // Close our scope whenever the current Fiber is interrupted
138
+ Effect.onExit((exit) => Scope.close(ctx.scope, exit))
139
+ )
140
+ }),
141
+ (defect) =>
142
+ Effect.gen(function*(_) {
143
+ if (isHydrationError(defect)) {
144
+ yield* _(Effect.logError(defect))
145
+ const hydrateCtx = yield* _(HydrateContext)
146
+ hydrateCtx.hydrate = false
147
+ return yield* _(render_(templateStrings, values).run(sink))
148
+ } else {
149
+ return yield* _(Effect.die(defect))
150
+ }
151
+ })
152
+ )
153
+ )
154
+ }
155
+ }
156
+
157
+ function setupParts(parts: Template.Template["parts"], ctx: HydrateTemplateContext) {
158
+ const effects: Array<Effect.Effect<void, any, any>> = []
159
+
160
+ for (const [part, path] of parts) {
161
+ const effect = setupPart(part, path, ctx)
162
+ if (effect) {
163
+ effects.push(effect)
164
+ }
165
+ }
166
+
167
+ return effects
168
+ }
169
+
170
+ function setupPart(
171
+ part: Template.PartNode | Template.SparsePartNode,
172
+ path: Chunk.Chunk<number>,
173
+ ctx: HydrateTemplateContext
174
+ ) {
175
+ switch (part._tag) {
176
+ case "attr":
177
+ return setupAttrPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
178
+ case "boolean-part":
179
+ return setupBooleanPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
180
+ case "className-part":
181
+ return setupClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
182
+ case "comment-part":
183
+ return setupCommentPart(part, findHydratePath(ctx.where, path) as any, ctx)
184
+ case "data":
185
+ return setupDataPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
186
+ case "event":
187
+ return setupEventPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
188
+ case "node": {
189
+ const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
190
+ if (hole === null) {
191
+ throw new CouldNotFindCommentError(part.index)
192
+ }
193
+ return setupHydratedNodePart(part, hole, ctx)
194
+ }
195
+ case "properties":
196
+ return setupPropertiesPart(part, findHydratePath(ctx.where, path) as any, ctx)
197
+ case "property":
198
+ return setupPropertyPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
199
+ case "ref":
200
+ return setupRefPart(part, findHydratePath(ctx.where, path) as any, ctx)
201
+ case "sparse-attr":
202
+ return setupSparseAttrPart(part, findHydratePath(ctx.where, path) as any, ctx)
203
+ case "sparse-class-name":
204
+ return setupSparseClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx)
205
+ case "sparse-comment":
206
+ return setupSparseCommentPart(part, findHydratePath(ctx.where, path) as any, ctx)
207
+ case "text-part": {
208
+ const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
209
+ if (hole === null) throw new CouldNotFindCommentError(part.index)
210
+ return setupTextPart(part, hole.endComment, ctx)
211
+ }
212
+ }
213
+ }
214
+
215
+ function setupHydratedNodePart(
216
+ part: Template.NodePart,
217
+ hole: HydrationHole,
218
+ ctx: HydrateTemplateContext
219
+ ) {
220
+ const nestedCtx = ctx.makeHydrateContext(hole, part.index)
221
+ const previousNodes = getPreviousNodes(hole)
222
+ const text = previousNodes.length === 2 && isCommentWithValue(previousNodes[0], "text") && isText(previousNodes[1])
223
+ ? previousNodes[1]
224
+ : null
225
+ const effect = setupNodePart(part, hole.endComment, ctx, text, text === null ? previousNodes : [text])
226
+ if (effect === null) return null
227
+ return Effect.provideService(effect, HydrateContext, nestedCtx)
228
+ }
229
+
230
+ export function findRootParentChildNodes(where: HTMLElement): ParentChildNodes {
231
+ const childNodes = findRootChildNodes(where)
232
+
233
+ return {
234
+ parentNode: where,
235
+ childNodes
236
+ }
237
+ }
238
+
239
+ const START = "typed-start"
240
+ const END = "typed-end"
241
+
242
+ // Finds all of the childNodes between the "typed-start" and "typed-end" comments
243
+ export function findRootChildNodes(where: HTMLElement): Array<Node> {
244
+ let start = -1
245
+ let end = -1
246
+
247
+ const { childNodes } = where
248
+ const length = childNodes.length
249
+
250
+ for (let i = 0; i < length; i++) {
251
+ const node = childNodes[i]
252
+
253
+ if (node.nodeType === node.COMMENT_NODE && node.nodeValue === START) {
254
+ start = i
255
+ break
256
+ }
257
+ }
258
+
259
+ for (let i = length - 1; i >= Math.max(start, 0); i--) {
260
+ const node = childNodes[i]
261
+
262
+ if (node.nodeType === node.COMMENT_NODE && node.nodeValue === END) {
263
+ end = i
264
+ break
265
+ }
266
+ }
267
+
268
+ // If we can't find the start and end comments, just return all childNodes
269
+ if (start === -1 && end === -1) {
270
+ return Array.from(childNodes)
271
+ }
272
+
273
+ start = start === -1 ? 0 : start
274
+ end = end === -1 ? length - 1 : end
275
+
276
+ const rootChildNodes: Array<Node> = Array(end - start)
277
+
278
+ for (let i = start + 1, j = 0; i <= end; i++) {
279
+ rootChildNodes[j++] = childNodes[i]
280
+ }
281
+
282
+ return rootChildNodes
283
+ }
284
+
285
+ function getWire(where: HydrationNode) {
286
+ const nodes = getNodes(where)
287
+ if (nodes.length === 1) return nodes[0]
288
+ return nodes
289
+ }
@@ -0,0 +1,308 @@
1
+ import { isComment, isElement, toHtml } from "@typed/wire"
2
+ import { type Inspectable, NodeInspectSymbol } from "effect/Inspectable"
3
+ import { CouldNotFindRootElement, CouldNotFindTemplateEndError } from "../errors"
4
+
5
+ const TYPED_TEMPLATE_PREFIX = `typed-`
6
+ const TYPED_TEMPLATE_END_PREFIX = `/typed-`
7
+ const MANY_PREFIX = `many`
8
+ const HOLE_PREFIX = `hole`
9
+
10
+ export function getHydrationRoot(root: HTMLElement): HydrationElement {
11
+ const childNodes = Array.from(root.childNodes)
12
+ let hydrationNodes = getHydrationNodes(childNodes)
13
+
14
+ // If your whole template is wrapped in a single hole, unwrap it.
15
+ if (hydrationNodes.length === 1 && hydrationNodes[0]._tag === "hole") {
16
+ hydrationNodes = getChildNodes(hydrationNodes[0])
17
+ }
18
+
19
+ return new HydrationElement(root, hydrationNodes)
20
+ }
21
+
22
+ function getHydrationNodes(nodes: Array<Node>): Array<HydrationNode> {
23
+ const out: Array<HydrationNode> = []
24
+
25
+ for (let i = 0; i < nodes.length; ++i) {
26
+ const node = nodes[i]
27
+
28
+ if (isComment(node)) {
29
+ if (node.data.startsWith(TYPED_TEMPLATE_PREFIX)) {
30
+ const hash = node.data.slice(TYPED_TEMPLATE_PREFIX.length)
31
+ const endIndex = getTemplateEndIndex(nodes, i, hash)
32
+ const childNodes = nodes.slice(i + 1, endIndex)
33
+
34
+ out.push(new HydrationTemplate(hash, getHydrationNodes(childNodes)))
35
+
36
+ i = endIndex
37
+ } else if (node.data.startsWith(MANY_PREFIX)) {
38
+ const last = out.pop()
39
+ out.push(new HydrationMany(node.data.slice(MANY_PREFIX.length), node, last ? [last] : []))
40
+ } else if (node.data.startsWith(HOLE_PREFIX)) {
41
+ const index = parseInt(node.data.slice(HOLE_PREFIX.length), 10)
42
+ const endIndex = getHoleEndIndex(nodes, i, index)
43
+ const endComment = nodes[endIndex] as Comment
44
+ out.push(new HydrationHole(index, node, endComment, getHydrationNodes(nodes.slice(i + 1, endIndex))))
45
+ i = endIndex
46
+ } else {
47
+ out.push(new HydrationLiteral(node))
48
+ }
49
+ } else if (isElement(node)) {
50
+ out.push(new HydrationElement(node, getHydrationNodes(Array.from(node.childNodes))))
51
+ } else {
52
+ out.push(new HydrationLiteral(node))
53
+ }
54
+ }
55
+
56
+ return out
57
+ }
58
+
59
+ function getTemplateEndIndex(nodes: Array<Node>, start: number, hash: string): number {
60
+ const endHash = TYPED_TEMPLATE_END_PREFIX + hash
61
+
62
+ for (let i = start; i < nodes.length; ++i) {
63
+ const node = nodes[i]
64
+
65
+ if (isComment(node) && node.data === endHash) {
66
+ return i
67
+ }
68
+ }
69
+
70
+ throw new CouldNotFindTemplateEndError(hash)
71
+ }
72
+
73
+ function getHoleEndIndex(nodes: Array<Node>, start: number, index: number): number {
74
+ const endHash = `/hole${index}`
75
+
76
+ let templateDepth = 0
77
+
78
+ for (let i = start; i < nodes.length; ++i) {
79
+ const node = nodes[i]
80
+
81
+ if (isComment(node)) {
82
+ if (templateDepth === 0 && node.data === endHash) return i
83
+ else if (node.data.startsWith(TYPED_TEMPLATE_PREFIX)) templateDepth++
84
+ else if (node.data.startsWith(TYPED_TEMPLATE_END_PREFIX)) templateDepth--
85
+ }
86
+ }
87
+
88
+ throw new CouldNotFindRootElement(index)
89
+ }
90
+
91
+ export class HydrationElement implements Inspectable {
92
+ readonly _tag = "element" as const
93
+
94
+ constructor(
95
+ readonly parentNode: Element,
96
+ readonly childNodes: Array<HydrationNode>
97
+ ) {}
98
+
99
+ toJSON(): unknown {
100
+ return {
101
+ _tag: this._tag,
102
+ parentNode: toHtml(this.parentNode),
103
+ childNodes: this.childNodes.map((n) => n.toJSON())
104
+ }
105
+ }
106
+
107
+ [NodeInspectSymbol]() {
108
+ return JSON.stringify(this.toJSON(), null, 2)
109
+ }
110
+ }
111
+
112
+ export class HydrationTemplate implements Inspectable {
113
+ readonly _tag = "template" as const
114
+
115
+ constructor(
116
+ readonly hash: string,
117
+ readonly childNodes: Array<HydrationNode>
118
+ ) {}
119
+
120
+ toJSON(): unknown {
121
+ return {
122
+ _tag: this._tag,
123
+ hash: this.hash,
124
+ childNodes: this.childNodes.map((n) => n.toJSON())
125
+ }
126
+ }
127
+
128
+ [NodeInspectSymbol]() {
129
+ return JSON.stringify(this.toJSON(), null, 2)
130
+ }
131
+ }
132
+
133
+ export type HydrationNode = HydrationElement | HydrationTemplate | HydrationMany | HydrationHole | HydrationLiteral
134
+
135
+ export class HydrationMany implements Inspectable {
136
+ readonly _tag = "many" as const
137
+
138
+ constructor(
139
+ readonly key: string,
140
+ readonly comment: Comment,
141
+ readonly childNodes: Array<HydrationNode>
142
+ ) {}
143
+
144
+ toJSON(): unknown {
145
+ return {
146
+ _tag: this._tag,
147
+ key: this.key,
148
+ childNodes: this.childNodes.map((n) => n.toJSON())
149
+ }
150
+ }
151
+
152
+ [NodeInspectSymbol]() {
153
+ return JSON.stringify(this.toJSON(), null, 2)
154
+ }
155
+ }
156
+
157
+ export class HydrationHole implements Inspectable {
158
+ readonly _tag = "hole" as const
159
+
160
+ constructor(
161
+ readonly index: number,
162
+ readonly startComment: Comment,
163
+ readonly endComment: Comment,
164
+ readonly childNodes: Array<HydrationNode>
165
+ ) {}
166
+
167
+ toJSON(): unknown {
168
+ return {
169
+ _tag: this._tag,
170
+ index: this.index,
171
+ childNodes: this.childNodes.map((n) => n.toJSON())
172
+ }
173
+ }
174
+
175
+ [NodeInspectSymbol]() {
176
+ return JSON.stringify(this.toJSON(), null, 2)
177
+ }
178
+ }
179
+
180
+ export class HydrationLiteral implements Inspectable {
181
+ readonly _tag = "literal" as const
182
+
183
+ constructor(
184
+ readonly node: Node
185
+ ) {}
186
+
187
+ toJSON(): unknown {
188
+ return {
189
+ _tag: this._tag,
190
+ node: toHtml(this.node)
191
+ }
192
+ }
193
+
194
+ [NodeInspectSymbol]() {
195
+ return JSON.stringify(this.toJSON(), null, 2)
196
+ }
197
+ }
198
+
199
+ export function getChildNodes(node: HydrationNode): Array<HydrationNode> {
200
+ switch (node._tag) {
201
+ case "literal":
202
+ return []
203
+ case "hole":
204
+ case "element":
205
+ case "template":
206
+ case "many":
207
+ return node.childNodes
208
+ }
209
+ }
210
+
211
+ export function findHydrationTemplate(
212
+ nodes: Array<HydrationNode>,
213
+ templateHash: string
214
+ ): HydrationTemplate | null {
215
+ const toProcess: Array<HydrationNode> = [...nodes]
216
+
217
+ while (toProcess.length > 0) {
218
+ const node = toProcess.shift()!
219
+
220
+ if (node._tag === "template" && node.hash === templateHash) {
221
+ return node
222
+ } else if (node._tag === "element") {
223
+ toProcess.push(...node.childNodes)
224
+ }
225
+ }
226
+
227
+ return null
228
+ }
229
+
230
+ export function findHydrationMany(
231
+ nodes: Array<HydrationNode>,
232
+ key: string
233
+ ): HydrationMany | null {
234
+ for (let i = 0; i < nodes.length; ++i) {
235
+ const node = nodes[i]
236
+ if (node._tag === "many" && node.key === key) {
237
+ return node
238
+ }
239
+ }
240
+
241
+ return null
242
+ }
243
+
244
+ export function findHydrationHole(nodes: Array<HydrationNode>, index: number): HydrationHole | null {
245
+ for (const node of nodes) {
246
+ if (node._tag === "hole" && node.index === index) {
247
+ return node
248
+ } else if (node._tag === "element") {
249
+ const found = findHydrationHole(node.childNodes, index)
250
+ if (found !== null) {
251
+ return found
252
+ }
253
+ }
254
+ }
255
+
256
+ return null
257
+ }
258
+
259
+ export function findHydrationNode(
260
+ node: HydrationNode,
261
+ index: number,
262
+ manyKey?: string
263
+ ): HydrationHole | HydrationMany | null {
264
+ const childNodes = getChildNodes(node)
265
+ const found = manyKey === undefined
266
+ ? findHydrationHole(childNodes, index)
267
+ : findHydrationMany(childNodes, manyKey)
268
+
269
+ return found
270
+ }
271
+
272
+ export function getNodes(node: HydrationNode): Array<Node> {
273
+ switch (node._tag) {
274
+ case "element":
275
+ return [node.parentNode]
276
+ case "literal":
277
+ return [node.node]
278
+ case "hole":
279
+ return [node.startComment, ...node.childNodes.flatMap(getNodes), node.endComment]
280
+ case "many":
281
+ return [...node.childNodes.flatMap(getNodes), node.comment]
282
+ case "template":
283
+ return node.childNodes.flatMap(getNodes)
284
+ }
285
+ }
286
+
287
+ export function getNodesExcludingStartComment(node: HydrationNode): Array<Node> {
288
+ switch (node._tag) {
289
+ case "element":
290
+ return [node.parentNode]
291
+ case "literal":
292
+ return [node.node]
293
+ case "hole":
294
+ return [...node.childNodes.flatMap(getNodesExcludingStartComment), node.endComment]
295
+ case "many":
296
+ return [...node.childNodes.flatMap(getNodesExcludingStartComment), node.comment]
297
+ case "template":
298
+ return node.childNodes.flatMap(getNodesExcludingStartComment)
299
+ }
300
+ }
301
+
302
+ export function getPreviousNodes(hole: HydrationHole | HydrationMany): Array<Node> {
303
+ const predicate: (n: Node) => boolean = hole._tag === "hole"
304
+ ? (n) => n !== hole.startComment && n !== hole.startComment
305
+ : (n) => n !== hole.comment
306
+
307
+ return hole.childNodes.flatMap(getNodes).filter(predicate)
308
+ }