@typed/template 0.11.0 → 0.13.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 (52) hide show
  1. package/compiler-tools/package.json +6 -0
  2. package/dist/cjs/Html.js +1 -1
  3. package/dist/cjs/Html.js.map +1 -1
  4. package/dist/cjs/Hydrate.js +0 -14
  5. package/dist/cjs/Hydrate.js.map +1 -1
  6. package/dist/cjs/Template.js.map +1 -1
  7. package/dist/cjs/Test.js +1 -1
  8. package/dist/cjs/Test.js.map +1 -1
  9. package/dist/cjs/compiler-tools.js +100 -0
  10. package/dist/cjs/compiler-tools.js.map +1 -0
  11. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  12. package/dist/cjs/internal/browser.js +1 -1
  13. package/dist/cjs/internal/browser.js.map +1 -1
  14. package/dist/cjs/internal/v2/render-sync-parts.js +1 -1
  15. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -1
  16. package/dist/cjs/internal/v2/render.js +241 -66
  17. package/dist/cjs/internal/v2/render.js.map +1 -1
  18. package/dist/dts/Hydrate.d.ts +2 -9
  19. package/dist/dts/Hydrate.d.ts.map +1 -1
  20. package/dist/dts/Template.d.ts +3 -3
  21. package/dist/dts/Template.d.ts.map +1 -1
  22. package/dist/dts/compiler-tools.d.ts +143 -0
  23. package/dist/dts/compiler-tools.d.ts.map +1 -0
  24. package/dist/dts/internal/v2/render.d.ts +31 -10
  25. package/dist/dts/internal/v2/render.d.ts.map +1 -1
  26. package/dist/esm/Html.js +2 -2
  27. package/dist/esm/Html.js.map +1 -1
  28. package/dist/esm/Hydrate.js +0 -12
  29. package/dist/esm/Hydrate.js.map +1 -1
  30. package/dist/esm/Template.js.map +1 -1
  31. package/dist/esm/Test.js +2 -2
  32. package/dist/esm/Test.js.map +1 -1
  33. package/dist/esm/compiler-tools.js +91 -0
  34. package/dist/esm/compiler-tools.js.map +1 -0
  35. package/dist/esm/internal/HydrateContext.js.map +1 -1
  36. package/dist/esm/internal/v2/render.js +231 -63
  37. package/dist/esm/internal/v2/render.js.map +1 -1
  38. package/package.json +17 -9
  39. package/src/Html.ts +2 -2
  40. package/src/Hydrate.ts +2 -37
  41. package/src/Template.ts +4 -2
  42. package/src/Test.ts +2 -2
  43. package/src/compiler-tools.ts +250 -0
  44. package/src/internal/HydrateContext.ts +0 -2
  45. package/src/internal/v2/render.ts +279 -54
  46. package/dist/cjs/internal/v2/hydrate.js +0 -202
  47. package/dist/cjs/internal/v2/hydrate.js.map +0 -1
  48. package/dist/dts/internal/v2/hydrate.d.ts +0 -7
  49. package/dist/dts/internal/v2/hydrate.d.ts.map +0 -1
  50. package/dist/esm/internal/v2/hydrate.js +0 -195
  51. package/dist/esm/internal/v2/hydrate.js.map +0 -1
  52. package/src/internal/v2/hydrate.ts +0 -289
@@ -1,9 +1,10 @@
1
1
  import * as Context from "@typed/context"
2
2
  import * as Fx from "@typed/fx"
3
3
  import type { Rendered } from "@typed/wire"
4
- import { persistent } from "@typed/wire"
5
- import type * as Cause from "effect/Cause"
6
- import type { Chunk } from "effect/Chunk"
4
+ import { isText, persistent } from "@typed/wire"
5
+ import { Option } from "effect"
6
+ import * as Cause from "effect/Cause"
7
+ import * as Chunk from "effect/Chunk"
7
8
  import * as Effect from "effect/Effect"
8
9
  import * as ExecutionStrategy from "effect/ExecutionStrategy"
9
10
  import { flow } from "effect/Function"
@@ -20,12 +21,24 @@ import { DomRenderEvent, type RenderEvent } from "../../RenderEvent.js"
20
21
  import { DEFAULT_PRIORITY, RenderQueue } from "../../RenderQueue.js"
21
22
  import type { RenderTemplate } from "../../RenderTemplate.js"
22
23
  import type * as Template from "../../Template.js"
