/** * `@nifrajs/web/nano` - the smallest state layer that still lets an island drive a keyed list. * * The islands lane (`@nifrajs/web/islands`) is imperative and correct-by-construction, but it has no * state primitive: every DOM update is hand-written, and a list means rebuilding the whole container. * nano adds a small, closed set of primitives and stops: * * - `signal(v)` - a current-value cell you read with `.get()` and write with `.set()`. * - `computed(fn, [deps])` - a derived cell. Its dependencies are DECLARED, never auto-tracked. * - `resource(fetcher, [deps])` - an async cell with an explicit pending/error/ready value. This is * nano's answer to "suspense": no thrown promise, no magic boundary, just a value you can bind. * - `bind` / `bindList` / `bindResource` - explicit, one-directional DOM bindings that each return * their teardown. * * The design rule is the whole point: every reactive edge is a call you can see - a `bind(...)` or a * `computed(fn, [deps])`. There is no re-render scope, no VDOM, no template compiler, and no effect * that silently subscribes to whatever it happened to read. That is what removes the three failure * modes an AI (or a human) hits in a framework - stale closures, wrong effect deps, hydration * mismatch - and, just as importantly, it is what makes every remaining mistake STATICALLY * detectable: a missing dep is `[deps]` that doesn't list a signal the body reads; a leak is a `bind` * whose cleanup isn't returned. A framework's reactivity can't be linted like that; nano's can. * * Where nano stops: no client router and no nested/composable view state (a component tree that mounts * and unmounts its own children). When an app needs those, it has outgrown a vanilla page - reach for * a framework adapter (`@nifrajs/web-preact`), not more nano. * * Browser code: it touches the DOM and is meant to run in an island enhancer, never under SSR. */ /** A value you can read now and be told about later. Both `signal` and `computed` are `Readable`, so * `bind`/`bindList` accept either. */ export interface Readable { get(): T; subscribe(listener: (value: T) => void): () => void; } /** A writable cell. `set` notifies subscribers only when the value actually changes (`Object.is`), so * a redundant write costs nothing. */ export interface Signal extends Readable { set(value: T): void; } /** A current-value cell. `signal(0)` -> `.get()` reads, `.set(1)` writes and notifies. */ export declare function signal(initial: T): Signal; /** * A derived cell. `computed(() => a.get() + b.get(), [a, b])` recomputes whenever a declared * dependency changes and notifies its own subscribers when the derived value changes. * * The `deps` array is mandatory and explicit ON PURPOSE - it is the contract a linter checks against * the signals the body reads (`NF-C023`). Auto-tracking would remove the array but reintroduce the * exact "why didn't this update" bug that makes framework reactivity hard to get right. */ export declare function computed(compute: () => T, deps: readonly Readable[]): Readable; /** The three states of an async cell. A discriminated union on `status` so a consumer must handle * every branch - there is no "value that might secretly be loading", the failure mode of ad-hoc * `isLoading` booleans. `value`/`error` are narrowed by `status`. */ export type ResourceState = { readonly status: "pending"; readonly value: undefined; readonly error: undefined; } | { readonly status: "ready"; readonly value: T; readonly error: undefined; } | { readonly status: "error"; readonly value: undefined; readonly error: unknown; }; /** An async cell: a `Readable` of `ResourceState` plus `refetch()`. This is nano's answer to * "suspense" - an explicit pending/error/ready value, never a thrown promise or a magic boundary. */ export interface Resource extends Readable> { refetch(): void; } /** * An async derived cell. `resource(fetcher, [deps])` runs `fetcher` immediately and again whenever a * declared dependency changes, exposing the result as an explicit `{ status, value, error }`. * * Two footguns are handled here so they cannot be got wrong by hand: * - Races: each run holds a generation; a stale run's resolution is dropped, and its `AbortSignal` is * aborted, so an earlier-started-later-finishing fetch never overwrites a newer one. * - Deps: like `computed`, dependencies are DECLARED (`NF-C023` checks the fetcher's `.get()` reads * against the array). A `resource` that reads a signal its deps omit won't refetch when it changes. */ export declare function resource(fetcher: (signal: AbortSignal) => Promise, deps?: readonly Readable[]): Resource; /** * Bind one element to a source: `apply(el, value)` runs once immediately and again on every change. * Returns the unsubscribe - hand it back as the island's cleanup (or collect several). * * bind(count, remaining, (el, n) => { el.textContent = String(n) }) */ export declare function bind(el: HTMLElement, source: Readable, apply: (el: HTMLElement, value: T) => void): () => void; /** How a `bindList` turns items into keyed DOM. `key` MUST be stable and unique per item (never the * array index) - it is what lets add/remove/reorder touch only the changed rows. */ export interface BindListOptions { key(item: T): string | number; create(item: T): HTMLElement; update?(el: HTMLElement, item: T): void; } /** * Bind a list signal to a container with keyed reconciliation: new items are `create`d, surviving * items are `update`d in place (keeping focus, scroll, and selection), removed items are detached, * and the children are ordered to match the array. Returns the unsubscribe. * * This is the one imperative task too error-prone to hand-write each time - it is why nano exists on * top of islands rather than leaving you to rebuild the container on every change. */ export declare function bindList(source: Readable, container: HTMLElement, options: BindListOptions): () => void; /** How a `bindResource` maps each async state to the DOM. `ready` is required (there is always a * success shape to render); `pending`/`error` are optional. Every branch is explicit - no hidden * "still loading" state can slip through as a rendered `undefined`. */ export interface BindResourceHandlers { ready(el: HTMLElement, value: T): void; pending?(el: HTMLElement): void; error?(el: HTMLElement, error: unknown): void; } /** * Bind an element to a `resource`, dispatching on `status`. Like `bind`, it applies immediately and * on every change and returns the unsubscribe - collect it (a discarded disposer is `NF-C021`). * * bindResource(el, user, { * pending: (n) => { n.textContent = "Loading…" }, * ready: (n, u) => { n.textContent = u.name }, * error: (n) => { n.textContent = "Failed to load" }, * }) */ export declare function bindResource(el: HTMLElement, source: Readable>, handlers: BindResourceHandlers): () => void; //# sourceMappingURL=nano.d.ts.map