Import
import { many } from "@typed/template";Signatures
export declare function many<A, E, R, B extends PropertyKey, R2, E2>(values: Fx.Fx<ReadonlyArray<A>, E, R>, getKey: (a: A) => B, render: (value: RefSubject.RefSubject<A>, key: B) => Fx.Fx<RenderEvent, E2, R2 | Scope>): Many<A, E | E2 | Cause.IllegalArgumentError, R | R2 | Scope | RenderTemplate>;Why
Keys turn collection identity into a local rendering contract. The DOM
renderer keeps one entry map for the dynamic range: a new key starts one
child, a removed key closes one child Scope, a retained changed value updates
that child’s RefSubject, and a pure reorder does not publish unchanged item
data. The same descriptor lets the HTML renderer serialize the first array in
source order and emit compatible hydration markers.
Ownership and lifetime
Each DOM key owns a forked child Scope. Removing the key closes that Scope;
interruption closes every remaining child. Both DOM and HTML rendering reject
duplicate keys with Cause.IllegalArgumentError. Hydratable output also
rejects local symbols because their identity cannot survive serialization;
use strings, numbers, or Symbol.for() keys across the server boundary.
Cost model and moves
Every source array requires O(n) key validation and ordering work; many does
not pretend an arbitrary list change is O(1). Within that pass, retained-key
lookup is O(1) on average, unchanged values skip RefSubject.set, and only
added or removed keys allocate or close child Scopes. DOM reconciliation is
confined to this range and uses equal-edge, append/remove, and reverse-swap
fast paths before an O(n) map fallback. An already-connected node is moved
with ParentNode.moveBefore when supported, preserving browser-managed state;
insertBefore is the compatibility fallback. HTML setup is O(n) for the
initial array and performs no live DOM reconciliation.
Examples
import { Effect, Layer } from "effect"
import { Fx, RefSubject } from "@typed/fx"
import { html, many } from "@typed/template"
import { DomRenderTemplate, render } from "@typed/template/Render"
interface Todo {
readonly id: string
readonly text: string
readonly completed: boolean
}
const program = Effect.gen(function* () {
const todos = yield* RefSubject.make<Todo[]>([
{ id: "1", text: "Learn Effect", completed: false },
{ id: "2", text: "Build app", completed: false }
])
const todoList = many(
todos,
(todo) => todo.id, // Key function
(todoRef, key) => // Render function receives RefSubject
html`<li>
${RefSubject.map(todoRef, (todo) => todo.text)}
<button onclick=${RefSubject.update(todoRef, (todo) =>
({ ...todo, completed: !todo.completed })
)}>Toggle</button>
</li>`
)
const template = html`<ul>${todoList}</ul>`
return yield* render(template, document.body).pipe(
Fx.drainLayer,
Layer.provide(DomRenderTemplate),
Layer.launch
)
})Other public imports
These import paths expose the same declaration. Each page retains its own public name and signature.