@wcstack/signals 1.14.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.
package/dist/dom.d.ts ADDED
@@ -0,0 +1,311 @@
1
+ type Cleanup = () => void;
2
+ type Equals<T> = (a: T, b: T) => boolean;
3
+ interface ReadSignal<T> {
4
+ /** Read the value AND register the current observer as a dependent. */
5
+ get(): T;
6
+ /** Read the value WITHOUT tracking (no dependency edge created). */
7
+ peek(): T;
8
+ }
9
+ interface WriteSignal<T> extends ReadSignal<T> {
10
+ set(value: T): void;
11
+ }
12
+ interface EffectHandle {
13
+ dispose(): void;
14
+ }
15
+ /**
16
+ * Synchronously flush queued effects. Provided for tests and for callers that
17
+ * need DOM updates applied before reading the DOM back. In normal use effects
18
+ * settle on their own microtask.
19
+ */
20
+ declare function flushSync(): void;
21
+ declare function signal<T>(initial: T, equals?: Equals<T>): WriteSignal<T>;
22
+ declare function computed<T>(fn: () => T, equals?: Equals<T>): ReadSignal<T>;
23
+ declare function effect(fn: () => Cleanup | void): EffectHandle;
24
+ /**
25
+ * Run `fn` inside a fresh ownership scope and return its result. Every effect (or
26
+ * nested cleanup) created during `fn` — directly or transitively — is owned by
27
+ * this root; calling the `dispose` passed to `fn` tears them all down.
28
+ *
29
+ * The root is detached: it is NOT auto-disposed by an enclosing owner. The caller
30
+ * holds `dispose` (e.g. a custom element disposes it in disconnectedCallback).
31
+ *
32
+ * If `fn` throws, any effects/cleanups it created BEFORE the throw are disposed
33
+ * before the error propagates — a caller that never received `dispose` (because the
34
+ * call threw) cannot leak the half-built scope.
35
+ */
36
+ declare function createRoot<T>(fn: (dispose: () => void) => T): T;
37
+ /**
38
+ * Register a teardown callback with the current owner. Runs when the owning effect
39
+ * re-runs or is disposed, or when the enclosing root is disposed. A no-op when
40
+ * there is no current owner.
41
+ */
42
+ declare function onCleanup(fn: () => void): void;
43
+
44
+ interface ResourceState<T> {
45
+ value: ReadSignal<T | undefined>;
46
+ loading: ReadSignal<boolean>;
47
+ /** Last error, or `null` when there is none. Initial value is `null`; a (re)start resets it to `null`. */
48
+ error: ReadSignal<unknown>;
49
+ /** Abort any in-flight request and stop reacting to `args` changes. */
50
+ dispose(): void;
51
+ }
52
+ interface ResourceOptions<T, A> {
53
+ /** Reactive inputs. Reading signals here wires restart-on-change. */
54
+ args?: () => A;
55
+ /** Seed value before the first resolution. */
56
+ initial?: T;
57
+ }
58
+ type ResourceSource<T, A> = (args: A, signal: AbortSignal) => Promise<T> | T;
59
+ declare function resource<T, A = void>(source: ResourceSource<T, A>, options?: ResourceOptions<T, A>): ResourceState<T>;
60
+
61
+ type StreamStatus = "idle" | "active" | "done" | "error";
62
+ interface StreamResourceState<T> {
63
+ value: ReadSignal<T | undefined>;
64
+ status: ReadSignal<StreamStatus>;
65
+ /** Last error, or `null` when there is none. Initial value is `null`; a (re)start resets it to `null`. */
66
+ error: ReadSignal<unknown>;
67
+ /** Abort the in-flight stream and stop reacting to `args`. */
68
+ dispose(): void;
69
+ }
70
+ interface StreamResourceOptions<T, C, A> {
71
+ /** Reactive inputs. Reading signals here wires restart-on-change. */
72
+ args?: () => A;
73
+ /** Combine the running value with each chunk. Default: latest (replace). */
74
+ fold?: (acc: T | undefined, chunk: C) => T;
75
+ /** Seed value, and the value `value` is reset to on each (re)start. */
76
+ initial?: T;
77
+ }
78
+ type StreamProducer<C> = AsyncIterable<C> | ReadableStream<C>;
79
+ /**
80
+ * Produce a stream for the current `args`. The `signal` aborts on restart/dispose —
81
+ * the source **MUST honor it** (this is a hard contract, not a suggestion): a
82
+ * `ReadableStream` is fully cancelled via `reader.cancel()`, and an async generator
83
+ * should observe `signal` (e.g. reject/break on `signal.aborted`) so a parked `await`
84
+ * can unwind. On abort the adapter also calls the iterator's `return()` to run a
85
+ * generator's `finally`, but a generator that parks forever while ignoring `signal`
86
+ * cannot be force-unwound and will leak. See the module header's CONTRACT.
87
+ */
88
+ type StreamSource<C, A> = (args: A, signal: AbortSignal) => StreamProducer<C> | Promise<StreamProducer<C>>;
89
+ declare function streamResource<T, C = T, A = void>(source: StreamSource<C, A>, options?: StreamResourceOptions<T, C, A>): StreamResourceState<T>;
90
+
91
+ interface WcBindableProperty<V = unknown> {
92
+ readonly name: string;
93
+ readonly event: string;
94
+ readonly getter?: (event: Event) => V;
95
+ }
96
+ interface WcBindableDescriptor {
97
+ readonly properties: readonly WcBindableProperty[];
98
+ readonly inputs?: readonly {
99
+ readonly name: string;
100
+ }[];
101
+ readonly commands?: readonly {
102
+ readonly name: string;
103
+ }[];
104
+ }
105
+ type OpenCommandMap = Record<string, (...args: any[]) => unknown>;
106
+ interface NodeShape {
107
+ /** propertyName → latest-snapshot value type (what `signals[name]` / `on(name)` carry). */
108
+ signals?: Record<string, unknown>;
109
+ /** inputName → settable value type (what `set` / `bindInput` accept). */
110
+ inputs?: Record<string, unknown>;
111
+ /** commandName → its call signature (what `command` / `bindCommand` invoke). */
112
+ commands?: OpenCommandMap;
113
+ }
114
+ interface DefaultNodeShape extends NodeShape {
115
+ signals: Record<string, unknown>;
116
+ inputs: Record<string, unknown>;
117
+ commands: OpenCommandMap;
118
+ }
119
+ type SignalsOf<S extends NodeShape> = S["signals"] extends Record<string, unknown> ? S["signals"] : Record<string, unknown>;
120
+ type InputsOf<S extends NodeShape> = S["inputs"] extends Record<string, unknown> ? S["inputs"] : Record<string, unknown>;
121
+ type CommandsOf<S extends NodeShape> = S["commands"] extends OpenCommandMap ? S["commands"] : OpenCommandMap;
122
+ /** Options for `on` — how each event emission folds into the stream signal. */
123
+ interface EventStreamOptions<T, C> {
124
+ /** Combine the running value with each emission. Default: latest (replace). */
125
+ fold?: (acc: T | undefined, chunk: C) => T;
126
+ /** Seed value before the first emission. */
127
+ initial?: T;
128
+ }
129
+ interface BoundNode<S extends NodeShape = DefaultNodeShape> {
130
+ /**
131
+ * Output properties as read-only signals (latest value), keyed by property name and
132
+ * typed from the node shape: `bound.signals.value.get()` carries the declared value
133
+ * type (no longer `unknown`). Keys are the declared property names.
134
+ */
135
+ readonly signals: {
136
+ readonly [K in keyof SignalsOf<S>]: ReadSignal<SignalsOf<S>[K]>;
137
+ };
138
+ /**
139
+ * An event-token STREAM for a declared property's event: a read signal that folds
140
+ * every emission (latest by default). Unlike `signals[prop]`, it updates on EVERY
141
+ * emit — even when the derived value is equal — because it models occurrences, not
142
+ * state. The chunk per emit is `getter(event)` if the property declares one, else
143
+ * the property's current value.
144
+ *
145
+ * With no fold the value type defaults to the property's declared snapshot type; a
146
+ * `fold` may reshape it (explicit `T`/`C` type args override).
147
+ */
148
+ on<K extends keyof SignalsOf<S>>(prop: K): ReadSignal<SignalsOf<S>[K] | undefined>;
149
+ on<T, C = SignalsOf<S>[keyof SignalsOf<S>]>(prop: keyof SignalsOf<S>, options: EventStreamOptions<T, C>): ReadSignal<T | undefined>;
150
+ /** Write a declared input on the node (imperative); value type checked against the shape. */
151
+ set<K extends keyof InputsOf<S>>(name: K, value: InputsOf<S>[K]): void;
152
+ /**
153
+ * Reactively write a declared input from a signal: an effect mirrors `source` into
154
+ * `node[name]`. A same-value guard (`node[name] !== v`) skips redundant writes, so
155
+ * a property whose write re-dispatches an event cannot feed back into an infinite
156
+ * loop. Returns a disposer; the effect is also torn down by `dispose()`.
157
+ */
158
+ bindInput<K extends keyof InputsOf<S>>(name: K, source: ReadSignal<InputsOf<S>[K]>): () => void;
159
+ /** Invoke a declared command on the node (imperative); args/return typed from the shape. */
160
+ command<K extends keyof CommandsOf<S>>(name: K, ...args: Parameters<CommandsOf<S>[K]>): ReturnType<CommandsOf<S>[K]>;
161
+ /**
162
+ * Command-token: invoke a declared command whenever `trigger` CHANGES. The initial
163
+ * value does not fire (subscribe-without-firing), so a command like `abort` is not
164
+ * triggered on mount. `mapArgs` shapes the call arguments from the trigger value
165
+ * (default: the value itself as a single argument). Returns a disposer; also torn
166
+ * down by `dispose()`.
167
+ */
168
+ bindCommand<K extends keyof CommandsOf<S>, V = unknown>(name: K, trigger: ReadSignal<V>, mapArgs?: (value: V) => Parameters<CommandsOf<S>[K]>): () => void;
169
+ /**
170
+ * Detach all listeners/effects and make the adapter inert. After dispose the output
171
+ * signals/streams stop updating and `set`/`command`/`bindInput`/`bindCommand`/`on`
172
+ * throw (use-after-dispose). Idempotent.
173
+ */
174
+ dispose(): void;
175
+ }
176
+ declare const DISPOSED_ERROR: unique symbol;
177
+ /**
178
+ * Error thrown when a {@link BoundNode} method is invoked after the adapter has been
179
+ * disposed. Every surface that mutates or subscribes — `on` / `set` / `bindInput` /
180
+ * `command` / `bindCommand` — throws this once `dispose()` has run, so a
181
+ * use-after-dispose is loud instead of silently writing/invoking on an inert adapter.
182
+ *
183
+ * Prefer {@link isDisposedError} over `instanceof` for the check: the brand survives a
184
+ * bundler that duplicates the class across realms, where `instanceof` would not. Use it
185
+ * to make teardown-order races robust — e.g. swallow a post-dispose throw while letting
186
+ * any other error surface:
187
+ *
188
+ * ```ts
189
+ * try {
190
+ * bound.command("abort");
191
+ * } catch (err) {
192
+ * if (!isDisposedError(err)) throw err; // expected during teardown; ignore
193
+ * }
194
+ * ```
195
+ */
196
+ declare class DisposedError extends Error {
197
+ readonly [DISPOSED_ERROR] = true;
198
+ }
199
+ /**
200
+ * True if `err` is a {@link DisposedError} — i.e. a use-after-dispose error thrown by a
201
+ * {@link BoundNode} method after `dispose()`. Brand-based (checks a Symbol on the
202
+ * instance), so it stays correct even if a consumer's bundler duplicates the
203
+ * `DisposedError` class across module realms, where `err instanceof DisposedError`
204
+ * could be false.
205
+ */
206
+ declare function isDisposedError(err: unknown): boolean;
207
+ /**
208
+ * Adapt a wc-bindable node into signals. The public `target` parameter is a plain
209
+ * `EventTarget` (the internal `Record<string, any>` indexing is cast away here, not
210
+ * leaked to callers — see {@link NodeTarget}). Pass an optional {@link NodeShape} type
211
+ * argument to type the result; omit it for the back-compat all-`unknown` shape.
212
+ *
213
+ * @example typed
214
+ * interface FetchShape extends NodeShape {
215
+ * signals: { value: Person[]; loading: boolean };
216
+ * inputs: { url: string };
217
+ * commands: { fetch: (url: string) => Promise<Person[]>; abort: () => void };
218
+ * }
219
+ * const bound = bindNode<FetchShape>(fetchEl, FetchCore.wcBindable);
220
+ * bound.signals.value.get(); // ReadSignal<Person[]>
221
+ * bound.set("url", "/api/people"); // string enforced
222
+ */
223
+ declare function bindNode<S extends NodeShape = DefaultNodeShape>(target: EventTarget, descriptor?: WcBindableDescriptor): BoundNode<S>;
224
+ /**
225
+ * Build a `resource` source from a wc-bindable node, bridging the resource's
226
+ * AbortSignal to the node's cancel command (default `"abort"`) before delegating to
227
+ * `run`. This generalizes the earlier hand-wired `sig → core.abort()` bridge (docs §8
228
+ * (e), §5-2): wrap the result in `resource({ args })` and any node that declares an
229
+ * abort command gets switchMap-style cancel/restart for free — an `args` change
230
+ * aborts the in-flight call (firing the node's abort command, which cancels its real
231
+ * AbortController) and starts the next.
232
+ *
233
+ * The node's own value/loading/error stay available via `bound.signals`; `resource`
234
+ * here is used for the cancel/restart lifecycle, not to re-derive that triad.
235
+ *
236
+ * @example
237
+ * const bound = bindNode(fetchEl);
238
+ * const r = resource(
239
+ * nodeSource(bound, (b, id) => b.command("fetch", `/api/${id}`)),
240
+ * { args: () => id.get() },
241
+ * );
242
+ */
243
+ declare function nodeSource<T, A = void, S extends NodeShape = DefaultNodeShape>(bound: BoundNode<S>, run: (node: BoundNode<S>, args: A) => Promise<T> | T, options?: {
244
+ abort?: string;
245
+ }): ResourceSource<T, A>;
246
+
247
+ declare const Fragment: unique symbol;
248
+ type Child = unknown;
249
+ type Props = Record<string, unknown> | null;
250
+ type Component = (props: Record<string, unknown> & {
251
+ children?: Child[];
252
+ }) => Node | Child[];
253
+ declare function h(tag: string | typeof Fragment | Component, props?: Props, ...children: Child[]): Node;
254
+ /** Append `child` into `container`, resolving fragments/arrays. */
255
+ declare function render(child: Child, container: Node): Node;
256
+ type ListAccessor<T> = ReadSignal<readonly T[]> | (() => readonly T[]);
257
+ type ForEach<T> = (item: T, index: () => number) => Node;
258
+ type IndexEach<T> = (item: () => T, index: number) => Node;
259
+ /**
260
+ * The return type of {@link For} / {@link Index}: a keyed list that owns an anchored
261
+ * region and reconciles rows in place. Exported (D4) so consumers can annotate
262
+ * helpers that build or pass around lists. `mount` is called by `h`/`render` when the
263
+ * list is used as a child; `__wcsList` is the brand `appendChild` checks.
264
+ */
265
+ interface ListView {
266
+ readonly __wcsList: true;
267
+ mount(parent: Node): void;
268
+ }
269
+ interface ForOptions<T> {
270
+ /** Derive a unique key per item. Default: the item value itself (identity). */
271
+ key?: (item: T, index: number) => unknown;
272
+ }
273
+ declare function For<T>(list: ListAccessor<T>, each: ForEach<T>, options?: ForOptions<T>): ListView;
274
+ declare function Index<T>(list: ListAccessor<T>, each: IndexEach<T>): ListView;
275
+ declare abstract class SignalsElementType extends HTMLElement {
276
+ /** Build the view with `h` + signals. Runs inside an ownership root. */
277
+ protected abstract render(): Node;
278
+ /** Mount target. Override to return a shadow root; default is light DOM. */
279
+ protected getMountPoint(): Node;
280
+ connectedCallback(): void;
281
+ disconnectedCallback(): void;
282
+ }
283
+ /**
284
+ * The lifecycle base contract: the abstract constructor type of the `SignalsElement`
285
+ * base. Abstract, so `new SignalsElement()` is rejected (the base has an unimplemented
286
+ * `render`) while `class X extends SignalsElement` is allowed. Returned by
287
+ * {@link createSignalsElement} and the type of the {@link SignalsElement} value.
288
+ */
289
+ type SignalsElementClass = typeof SignalsElementType;
290
+ /**
291
+ * Build (once, memoized) the `SignalsElement` lifecycle base class. Resolves
292
+ * `HTMLElement` at CALL TIME, so importing `@wcstack/signals/dom` no longer requires
293
+ * a DOM at module-evaluation time — only *calling* this (or subclassing
294
+ * {@link SignalsElement}) does.
295
+ *
296
+ * Contract:
297
+ * - The `.` entry (`@wcstack/signals`) is non-DOM: it evaluates and runs in SSR,
298
+ * Node, and Web Workers with no DOM globals.
299
+ * - The `./dom` entry (`@wcstack/signals/dom`) may now also be *evaluated* without a
300
+ * DOM (so an SSR pre-pass that imports it for the headless re-exports does not
301
+ * crash). The DOM-touching surface — `h`/`render`/`For`/`Index` and this base —
302
+ * still requires DOM globals when actually used.
303
+ *
304
+ * @throws a clear `Error` (not a raw `ReferenceError`) when no `HTMLElement` global is
305
+ * present, naming the offending entry so the failure is self-explanatory.
306
+ */
307
+ declare function createSignalsElement(): SignalsElementClass;
308
+ declare const SignalsElement: SignalsElementClass;
309
+
310
+ export { DisposedError, For, Fragment, Index, SignalsElement, bindNode, computed, createRoot, createSignalsElement, effect, flushSync, h, isDisposedError, nodeSource, onCleanup, render, resource, signal, streamResource };
311
+ export type { BoundNode, Child, Cleanup, Component, DefaultNodeShape, EffectHandle, Equals, EventStreamOptions, ForEach, ForOptions, IndexEach, ListAccessor, ListView, NodeShape, Props, ReadSignal, ResourceOptions, ResourceSource, ResourceState, SignalsElementClass, StreamProducer, StreamResourceOptions, StreamResourceState, StreamSource, StreamStatus, WcBindableDescriptor, WcBindableProperty, WriteSignal };