@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/README.ja.md +437 -0
- package/README.md +439 -0
- package/dist/core-DCHZTbGv.esm.min.js +2 -0
- package/dist/core-DCHZTbGv.esm.min.js.map +1 -0
- package/dist/core-DRLhLwcb.esm.js +0 -0
- package/dist/core-DRLhLwcb.esm.js.map +1 -0
- package/dist/dom.d.ts +311 -0
- package/dist/dom.esm.js +743 -0
- package/dist/dom.esm.js.map +1 -0
- package/dist/dom.esm.min.js +2 -0
- package/dist/dom.esm.min.js.map +1 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +74 -0
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 };
|