preact-sigma 2.2.2 → 2.3.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.md +11 -291
- package/dist/index.d.mts +122 -34
- package/dist/index.mjs +68 -18
- package/docs/context.md +104 -0
- package/examples/async-commit.ts +42 -0
- package/examples/basic-counter.ts +23 -0
- package/examples/command-palette.tsx +211 -0
- package/examples/observe-and-restore.ts +27 -0
- package/examples/setup-act.ts +34 -0
- package/examples/sigma-target.ts +26 -0
- package/examples/signal-access.ts +28 -0
- package/package.json +3 -2
- package/llms.txt +0 -437
package/README.md
CHANGED
|
@@ -1,56 +1,21 @@
|
|
|
1
1
|
# preact-sigma
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`preact-sigma` is a typed state-model builder for Preact and TypeScript. It keeps top-level public state reactive, derived reads local to the model, writes explicit through actions, and side effects owned by explicit setup.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Immer-style writes
|
|
9
|
-
- explicit setup and cleanup
|
|
10
|
-
- typed events
|
|
11
|
-
- a constructor you can instantiate anywhere
|
|
12
|
-
|
|
13
|
-
Use it when your state has started to feel like more than "some values in a component."
|
|
14
|
-
|
|
15
|
-
Instead of spreading logic across loose signals, reducers, effects, and cleanup code, you define one model with:
|
|
16
|
-
|
|
17
|
-
- top-level state
|
|
18
|
-
- derived reads
|
|
19
|
-
- write methods
|
|
20
|
-
- side-effect setup
|
|
21
|
-
- optional events
|
|
22
|
-
|
|
23
|
-
Then you create instances wherever they make sense: inside a component, in a shared module, or in plain TypeScript.
|
|
24
|
-
|
|
25
|
-
Under the hood, each top-level state property is backed by its own Preact signal, while writes happen through actions with Immer-backed mutation semantics.
|
|
26
|
-
|
|
27
|
-
## Why you would use it
|
|
28
|
-
|
|
29
|
-
`preact-sigma` is a good fit when you want state and behavior to live together.
|
|
30
|
-
|
|
31
|
-
It is especially useful when you need to:
|
|
32
|
-
|
|
33
|
-
- keep state, derived values, mutations, and lifecycle in one place
|
|
34
|
-
- create multiple instances of the same state model
|
|
35
|
-
- expose readonly public state while keeping writes explicit
|
|
36
|
-
- get fine-grained reactivity without wiring together a pile of loose signals
|
|
37
|
-
- own timers, subscriptions, listeners, or nested setup with clear cleanup
|
|
38
|
-
|
|
39
|
-
If a couple of plain signals are enough, use plain signals.
|
|
40
|
-
`preact-sigma` is for the point where state starts acting like a small system.
|
|
41
|
-
|
|
42
|
-
## Install
|
|
7
|
+
## Installation
|
|
43
8
|
|
|
44
9
|
```bash
|
|
45
10
|
npm install preact-sigma
|
|
46
11
|
```
|
|
47
12
|
|
|
48
|
-
##
|
|
13
|
+
## Quick Example
|
|
49
14
|
|
|
50
15
|
```ts
|
|
51
16
|
import { SigmaType } from "preact-sigma";
|
|
52
17
|
|
|
53
|
-
const Counter = new SigmaType<{ count: number }>()
|
|
18
|
+
const Counter = new SigmaType<{ count: number }>("Counter")
|
|
54
19
|
.defaultState({
|
|
55
20
|
count: 0,
|
|
56
21
|
})
|
|
@@ -73,255 +38,10 @@ console.log(counter.count); // 1
|
|
|
73
38
|
console.log(counter.doubled); // 2
|
|
74
39
|
```
|
|
75
40
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- state is public and reactive
|
|
79
|
-
- derived values live in `computed(...)`
|
|
80
|
-
- writes happen in `actions(...)`
|
|
81
|
-
- an instance behaves like a small stateful object
|
|
82
|
-
|
|
83
|
-
## The mental model
|
|
84
|
-
|
|
85
|
-
A sigma model is made from a few simple pieces.
|
|
86
|
-
|
|
87
|
-
### `defaultState(...)`
|
|
88
|
-
|
|
89
|
-
Defines the top-level state for each instance.
|
|
90
|
-
|
|
91
|
-
Each top-level property becomes a reactive public property on the instance.
|
|
92
|
-
|
|
93
|
-
Use plain values for simple defaults, or zero-argument functions when each instance needs a fresh object or array.
|
|
94
|
-
|
|
95
|
-
### `computed(...)`
|
|
96
|
-
|
|
97
|
-
Use computeds for derived values that take no arguments.
|
|
98
|
-
|
|
99
|
-
They behave like tracked getters:
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
.completedCount() // no
|
|
103
|
-
todoList.completedCount // yes
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### `queries(...)`
|
|
107
|
-
|
|
108
|
-
Use queries for reactive reads that need arguments.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
visibleTodos("open");
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Queries are for reading, not writing.
|
|
115
|
-
|
|
116
|
-
### `actions(...)`
|
|
117
|
-
|
|
118
|
-
Actions are where state changes happen.
|
|
119
|
-
|
|
120
|
-
Outside an action, public state is readonly. Inside an action, you write with normal mutation syntax and sigma handles the draft/update flow for you.
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
.actions({
|
|
124
|
-
rename(title: string) {
|
|
125
|
-
this.title = title;
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### `setup(...)`
|
|
131
|
-
|
|
132
|
-
Setup is where side effects belong.
|
|
133
|
-
|
|
134
|
-
Use it for things like:
|
|
135
|
-
|
|
136
|
-
- timers
|
|
137
|
-
- event listeners
|
|
138
|
-
- subscriptions
|
|
139
|
-
- nested model setup
|
|
140
|
-
- storage sync
|
|
141
|
-
|
|
142
|
-
Setup is explicit. A new instance does not automatically run setup. When setup does run, it returns one cleanup function that tears down everything that instance owns.
|
|
143
|
-
|
|
144
|
-
### Events
|
|
145
|
-
|
|
146
|
-
Use events when the model needs to notify the outside world without exposing mutable internals.
|
|
147
|
-
|
|
148
|
-
Emit inside actions or setup:
|
|
149
|
-
|
|
150
|
-
```ts
|
|
151
|
-
this.emit("saved", { count: 3 });
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Listen from the outside:
|
|
155
|
-
|
|
156
|
-
```ts
|
|
157
|
-
const stop = instance.on("saved", ({ count }) => {
|
|
158
|
-
console.log(count);
|
|
159
|
-
});
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## A more realistic example
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
import { SigmaType } from "preact-sigma";
|
|
166
|
-
|
|
167
|
-
type Todo = {
|
|
168
|
-
id: string;
|
|
169
|
-
title: string;
|
|
170
|
-
done: boolean;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const TodoList = new SigmaType<
|
|
174
|
-
{ draft: string; todos: Todo[]; saving: boolean },
|
|
175
|
-
{ saved: { count: number } }
|
|
176
|
-
>()
|
|
177
|
-
.defaultState({
|
|
178
|
-
draft: "",
|
|
179
|
-
todos: [],
|
|
180
|
-
saving: false,
|
|
181
|
-
})
|
|
182
|
-
.computed({
|
|
183
|
-
remainingCount() {
|
|
184
|
-
return this.todos.filter((todo) => !todo.done).length;
|
|
185
|
-
},
|
|
186
|
-
})
|
|
187
|
-
.queries({
|
|
188
|
-
visibleTodos(filter: "all" | "open" | "done") {
|
|
189
|
-
return this.todos.filter((todo) => {
|
|
190
|
-
if (filter === "open") return !todo.done;
|
|
191
|
-
if (filter === "done") return todo.done;
|
|
192
|
-
return true;
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
})
|
|
196
|
-
.actions({
|
|
197
|
-
setDraft(draft: string) {
|
|
198
|
-
this.draft = draft;
|
|
199
|
-
},
|
|
200
|
-
addTodo() {
|
|
201
|
-
if (!this.draft.trim()) return;
|
|
202
|
-
|
|
203
|
-
this.todos.push({
|
|
204
|
-
id: crypto.randomUUID(),
|
|
205
|
-
title: this.draft,
|
|
206
|
-
done: false,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
this.draft = "";
|
|
210
|
-
},
|
|
211
|
-
toggleTodo(id: string) {
|
|
212
|
-
const todo = this.todos.find((todo) => todo.id === id);
|
|
213
|
-
if (todo) todo.done = !todo.done;
|
|
214
|
-
},
|
|
215
|
-
async save() {
|
|
216
|
-
this.saving = true;
|
|
217
|
-
this.commit(); // publish "saving" before awaiting
|
|
218
|
-
|
|
219
|
-
await fetch("/api/todos", {
|
|
220
|
-
method: "POST",
|
|
221
|
-
body: JSON.stringify(this.todos),
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
this.saving = false;
|
|
225
|
-
this.commit(); // publish before emitting
|
|
226
|
-
this.emit("saved", { count: this.todos.length });
|
|
227
|
-
},
|
|
228
|
-
})
|
|
229
|
-
.setup(function (storageKey: string) {
|
|
230
|
-
const interval = window.setInterval(() => {
|
|
231
|
-
localStorage.setItem(storageKey, JSON.stringify(this.todos));
|
|
232
|
-
}, 1000);
|
|
233
|
-
|
|
234
|
-
return [() => window.clearInterval(interval)];
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const todoList = new TodoList();
|
|
238
|
-
const cleanup = todoList.setup("todos-demo");
|
|
239
|
-
|
|
240
|
-
const stop = todoList.on("saved", ({ count }) => {
|
|
241
|
-
console.log(`Saved ${count} todos`);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
todoList.setDraft("Rewrite the README");
|
|
245
|
-
todoList.addTodo();
|
|
246
|
-
|
|
247
|
-
console.log(todoList.remainingCount);
|
|
248
|
-
console.log(todoList.visibleTodos("open"));
|
|
249
|
-
|
|
250
|
-
await todoList.save();
|
|
251
|
-
|
|
252
|
-
stop();
|
|
253
|
-
cleanup();
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## The one rule to remember about actions
|
|
257
|
-
|
|
258
|
-
For normal synchronous actions, mutate state and return. You usually do **not** need `this.commit()`.
|
|
259
|
-
|
|
260
|
-
Use `this.commit()` when you have unpublished changes and the action is about to cross a boundary like:
|
|
261
|
-
|
|
262
|
-
- `await`
|
|
263
|
-
- `emit(...)`
|
|
264
|
-
- another action boundary that should not keep using the current draft
|
|
265
|
-
|
|
266
|
-
In practice, that means:
|
|
267
|
-
|
|
268
|
-
- sync action with no boundary: mutate and return
|
|
269
|
-
- async action before `await`: `commit()` if you want those changes published first
|
|
270
|
-
- action before `emit(...)`: `commit()` if there are pending changes
|
|
271
|
-
|
|
272
|
-
That rule is the main thing to learn beyond the basic API.
|
|
273
|
-
|
|
274
|
-
## In Preact
|
|
275
|
-
|
|
276
|
-
`preact-sigma` works outside components, but it also has a nice component story.
|
|
277
|
-
|
|
278
|
-
Use `useSigma(...)` when the component should own one instance:
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
import { useSigma } from "preact-sigma";
|
|
282
|
-
|
|
283
|
-
const todoList = useSigma(() => new TodoList(), ["todos-demo"]);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
If the model defines setup handlers, `useSigma(...)` runs setup for that component-owned instance and cleans it up automatically when setup params change or the component unmounts.
|
|
287
|
-
|
|
288
|
-
Use `useListener(...)` for component-scoped event subscriptions:
|
|
289
|
-
|
|
290
|
-
```ts
|
|
291
|
-
import { useListener } from "preact-sigma";
|
|
292
|
-
|
|
293
|
-
useListener(todoList, "saved", ({ count }) => {
|
|
294
|
-
console.log(`Saved ${count} todos`);
|
|
295
|
-
});
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## What you get out of the box
|
|
299
|
-
|
|
300
|
-
Beyond the core model API, `preact-sigma` also includes:
|
|
301
|
-
|
|
302
|
-
- `observe(...)` for reacting to committed state changes
|
|
303
|
-
- optional Immer patch delivery in observers
|
|
304
|
-
- `snapshot(...)` and `replaceState(...)` for restore/undo-like flows
|
|
305
|
-
- `get(key)` when you need direct signal access for a state key or computed
|
|
306
|
-
|
|
307
|
-
## Why this shape exists
|
|
308
|
-
|
|
309
|
-
`preact-sigma` exists for the space between two extremes:
|
|
310
|
-
|
|
311
|
-
- **too small for a big store abstraction**
|
|
312
|
-
- **too stateful for a handful of loose signals**
|
|
313
|
-
|
|
314
|
-
It keeps the ergonomics of working with a model object, while preserving:
|
|
315
|
-
|
|
316
|
-
- readonly public reads
|
|
317
|
-
- explicit write boundaries
|
|
318
|
-
- fine-grained reactivity
|
|
319
|
-
- explicit ownership of side effects
|
|
320
|
-
|
|
321
|
-
That makes it useful for app state that has real behavior, not just values.
|
|
322
|
-
|
|
323
|
-
## More docs
|
|
41
|
+
## Documentation Map
|
|
324
42
|
|
|
325
|
-
- [`
|
|
326
|
-
-
|
|
327
|
-
-
|
|
43
|
+
- Concepts, lifecycle, invariants, and API selection live in [`docs/context.md`](./docs/context.md).
|
|
44
|
+
- Quick-start usage lives in [`examples/basic-counter.ts`](./examples/basic-counter.ts).
|
|
45
|
+
- An advanced end-to-end example lives in [`examples/command-palette.tsx`](./examples/command-palette.tsx).
|
|
46
|
+
- Focused examples for non-obvious APIs live in [`examples/async-commit.ts`](./examples/async-commit.ts), [`examples/observe-and-restore.ts`](./examples/observe-and-restore.ts), [`examples/setup-act.ts`](./examples/setup-act.ts), [`examples/sigma-target.ts`](./examples/sigma-target.ts), and [`examples/signal-access.ts`](./examples/signal-access.ts).
|
|
47
|
+
- Exact exported signatures live in `dist/index.d.mts` after `pnpm build`.
|
package/dist/index.d.mts
CHANGED
|
@@ -59,7 +59,7 @@ type SigmaRef<T = unknown> = T & SigmaRefBrand;
|
|
|
59
59
|
type AnyEvents = Record<string, object | void>;
|
|
60
60
|
/** The top-level state object shape used by sigma types. */
|
|
61
61
|
type AnyState = Record<string, unknown>;
|
|
62
|
-
/** The object accepted by `.defaultState(...)
|
|
62
|
+
/** The object accepted by `.defaultState(...)`, where each property may be a value or a zero-argument initializer. */
|
|
63
63
|
type AnyDefaultState<TState extends AnyState> = { [K in keyof TState]?: DefaultStateValue<TState[K]> };
|
|
64
64
|
/** A cleanup resource supported by `.setup(...)`, including function, `dispose()`, and `Symbol.dispose` cleanup. */
|
|
65
65
|
type AnyResource = Cleanup | Disposable | DisposableLike | AbortController;
|
|
@@ -68,16 +68,16 @@ type ComputedContext<TState extends AnyState, TComputeds extends object> = Immut
|
|
|
68
68
|
type QueryMethods<TQueries extends object | undefined> = [undefined] extends [TQueries] ? never : { [K in keyof TQueries]: TQueries[K] extends AnyFunction ? (...args: Parameters<TQueries[K]>) => ReturnType<TQueries[K]> : never };
|
|
69
69
|
type ActionMethods<TActions extends object | undefined> = [undefined] extends [TActions] ? never : { [K in keyof TActions]: TActions[K] extends AnyFunction ? (...args: Parameters<TActions[K]>) => ReturnType<TActions[K]> : never };
|
|
70
70
|
type EventMethods<TEvents extends AnyEvents | undefined> = [undefined] extends [TEvents] ? never : {
|
|
71
|
-
readonly [sigmaEventsBrand]: TEvents;
|
|
71
|
+
readonly [sigmaEventsBrand]: TEvents; /** Registers a typed event listener and returns an unsubscribe function. */
|
|
72
72
|
on<TEvent extends string & keyof TEvents>(name: TEvent, listener: [TEvents[TEvent]] extends [void] ? () => void : (payload: TEvents[TEvent]) => void): Cleanup;
|
|
73
73
|
};
|
|
74
74
|
type SetupMethods<TSetupArgs extends any[] | undefined> = [TSetupArgs] extends [undefined] ? never : {
|
|
75
|
-
setup(...args: Extract<TSetupArgs, any[]>): Cleanup;
|
|
75
|
+
/** Runs every registered setup handler and returns one cleanup function for the active setup. */setup(...args: Extract<TSetupArgs, any[]>): Cleanup;
|
|
76
76
|
};
|
|
77
77
|
type ReadonlyContext<TState extends AnyState, TComputeds extends object, TQueries extends object> = Immutable<TState> & ComputedValues<TComputeds> & QueryMethods<TQueries>;
|
|
78
78
|
type Emit<TEvents extends AnyEvents> = <TEvent extends string & keyof TEvents>(name: TEvent, ...args: [TEvents[TEvent]] extends [void] ? [] : [payload: TEvents[TEvent]]) => void;
|
|
79
79
|
type ActionContext<TState extends AnyState, TEvents extends AnyEvents, TComputeds extends object, TQueries extends object, TActions extends object> = Draft<TState> & ComputedValues<TComputeds> & QueryMethods<TQueries> & ActionMethods<TActions> & {
|
|
80
|
-
/** Publishes the current action draft immediately so later boundaries use committed state. */commit(): void;
|
|
80
|
+
/** Publishes the current action draft immediately so later boundaries use committed state. */commit(): void; /** Emits a typed event from the current action. */
|
|
81
81
|
emit: Emit<TEvents>;
|
|
82
82
|
};
|
|
83
83
|
type DefinitionEvents<T extends SigmaDefinition> = T["events"] extends AnyEvents ? T["events"] : {};
|
|
@@ -94,7 +94,7 @@ type AnySigmaStateWithEvents<TEvents extends AnyEvents> = AnySigmaState & {
|
|
|
94
94
|
};
|
|
95
95
|
/** Options accepted by `.observe(...)`. */
|
|
96
96
|
type SigmaObserveOptions = {
|
|
97
|
-
patches?: boolean;
|
|
97
|
+
/** Includes Immer patches and inverse patches on the delivered change object. */patches?: boolean;
|
|
98
98
|
};
|
|
99
99
|
/** The change object delivered to `.observe(...)` listeners. */
|
|
100
100
|
type SigmaObserveChange<TState extends AnyState, TWithPatches extends boolean = false> = {
|
|
@@ -113,15 +113,16 @@ type SigmaDefinition = {
|
|
|
113
113
|
setupArgs?: any[];
|
|
114
114
|
};
|
|
115
115
|
interface SignalAccessors<T extends object> {
|
|
116
|
+
/** Returns the underlying signal for a top-level state property or computed. */
|
|
116
117
|
get<K extends keyof T>(key: K): ReadonlySignal<T[K]>;
|
|
117
118
|
}
|
|
118
119
|
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
119
120
|
type Simplify<T> = {} & { [K in keyof T]: T[K] };
|
|
120
121
|
type MapSigmaDefinition<T extends SigmaDefinition> = keyof T extends infer K ? K extends "state" ? Immutable<T[K]> & SignalAccessors<Immutable<T[K]>> : K extends "computeds" ? ComputedValues<T[K]> & SignalAccessors<ComputedValues<T[K]>> : K extends "queries" ? QueryMethods<T[K]> : K extends "actions" ? ActionMethods<T[K]> : K extends "events" ? EventMethods<T[K]> : K extends "setupArgs" ? SetupMethods<T[K]> : never : never;
|
|
121
|
-
/** The public instance shape produced by a configured sigma type. */
|
|
122
|
+
/** The public instance shape produced by a configured sigma type, including signal access inferred from the definition. */
|
|
122
123
|
type SigmaState<T extends SigmaDefinition = SigmaDefinition> = AnySigmaState & Simplify<UnionToIntersection<MapSigmaDefinition<T>>>;
|
|
123
124
|
type SetupContext<T extends SigmaDefinition> = SigmaState<T> & {
|
|
124
|
-
act<TResult>(fn: (this: ActionContext<T["state"], DefinitionEvents<T>, DefinitionComputeds<T>, DefinitionQueries<T>, DefinitionActions<T>>) => TResult): TResult;
|
|
125
|
+
/** Runs a synchronous anonymous action from setup so reads and writes use normal action semantics. */act<TResult>(fn: (this: ActionContext<T["state"], DefinitionEvents<T>, DefinitionComputeds<T>, DefinitionQueries<T>, DefinitionActions<T>>) => TResult): TResult; /** Emits a typed event from setup. */
|
|
125
126
|
emit: T["events"] extends object ? Emit<T["events"]> : never;
|
|
126
127
|
};
|
|
127
128
|
type MergeObjects<TLeft extends object, TRight> = [TRight] extends [object] ? Extract<Simplify<Omit<TLeft, keyof TRight> & TRight>, TLeft> : TLeft;
|
|
@@ -136,39 +137,56 @@ type InferSetupArgs<T extends AnySigmaState> = T extends {
|
|
|
136
137
|
} ? TArgs : never;
|
|
137
138
|
//#endregion
|
|
138
139
|
//#region src/internal/runtime.d.ts
|
|
139
|
-
/** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled. */
|
|
140
|
+
/** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled and the setting is shared across instances. */
|
|
140
141
|
declare function setAutoFreeze(autoFreeze: boolean): void;
|
|
141
142
|
/**
|
|
142
143
|
* Returns a shallow snapshot of an instance's committed public state.
|
|
143
144
|
*
|
|
144
145
|
* The snapshot includes one own property for each top-level state key and reads
|
|
145
|
-
* the current committed value for that key.
|
|
146
|
-
* instance's sigma-state
|
|
146
|
+
* the current committed value for that key. Nested sigma states remain as
|
|
147
|
+
* referenced values. Its type is inferred from the instance's sigma-state
|
|
148
|
+
* definition.
|
|
147
149
|
*/
|
|
148
150
|
declare function snapshot<T extends AnySigmaState>(publicInstance: T): T extends SigmaState<infer TDefinition> ? Immutable<TDefinition["state"]> : never;
|
|
149
151
|
/**
|
|
150
152
|
* Replaces an instance's committed public state from a snapshot object.
|
|
151
153
|
*
|
|
152
154
|
* The replacement snapshot must be a plain object with exactly the instance's
|
|
153
|
-
* top-level state keys.
|
|
154
|
-
*
|
|
155
|
+
* top-level state keys. It updates committed state outside action semantics and
|
|
156
|
+
* notifies observers when the committed state changes. Its type is inferred
|
|
157
|
+
* from the instance's sigma-state definition.
|
|
155
158
|
*/
|
|
156
159
|
declare function replaceState<T extends AnySigmaState>(publicInstance: T, nextState: T extends SigmaState<infer TDefinition> ? Immutable<TDefinition["state"]> : never): void;
|
|
157
160
|
//#endregion
|
|
158
161
|
//#region src/framework.d.ts
|
|
159
|
-
/** Checks whether a value is a sigma
|
|
160
|
-
declare function isSigmaState(value:
|
|
161
|
-
/**
|
|
162
|
+
/** Checks whether a value is an instance created by a configured sigma type. */
|
|
163
|
+
declare function isSigmaState(value: object): value is AnySigmaState;
|
|
164
|
+
/**
|
|
165
|
+
* Creates a standalone tracked query helper with the same signature as `fn`.
|
|
166
|
+
*
|
|
167
|
+
* Each call is reactive at the call site and does not memoize results across
|
|
168
|
+
* invocations, which makes `query(fn)` a good fit for local tracked helpers
|
|
169
|
+
* that do not need to live on the sigma-state instance.
|
|
170
|
+
*/
|
|
162
171
|
declare function query<TArgs extends any[], TResult>(fn: (this: void, ...args: TArgs) => TResult): typeof fn;
|
|
163
172
|
/**
|
|
164
|
-
* Builds sigma-state constructors by accumulating default state, computeds,
|
|
165
|
-
* observers, actions, and setup handlers.
|
|
173
|
+
* Builds sigma-state constructors by accumulating default state, computeds,
|
|
174
|
+
* queries, observers, actions, and setup handlers.
|
|
175
|
+
*
|
|
176
|
+
* State and event inference starts from `new SigmaType<TState, TEvents>()`.
|
|
177
|
+
* Later builder methods infer names and types from the objects you pass to them.
|
|
166
178
|
*/
|
|
167
179
|
declare class SigmaType<TState extends AnyState, TEvents extends AnyEvents = {}, TDefaults extends AnyDefaultState<TState> = {}, TComputeds extends object = {}, TQueries extends object = {}, TActions extends object = {}, TSetupArgs extends any[] = never> extends Function {
|
|
168
180
|
constructor(name?: string);
|
|
169
181
|
}
|
|
170
182
|
/** The constructor shape exposed by a configured sigma type. */
|
|
171
183
|
interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefaults extends AnyDefaultState<TState>, TComputeds extends object, TQueries extends object, TActions extends object, TSetupArgs extends any[]> {
|
|
184
|
+
/**
|
|
185
|
+
* Creates a sigma-state instance.
|
|
186
|
+
*
|
|
187
|
+
* Constructor input shallowly overrides `defaultState(...)`. Required keys are
|
|
188
|
+
* inferred from whichever state properties still do not have defaults.
|
|
189
|
+
*/
|
|
172
190
|
new (...args: InitialStateInput<TState, TDefaults>): SigmaState<Extract<OmitEmpty<{
|
|
173
191
|
state: TState;
|
|
174
192
|
events: TEvents;
|
|
@@ -177,7 +195,12 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
177
195
|
actions: TActions;
|
|
178
196
|
setupArgs: TSetupArgs;
|
|
179
197
|
}>, SigmaDefinition>>;
|
|
180
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Type-only access to the configured instance shape.
|
|
200
|
+
*
|
|
201
|
+
* This property does not exist at runtime. Its type is inferred from the
|
|
202
|
+
* generics on `new SigmaType<TState, TEvents>()` plus the later builder inputs.
|
|
203
|
+
*/
|
|
181
204
|
get Instance(): SigmaState<Extract<OmitEmpty<{
|
|
182
205
|
state: TState;
|
|
183
206
|
events: TEvents;
|
|
@@ -186,9 +209,34 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
186
209
|
actions: TActions;
|
|
187
210
|
setupArgs: TSetupArgs;
|
|
188
211
|
}>, SigmaDefinition>>;
|
|
212
|
+
/**
|
|
213
|
+
* Adds top-level public state and default values to the builder.
|
|
214
|
+
*
|
|
215
|
+
* Each property becomes a reactive public state property on instances. Use a
|
|
216
|
+
* zero-argument function when each instance needs a fresh object or array.
|
|
217
|
+
*/
|
|
189
218
|
defaultState<TNextDefaults extends AnyDefaultState<TState>>(defaultState: TNextDefaults): SigmaType<TState, TEvents, MergeObjects<TDefaults, TNextDefaults>, TComputeds, TQueries, TActions, TSetupArgs>;
|
|
219
|
+
/**
|
|
220
|
+
* Adds reactive getter properties for derived values that take no arguments.
|
|
221
|
+
*
|
|
222
|
+
* Computed names and return types are inferred from the object you pass.
|
|
223
|
+
* `this` exposes readonly state plus computeds that are already on the builder.
|
|
224
|
+
*/
|
|
190
225
|
computed<TNextComputeds extends object>(computeds: TNextComputeds & ThisType<ComputedContext<TState, MergeObjects<TComputeds, TNextComputeds>>>): SigmaType<TState, TEvents, TDefaults, MergeObjects<TComputeds, TNextComputeds>, TQueries, TActions, TSetupArgs>;
|
|
226
|
+
/**
|
|
227
|
+
* Adds reactive read methods that accept arguments.
|
|
228
|
+
*
|
|
229
|
+
* Query names, parameters, and return types are inferred from the object you
|
|
230
|
+
* pass. Each call tracks reactively at the call site and does not memoize
|
|
231
|
+
* results across invocations.
|
|
232
|
+
*/
|
|
191
233
|
queries<TNextQueries extends object>(queries: TNextQueries & ThisType<ReadonlyContext<TState, TComputeds, MergeObjects<TQueries, TNextQueries>>>): SigmaType<TState, TEvents, TDefaults, TComputeds, MergeObjects<TQueries, TNextQueries>, TActions, TSetupArgs>;
|
|
234
|
+
/**
|
|
235
|
+
* Adds a committed-state observer.
|
|
236
|
+
*
|
|
237
|
+
* Observers run after successful publishes and can opt into Immer patches
|
|
238
|
+
* with `{ patches: true }`.
|
|
239
|
+
*/
|
|
192
240
|
observe(listener: (this: ReadonlyContext<TState, TComputeds, TQueries>, change: SigmaObserveChange<TState>) => void, options?: SigmaObserveOptions & {
|
|
193
241
|
patches?: false | undefined;
|
|
194
242
|
}): this;
|
|
@@ -207,6 +255,12 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
207
255
|
* returns a promise, sigma throws so async boundaries stay explicit.
|
|
208
256
|
*/
|
|
209
257
|
actions<TNextActions extends object>(actions: TNextActions & ThisType<ActionContext<TState, TEvents, TComputeds, TQueries, MergeObjects<TActions, TNextActions>>>): SigmaType<TState, TEvents, TDefaults, TComputeds, TQueries, MergeObjects<TActions, TNextActions>, TSetupArgs>;
|
|
258
|
+
/**
|
|
259
|
+
* Adds an explicit setup handler for side effects and owned resources.
|
|
260
|
+
*
|
|
261
|
+
* Every registered handler runs when `instance.setup(...)` is called, and the
|
|
262
|
+
* setup argument list is inferred from the first handler you add.
|
|
263
|
+
*/
|
|
210
264
|
setup<TNextSetupArgs extends ([TSetupArgs] extends [never] ? any[] : NonNullable<TSetupArgs>)>(setup: (this: SetupContext<{
|
|
211
265
|
state: TState;
|
|
212
266
|
events: TEvents;
|
|
@@ -218,27 +272,61 @@ interface SigmaType<TState extends AnyState, TEvents extends AnyEvents, TDefault
|
|
|
218
272
|
}
|
|
219
273
|
//#endregion
|
|
220
274
|
//#region src/listener.d.ts
|
|
221
|
-
type
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
275
|
+
type InferEventMap<TTarget extends EventTarget> = TTarget extends {
|
|
276
|
+
[sigmaEventsBrand]: infer TEvents extends AnyEvents;
|
|
277
|
+
} ? TEvents : TTarget extends Window ? WindowEventMap : TTarget extends Document ? DocumentEventMap : TTarget extends HTMLBodyElement ? HTMLBodyElementEventMap : TTarget extends HTMLMediaElement ? HTMLMediaElementEventMap : TTarget extends HTMLElement ? HTMLElementEventMap : TTarget extends SVGSVGElement ? SVGSVGElementEventMap : TTarget extends SVGElement ? SVGElementEventMap : never;
|
|
278
|
+
type InferListenerArgs<TEvents extends object, TTarget extends EventTarget, TEvent extends string> = [(TEvent extends keyof TEvents ? TEvents[TEvent] : never) extends infer TPayload ? TTarget extends {
|
|
279
|
+
[sigmaEventsBrand]: TEvents;
|
|
280
|
+
} ? [TPayload] extends [never] ? never : [TPayload] extends [void] ? undefined : TPayload : ([TPayload] extends [never] ? CustomEvent : Extract<TPayload, Event>) & {
|
|
281
|
+
readonly currentTarget: TTarget;
|
|
282
|
+
} : never];
|
|
283
|
+
/** Infers the listener callback shape for a target and event name. Sigma states receive payloads directly, while DOM targets receive typed events. */
|
|
284
|
+
type InferListener<TTarget extends EventTarget, TEvent extends string = string> = InferEventMap<TTarget> extends infer TEvents extends object ? ((...args: InferListenerArgs<TEvents, TTarget, TEvent>) => void) & {
|
|
285
|
+
__eventType?: TEvent;
|
|
286
|
+
} : never;
|
|
287
|
+
/** Infers the event names accepted by `listen(...)` or `useListener(...)` for a target. */
|
|
228
288
|
type InferEventType<TTarget extends EventTarget> = (InferListener<TTarget> extends {
|
|
229
289
|
__eventType?: infer TEvent;
|
|
230
290
|
} ? string & TEvent : never) | (string & {});
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
291
|
+
/**
|
|
292
|
+
* A standalone typed event hub with `emit(...)` and `on(...)` methods and full
|
|
293
|
+
* `EventTarget`, `listen(...)`, and `useListener(...)` compatibility.
|
|
294
|
+
*/
|
|
295
|
+
declare class SigmaTarget<TEvents extends AnyEvents = {}> extends EventTarget {
|
|
296
|
+
readonly [sigmaEventsBrand]: TEvents;
|
|
297
|
+
/**
|
|
298
|
+
* Emits a typed event from the hub.
|
|
299
|
+
*
|
|
300
|
+
* Void events dispatch a plain `Event`. Payload events dispatch a
|
|
301
|
+
* `CustomEvent` whose `detail` holds the payload.
|
|
302
|
+
*/
|
|
303
|
+
emit<TEvent extends string & keyof TEvents>(name: TEvent, ...args: [TEvents[TEvent]] extends [void] ? [] : [payload: TEvents[TEvent]]): void;
|
|
304
|
+
/**
|
|
305
|
+
* Registers a typed event listener and returns an unsubscribe function.
|
|
306
|
+
*
|
|
307
|
+
* Payload events pass their payload directly to the listener. Void events
|
|
308
|
+
* call the listener with no arguments.
|
|
309
|
+
*/
|
|
310
|
+
on<TEvent extends string & keyof TEvents>(name: TEvent, listener: (...args: InferListenerArgs<TEvents, this, TEvent>) => void): () => void;
|
|
311
|
+
}
|
|
312
|
+
/** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
|
|
313
|
+
declare function listen<TTarget extends EventTarget, TEvent extends InferEventType<TTarget>>(target: TTarget, name: TEvent, listener: InferListener<TTarget, TEvent>): () => void;
|
|
237
314
|
//#endregion
|
|
238
315
|
//#region src/hooks.d.ts
|
|
239
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* Creates one sigma-state instance for a component.
|
|
318
|
+
*
|
|
319
|
+
* `create()` runs once per mounted component instance. When the sigma state
|
|
320
|
+
* defines setup, `useSigma(...)` also runs `setup(...setupArgs)` in an effect
|
|
321
|
+
* and cleans it up when the setup arguments change or the component unmounts.
|
|
322
|
+
*/
|
|
240
323
|
declare function useSigma<T extends AnySigmaState>(create: () => T, setupArgs?: InferSetupArgs<T>): T;
|
|
241
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Attaches an event listener in a component and cleans it up automatically.
|
|
326
|
+
*
|
|
327
|
+
* Passing `null` disables the listener. The latest callback is used without
|
|
328
|
+
* forcing the effect to resubscribe on every render.
|
|
329
|
+
*/
|
|
242
330
|
declare function useListener<TTarget extends EventTarget | AnySigmaState, TEvent extends InferEventType<TTarget>>(target: TTarget | null, name: TEvent, listener: InferListener<TTarget, TEvent>): void;
|
|
243
331
|
//#endregion
|
|
244
|
-
export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaRef, type SigmaState, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|
|
332
|
+
export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaRef, type SigmaState, SigmaTarget, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
|