24
+ import { CouldNotFindCommentError, isHydrationError } from "../errors.js"
23
25
  import type { EventSource } from "../EventSource.js"
24
26
  import { makeEventSource } from "../EventSource.js"
27
+ import { HydrateContext } from "../HydrateContext.js"
25
28
  import type { IndexRefCounter } from "../indexRefCounter.js"
26
29
  import { makeRefCounter } from "../indexRefCounter.js"
27
- import { findHoleComment, findPath, keyToPartType } from "../utils.js"
30
+ import type { ParentChildNodes } from "../utils.js"
31
+ import { findHoleComment, findHydratePath, findPath, isCommentWithValue, keyToPartType } from "../utils.js"
28
32
  import { isNullOrUndefined } from "./helpers.js"
33
+ import type { HydrationHole, HydrationNode, HydrationTemplate } from "./hydration-template.js"
34
+ import {
35
+ findHydrationHole,
36
+ findHydrationMany,
37
+ findHydrationTemplate,
38
+ getChildNodes,
39
+ getNodes,
40
+ getPreviousNodes
41
+ } from "./hydration-template.js"
29
42
  import { EventPartImpl, RefPartImpl, syncPartToPart } from "./parts.js"
30
43
  import { getBrowserEntry } from "./render-entry.js"
31
44
  import * as SyncPartsInternal from "./render-sync-parts.js"
@@ -41,7 +54,6 @@ export type TemplateContext = {
41
54
  */
42
55
  spreadIndex: number
43
56
 
44
- readonly content: DocumentFragment
45
57
  readonly context: Context.Context<Scope.Scope>
46
58
  readonly document: Document
47
59
  readonly eventSource: EventSource
@@ -52,6 +64,9 @@ export type TemplateContext = {
52
64
  readonly scope: Scope.CloseableScope
53
65
  readonly values: ReadonlyArray<Renderable<any, any>>
54
66
  readonly onCause: (cause: Cause.Cause<any>) => Effect.Effect<unknown>
67
+ readonly manyKey: string | undefined
68
+
69
+ readonly hydrateContext: Option.Option<HydrateContext>
55
70
  }
56
71
 
57
72
  export const renderTemplate: (
@@ -63,28 +78,48 @@ export const renderTemplate: (
63
78
  values: Values
64
79
  ) => {
65
80
  const entry = getBrowserEntry(document, renderContext, templateStrings)
66
- if (entry.template.parts.length === 0) {
67
- return Fx.succeed(DomRenderEvent(persistent(document, document.importNode(entry.content, true))))
68
- }
69
81
 
70
82
  return Fx.make<
71
83
  RenderEvent,
72
84
  Placeholder.Error<Values[number]>,
73
85
  Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>
74
- >((sink) =>
75
- Effect.catchAllCause(
86
+ >(function render(
87
+ sink
88
+ ): Effect.Effect<unknown, never, Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>> {
89
+ return Effect.catchAllCause(
76
90
  Effect.gen(function*() {
77
91
  // Create a context for rendering our template
78
92
  const ctx = yield* makeTemplateContext<Values>(
79
- entry.content,
80
93
  document,
81
94
  renderContext,
82
95
  values,
83
96
  sink.onFailure
84
97
  )
85
98
 
86
- // Setup all parts
87
- const effects = setupParts(entry.template.parts, ctx)
99
+ const hydration = attemptHydration(ctx, entry.template.hash)
100
+
101
+ let effects: Array<Effect.Effect<void, any, any>>
102
+ let content: DocumentFragment
103
+ let wire: Rendered | undefined
104
+
105
+ if (Option.isSome(hydration)) {
106
+ const { hydrateCtx, where } = hydration.value
107
+ effects = setupHydrateParts(entry.template.parts, {
108
+ ...ctx,
109
+ where,
110
+ manyKey: hydrateCtx.manyKey,
111
+ makeHydrateContext: (where: HydrationNode): HydrateContext => ({
112
+ where,
113
+ hydrate: true
114
+ })
115
+ })
116
+
117
+ wire = getWire(where)
118
+ } else {
119
+ content = ctx.document.importNode(entry.content, true)
120
+ effects = setupRenderParts(entry.template.parts, content, ctx)
121
+ }
122
+
88
123
  if (effects.length > 0) {
89
124
  yield* Effect.forEach(effects, flow(Effect.catchAllCause(ctx.onCause), Effect.forkIn(ctx.scope)))
90
125
  }
@@ -95,14 +130,21 @@ export const renderTemplate: (
95
130
  yield* ctx.refCounter.wait
96
131
  }
97
132
 
98
- // Create a persistent wire from our content
99
- const wire = persistent(document, ctx.content)
133
+ // If we're not hydrating, we need to create our wire from our content
134
+ if (wire === undefined) {
135
+ wire = persistent(ctx.document, content!)
136
+ }
100
137
 
101
138
  // Setup our event listeners for our wire.
102
139
  // We use the parentScope to allow event listeners to exist
103
140
  // beyond the lifetime of the current Fiber, but no further than its parent template.
104
141
  yield* ctx.eventSource.setup(wire, ctx.parentScope)
105
142
 
143
+ // If we're hydrating, we need to mark this part of the stack as hydrated
144
+ if (Option.isSome(hydration)) {
145
+ hydration.value.hydrateCtx.hydrate = false
146
+ }
147
+
106
148
  // Emit our DomRenderEvent
107
149
  yield* sink.onSuccess(DomRenderEvent(wire)).pipe(
108
150
  // Ensure our templates last forever in the DOM environment
@@ -112,13 +154,22 @@ export const renderTemplate: (
112
154
  Effect.onExit((exit) => Scope.close(ctx.scope, exit))
113
155
  )
114
156
  }),
115
- sink.onFailure
157
+ (cause) => {
158
+ const hydrationFailure = Chunk.findFirst(Cause.defects(cause), isHydrationError)
159
+ if (Option.isSome(hydrationFailure)) {
160
+ return HydrateContext.pipe(
161
+ Effect.tap((ctx) => ctx.hydrate = false),
162
+ Effect.flatMap(() => render(sink))
163
+ )
164
+ }
165
+
166
+ return sink.onFailure(cause)
167
+ }
116
168
  )
117
- )
169
+ })
118
170
  }
119
171
 
120
172
  export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any, any>>>(
121
- entry: DocumentFragment,
122
173
  document: Document,
123
174
  renderContext: RenderContext,
124
175
  values: ReadonlyArray<Renderable<any, any>>,
@@ -130,15 +181,16 @@ export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any,
130
181
  const queue = Context.get(context, RenderQueue)
131
182
  const parentScope = Context.get(context, Scope.Scope)
132
183
  const eventSource = makeEventSource()
133
- const content = document.importNode(entry, true)
134
184
  const scope = yield* Scope.fork(parentScope, ExecutionStrategy.sequential)
185
+ const hydrateContext = Context.getOption(context, HydrateContext)
135
186
  const templateContext: TemplateContext = {
136
187
  context: Context.add(context, Scope.Scope, scope),
137
188
  expected: 0,
138
- content,
139
189
  document,
140
190
  eventSource,
191
+ hydrateContext,
141
192
  parentScope,
193
+ manyKey: undefined,
142
194
  queue,
143
195
  refCounter,
144
196
  renderContext,
@@ -152,11 +204,33 @@ export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any,
152
204
  })
153
205
  }
154
206
 
155
- function setupParts(parts: Template.Template["parts"], ctx: TemplateContext) {
207
+ export function attemptHydration(
208
+ ctx: TemplateContext,
209
+ hash: string
210
+ ): Option.Option<{ readonly where: HydrationTemplate; readonly hydrateCtx: HydrateContext }> {
211
+ if (Option.isSome(ctx.hydrateContext) && ctx.hydrateContext.value.hydrate) {
212
+ const hydrateCtx = ctx.hydrateContext.value
213
+ const where = findHydrationTemplateByHash(hydrateCtx, hash)
214
+ if (where === null) {
215
+ hydrateCtx.hydrate = false
216
+ return Option.none()
217
+ } else {
218
+ return Option.some({ where, hydrateCtx })
219
+ }
220
+ }
221
+
222
+ return Option.none()
223
+ }
224
+
225
+ function setupRenderParts(
226
+ parts: Template.Template["parts"],
227
+ content: ParentChildNodes,
228
+ ctx: TemplateContext
229
+ ) {
156
230
  const effects: Array<Effect.Effect<void, any, any>> = []
157
231
 
158
232
  for (const [part, path] of parts) {
159
- const effect = setupPart(part, path, ctx)
233
+ const effect = setupRenderPart(part, content, path, ctx)
160
234
  if (effect) {
161
235
  effects.push(effect)
162
236
  }
@@ -165,58 +239,59 @@ function setupParts(parts: Template.Template["parts"], ctx: TemplateContext) {
165
239
  return effects
166
240
  }
167
241
 
168
- function setupPart(
242
+ function setupRenderPart(
169
243
  part: Template.PartNode | Template.SparsePartNode,
170
- path: Chunk<number>,
244
+ content: ParentChildNodes,
245
+ path: Chunk.Chunk<number>,
171
246
  ctx: TemplateContext
172
247
  ) {
173
248
  switch (part._tag) {
174
249
  case "attr":
175
- return setupAttrPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
250
+ return setupAttrPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
176
251
  case "boolean-part":
177
252
  return setupBooleanPart(
178
253
  part,
179
- findPath(ctx.content, path) as HTMLElement | SVGElement,
254
+ findPath(content, path) as HTMLElement | SVGElement,
180
255
  ctx,
181
256
  ctx.values[part.index]
182
257
  )
183
258
  case "className-part":
184
259
  return setupClassNamePart(
185
260
  part,
186
- findPath(ctx.content, path) as HTMLElement | SVGElement,
261
+ findPath(content, path) as HTMLElement | SVGElement,
187
262
  ctx,
188
263
  ctx.values[part.index]
189
264
  )
190
265
  case "comment-part":
191
- return setupCommentPart(part, findPath(ctx.content, path) as Comment, ctx)
266
+ return setupCommentPart(part, findPath(content, path) as Comment, ctx, ctx.values[part.index])
192
267
  case "data":
193
- return setupDataPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
268
+ return setupDataPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
194
269
  case "event":
195
- return setupEventPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
270
+ return setupEventPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
196
271
  case "node": {
197
- const parent = findPath(ctx.content, path) as Element
272
+ const parent = findPath(content, path) as Element
198
273
  const comment = findHoleComment(parent, part.index)
199
274
  return setupNodePart(part, comment, ctx, null, [])
200
275
  }
201
276
  case "properties":
202
- return setupPropertiesPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
277
+ return setupPropertiesPart(findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
203
278
  case "property":
204
279
  return setupPropertyPart(
205
280
  part,
206
- findPath(ctx.content, path) as HTMLElement | SVGElement,
281
+ findPath(content, path) as HTMLElement | SVGElement,
207
282
  ctx,
208
283
  ctx.values[part.index]
209
284
  )
210
285
  case "ref":
211
- return setupRefPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
286
+ return setupRefPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
212
287
  case "sparse-attr":
213
- return setupSparseAttrPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
288
+ return setupSparseAttrPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
214
289
  case "sparse-class-name":
215
- return setupSparseClassNamePart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
290
+ return setupSparseClassNamePart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
216
291
  case "sparse-comment":
217
- return setupSparseCommentPart(part, findPath(ctx.content, path) as Comment, ctx)
292
+ return setupSparseCommentPart(part, findPath(content, path) as Comment, ctx)
218
293
  case "text-part": {
219
- const parent = findPath(ctx.content, path) as Element
294
+ const parent = findPath(content, path) as Element
220
295
  const comment = findHoleComment(parent, part.index)
221
296
  return setupTextPart(part, comment, ctx)
222
297
  }
@@ -257,10 +332,10 @@ export function setupClassNamePart(
257
332
  export function setupCommentPart(
258
333
  { index }: Pick<Template.CommentPartNode, "index">,
259
334
  comment: Comment,
260
- ctx: TemplateContext
335
+ ctx: TemplateContext,
336
+ renderable: Renderable<any, any>
261
337
  ) {
262
338
  const part = SyncPartsInternal.makeCommentPart(index, comment)
263
- const renderable = ctx.values[index]
264
339
  return matchSyncPart(renderable, ctx, part)
265
340
  }
266
341
 
@@ -348,10 +423,8 @@ export function setupPropertyPart(
348
423
  export function setupRefPart(
349
424
  { index }: Pick<Template.RefPartNode, "index">,
350
425
  element: HTMLElement | SVGElement,
351
- ctx: TemplateContext
426
+ renderable: Renderable<any, any>
352
427
  ) {
353
- const renderable = ctx.values[index]
354
-
355
428
  if (isNullOrUndefined(renderable)) return null
356
429
  else if (isDirective(renderable)) {
357
430
  return renderable(
@@ -366,11 +439,10 @@ export function setupRefPart(
366
439
  }
367
440
 
368
441
  export function setupPropertiesPart(
369
- { index }: Pick<Template.PropertiesPartNode, "index">,
370
442
  element: HTMLElement | SVGElement,
371
- ctx: TemplateContext
443
+ ctx: TemplateContext,
444
+ renderable: Renderable<any, any>
372
445
  ) {
373
- const renderable = ctx.values[index]
374
446
  if (renderable && typeof renderable === "object") {
375
447
  const effects: Array<Effect.Effect<void, any, any>> = []
376
448
  const addEffect = (effect: Effect.Effect<void, any, any> | null | undefined) => {
@@ -410,7 +482,7 @@ export function setupPropertiesPart(
410
482
  }
411
483
 
412
484
  export function setupSparseAttrPart(
413
- { name, nodes }: Template.SparseAttrNode,
485
+ { name, nodes }: Pick<Template.SparseAttrNode, "name" | "nodes">,
414
486
  element: HTMLElement | SVGElement,
415
487
  ctx: TemplateContext
416
488
  ) {
@@ -427,7 +499,7 @@ export function setupSparseAttrPart(
427
499
  }
428
500
 
429
501
  export function setupSparseClassNamePart(
430
- { nodes }: Template.SparseClassNameNode,
502
+ { nodes }: Pick<Template.SparseClassNameNode, "nodes">,
431
503
  element: HTMLElement | SVGElement,
432
504
  ctx: TemplateContext
433
505
  ) {
@@ -442,7 +514,7 @@ export function setupSparseClassNamePart(
442
514
  }
443
515
 
444
516
  export function setupSparseCommentPart(
445
- { nodes }: Template.SparseCommentNode,
517
+ { nodes }: Pick<Template.SparseCommentNode, "nodes">,
446
518
  comment: Comment,
447
519
  ctx: TemplateContext
448
520
  ) {
@@ -499,9 +571,8 @@ function unwrapRenderable<E, R>(renderable: unknown): Fx.Fx<any, E, R> {
499
571
  else if (Array.isArray(renderable)) {
500
572
  return renderable.length === 0
501
573
  ? Fx.succeed(null)
502
- // TODO: We need to ensure the ordering of these values in server environments
503
574
  : Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
504
- } else if (Fx.TypeId in renderable) {
575
+ } else if (Fx.FxTypeId in renderable) {
505
576
  return renderable as any
506
577
  } else if (Effect.EffectTypeId in renderable) {
507
578
  return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<E, R>))
@@ -573,16 +644,170 @@ export function attachRoot<T extends RenderEvent | null>(
573
644
  }
574
645
 
575
646
  export function removeChildren(where: HTMLElement, previous: Rendered) {
576
- for (const node of getNodes(previous)) {
647
+ for (const node of getNodesFromRendered(previous)) {
577
648
  where.removeChild(node)
578
649
  }
579
650
  }
580
651
 
581
652
  export function replaceChildren(where: HTMLElement, wire: Rendered) {
582
- where.replaceChildren(...getNodes(wire))
653
+ where.replaceChildren(...getNodesFromRendered(wire))
583
654
  }
584
655
 
585
- export function getNodes(rendered: Rendered): Array<globalThis.Node> {
656
+ export function getNodesFromRendered(rendered: Rendered): Array<globalThis.Node> {
586
657
  const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
587
658
  return Array.isArray(value) ? value : [value]
588
659
  }
660
+
661
+ export type HydrateTemplateContext = TemplateContext & {
662
+ readonly where: HydrationNode
663
+ readonly makeHydrateContext: (where: HydrationNode, index: number) => HydrateContext
664
+ }
665
+
666
+ export function findHydrationTemplateByHash(hydrateCtx: HydrateContext, hash: string): HydrationTemplate | null {
667
+ // If there is not a manyKey, we can just find the template by its hash
668
+ if (hydrateCtx.manyKey === undefined) {
669
+ return findHydrationTemplate(getChildNodes(hydrateCtx.where), hash)
670
+ }
671
+
672
+ // If there is a manyKey, we need to find the many node first
673
+ const many = findHydrationMany(getChildNodes(hydrateCtx.where), hydrateCtx.manyKey)
674
+
675
+ if (many === null) return null
676
+
677
+ // Then we can find the template by its hash
678
+ return findHydrationTemplate(getChildNodes(many), hash)
679
+ }
680
+
681
+ export function setupHydrateParts(parts: Template.Template["parts"], ctx: HydrateTemplateContext) {
682
+ const effects: Array<Effect.Effect<void, any, any>> = []
683
+
684
+ for (const [part, path] of parts) {
685
+ const effect = setupHydratePart(part, path, ctx)
686
+ if (effect) {
687
+ effects.push(effect)
688
+ }
689
+ }
690
+
691
+ return effects
692
+ }
693
+
694
+ export function setupHydratePart(
695
+ part: Template.PartNode | Template.SparsePartNode,
696
+ path: Chunk.Chunk<number>,
697
+ ctx: HydrateTemplateContext
698
+ ) {
699
+ switch (part._tag) {
700
+ case "attr":
701
+ return setupAttrPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
702
+ case "boolean-part":
703
+ return setupBooleanPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
704
+ case "className-part":
705
+ return setupClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
706
+ case "comment-part":
707
+ return setupCommentPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
708
+ case "data":
709
+ return setupDataPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
710
+ case "event":
711
+ return setupEventPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
712
+ case "node": {
713
+ const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
714
+ if (hole === null) {
715
+ throw new CouldNotFindCommentError(part.index)
716
+ }
717
+ return setupHydratedNodePart(part, hole, ctx)
718
+ }
719
+ case "properties":
720
+ return setupPropertiesPart(findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
721
+ case "property":
722
+ return setupPropertyPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
723
+ case "ref":
724
+ return setupRefPart(part, findHydratePath(ctx.where, path) as any, ctx.values[part.index])
725
+ case "sparse-attr":
726
+ return setupSparseAttrPart(part, findHydratePath(ctx.where, path) as any, ctx)
727
+ case "sparse-class-name":
728
+ return setupSparseClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx)
729
+ case "sparse-comment":
730
+ return setupSparseCommentPart(part, findHydratePath(ctx.where, path) as any, ctx)
731
+ case "text-part": {
732
+ const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
733
+ if (hole === null) throw new CouldNotFindCommentError(part.index)
734
+ return setupTextPart(part, hole.endComment, ctx)
735
+ }
736
+ }
737
+ }
738
+
739
+ export function setupHydratedNodePart(
740
+ part: Template.NodePart,
741
+ hole: HydrationHole,
742
+ ctx: HydrateTemplateContext
743
+ ) {
744
+ const nestedCtx = ctx.makeHydrateContext(hole, part.index)
745
+ const previousNodes = getPreviousNodes(hole)
746
+ const text = previousNodes.length === 2 && isCommentWithValue(previousNodes[0], "text") && isText(previousNodes[1])
747
+ ? previousNodes[1]
748
+ : null
749
+ const effect = setupNodePart(part, hole.endComment, ctx, text, text === null ? previousNodes : [text])
750
+ if (effect === null) return null
751
+ return Effect.provideService(effect, HydrateContext, nestedCtx)
752
+ }
753
+
754
+ export function findRootParentChildNodes(where: HTMLElement): ParentChildNodes {
755
+ const childNodes = findRootChildNodes(where)
756
+
757
+ return {
758
+ parentNode: where,
759
+ childNodes
760
+ }
761
+ }
762
+
763
+ const START = "typed-start"
764
+ const END = "typed-end"
765
+
766
+ // Finds all of the childNodes between the "typed-start" and "typed-end" comments
767
+ export function findRootChildNodes(where: HTMLElement): Array<Node> {
768
+ let start = -1
769
+ let end = -1
770
+
771
+ const { childNodes } = where
772
+ const length = childNodes.length
773
+
774
+ for (let i = 0; i < length; i++) {
775
+ const node = childNodes[i]
776
+
777
+ if (node.nodeType === node.COMMENT_NODE && node.nodeValue === START) {
778
+ start = i
779
+ break
780
+ }
781
+ }
782
+
783
+ for (let i = length - 1; i >= Math.max(start, 0); i--) {
784
+ const node = childNodes[i]
785
+
786
+ if (node.nodeType === node.COMMENT_NODE && node.nodeValue === END) {
787
+ end = i
788
+ break
789
+ }
790
+ }
791
+
792
+ // If we can't find the start and end comments, just return all childNodes
793
+ if (start === -1 && end === -1) {
794
+ return Array.from(childNodes)
795
+ }
796
+
797
+ start = start === -1 ? 0 : start
798
+ end = end === -1 ? length - 1 : end
799
+
800
+ const rootChildNodes: Array<Node> = Array(end - start)
801
+
802
+ for (let i = start + 1, j = 0; i <= end; i++) {
803
+ rootChildNodes[j++] = childNodes[i]
804
+ }
805
+
806
+ return rootChildNodes
807
+ }
808
+
809
+ export function getWire(where: HydrationNode) {
810
+ const nodes = getNodes(where)
811
+ if (nodes.length === 1) return nodes[0]
812
+ return nodes
813
+ }