preact-sigma 2.2.2 → 2.2.3

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/llms.txt DELETED
@@ -1,437 +0,0 @@
1
- # `preact-sigma` for LLMs
2
-
3
- ## Glossary
4
-
5
- - `sigma type`: The builder returned by `new SigmaType<...>()`. It is also the constructor for sigma-state instances after configuration.
6
- - `sigma state`: An instance created from a configured sigma type.
7
- - `state property`: A top-level property from `TState`, such as `draft` in `{ draft: string }`.
8
- - `computed`: A tracked getter declared with `.computed({ ... })`.
9
- - `query`: A tracked method declared with `.queries({ ... })` or created with `query(fn)`.
10
- - `action`: A method declared with `.actions({ ... })` that reads and writes through one Immer draft for one synchronous call.
11
- - `anonymous action`: A synchronous function passed to `this.act(...)` inside setup to run one anonymous action.
12
- - `draft boundary`: Any point where sigma cannot keep reusing the current draft.
13
- - `setup handler`: A function declared with `.setup(fn)` that returns an array of cleanup resources.
14
- - `cleanup resource`: A cleanup function, an `AbortController`, an object with `dispose()`, or an object with `[Symbol.dispose]()`.
15
- - `signal access`: Reading the underlying `ReadonlySignal` for a state property or computed through `instance.get(key)`.
16
-
17
- ## Navigation
18
-
19
- - For state shape, inference, and instance shape, read `Start Here`, `Inference`, `SigmaType`, and `Public Instance Shape`.
20
- - For mutation semantics, read `Critical Rules`, `actions`, `immerable`, and `setAutoFreeze`.
21
- - For type-level by-reference values, see `SigmaRef<T>`.
22
- - For side effects and events, read `setup`, `Events`, `listen`, `useListener`, and `useSigma`.
23
- - For committed-state utilities, read `observe`, `snapshot`, and `replaceState`.
24
-
25
- ## Start Here
26
-
27
- - `preact-sigma` is a class-builder state API built on top of `@preact/signals` and `immer`.
28
- - Use `new SigmaType<TState, TEvents>()` to build reusable constructors for sigma states.
29
- - Each top-level state property gets its own signal and readonly public property.
30
- - Use `computed` for argument-free derived state.
31
- - Use `.queries({ ... })` for reactive reads that accept parameters.
32
- - Put writes in `.actions({ ... })`.
33
- - `setup` is explicit and returns cleanup resources.
34
- - `observe` sees committed state changes after successful publishes.
35
- - `snapshot` and `replaceState` operate on committed top-level state outside action semantics.
36
-
37
- ## Critical Rules
38
-
39
- - Prefer explicit type arguments only on `new SigmaType<TState, TEvents>()`. Let builder methods infer from their inputs.
40
- - `emit()` is a draft boundary.
41
- - Any action call is a draft boundary unless it is a same-instance sync nested action call.
42
- - That means cross-instance action calls and all async action calls are draft boundaries.
43
- - `await` inside an async action is also a draft boundary.
44
- - `this.commit()` is only needed when the current action has unpublished draft changes and is about to cross a draft boundary.
45
- - A synchronous action does not need `this.commit()` when it finishes without crossing a draft boundary.
46
- - Successful publishes deep-freeze draftable public state while auto-freezing is enabled.
47
- - Only values that Immer considers draftable participate in that freeze path. Custom class instances without a true `[immerable]` property stay outside it.
48
-
49
- ## Runtime Exports
50
-
51
- Import runtime APIs from `preact-sigma`.
52
-
53
- ```typescript
54
- import {
55
- SigmaType,
56
- action,
57
- batch,
58
- computed,
59
- effect,
60
- freeze,
61
- immerable,
62
- isSigmaState,
63
- listen,
64
- query,
65
- type SigmaRef,
66
- replaceState,
67
- setAutoFreeze,
68
- snapshot,
69
- untracked,
70
- useListener,
71
- useSigma,
72
- } from "preact-sigma";
73
- ```
74
-
75
- ## Type Exports
76
-
77
- - `AnyDefaultState`: Describes the object accepted by `.defaultState(...)`.
78
- - `AnyEvents`: Describes an event map from event names to payload objects or `void`.
79
- - `AnyResource`: Describes a supported setup cleanup resource, including cleanup functions, `AbortController`, objects with `dispose()`, and objects with `Symbol.dispose`.
80
- - `AnySigmaState`: Describes the public shape shared by all sigma-state instances.
81
- - `AnySigmaStateWithEvents`: Describes a sigma-state instance with a typed event map.
82
- - `AnyState`: Describes the top-level state object for a sigma type.
83
- - `InferEventType`: Infers the supported event names for a target used with `listen(...)` or `useListener(...)`.
84
- - `InferListener`: Infers the listener signature for a target and event name.
85
- - `InferSetupArgs`: Infers the `setup(...)` argument list for a sigma-state instance.
86
- - `SigmaObserveChange`: Describes the object received by `.observe(...)` listeners.
87
- - `SigmaObserveOptions`: Describes the options object accepted by `.observe(...)`.
88
- - `SigmaState`: Describes the public instance shape produced by a configured sigma type.
89
-
90
- ## Builder Example
91
-
92
- ```typescript
93
- import { SigmaType } from "preact-sigma";
94
-
95
- type Todo = {
96
- id: string;
97
- title: string;
98
- completed: boolean;
99
- };
100
-
101
- type TodoListState = {
102
- draft: string;
103
- todos: Todo[];
104
- };
105
-
106
- type TodoListEvents = {
107
- added: Todo;
108
- };
109
-
110
- const TodoList = new SigmaType<TodoListState, TodoListEvents>()
111
- .defaultState({
112
- draft: "",
113
- todos: [],
114
- })
115
- .computed({
116
- completedCount() {
117
- return this.todos.filter((todo) => todo.completed).length;
118
- },
119
- })
120
- .queries({
121
- canAddTodo() {
122
- return this.draft.trim().length > 0;
123
- },
124
- })
125
- .actions({
126
- setDraft(draft: string) {
127
- this.draft = draft;
128
- },
129
- addTodo() {
130
- const todo = {
131
- id: crypto.randomUUID(),
132
- title: this.draft,
133
- completed: false,
134
- };
135
- this.todos.push(todo);
136
- this.draft = "";
137
- this.commit();
138
- this.emit("added", todo);
139
- },
140
- });
141
-
142
- const todoList = new TodoList();
143
- ```
144
-
145
- ## Inference
146
-
147
- - `TState` and `TEvents` come from `new SigmaType<TState, TEvents>()`.
148
- - `defaultState` comes from `.defaultState(...)`, where each property may be either a value or a zero-argument initializer that returns the value.
149
- - Public computed names and return types come from `.computed(...)`.
150
- - Public query names, parameter types, and return types come from `.queries(...)`.
151
- - `observe(change)` types come from `.observe(...)`, and `patches` and `inversePatches` are present when that call uses `{ patches: true }`.
152
- - Public action names, parameter types, and return types come from `.actions(...)`.
153
- - `setup(...args)` argument types come from the first `.setup(...)` call and later setup calls reuse that same argument list.
154
- - `get(key)` signal types come from `TState` and from the return types declared by `.computed(...)`.
155
- - Do not pass explicit type arguments to the builder methods. Let inference come from each method input.
156
-
157
- ## `SigmaType`
158
-
159
- `new SigmaType<TState, TEvents>()` creates a mutable, reusable sigma-state builder that is also the constructor for sigma-state instances after configuration.
160
-
161
- Behavior:
162
-
163
- - `.defaultState(...)`, `.setup(...)`, `.computed(...)`, `.queries(...)`, `.observe(...)`, and `.actions(...)` all mutate the same builder and return it.
164
- - Those builder methods are additive and may be called in any order.
165
- - Builder method typing only exposes state helpers that existed when that builder call happened.
166
- - Runtime contexts use the full accumulated builder, including definitions added by later builder calls.
167
- - `defaultState` is optional and must be a plain object when provided.
168
- - Function-valued `defaultState` properties act as per-instance initializers, and their return values become the state values for that instance.
169
- - Constructor input must be a plain object when provided.
170
- - Constructor input shallowly overrides `defaultState`.
171
- - If every required state property is covered by `defaultState`, constructor input is optional.
172
- - Duplicate names across state properties, computeds, queries, and actions are rejected at runtime.
173
- - Reserved public names are `act`, `get`, `setup`, `on`, and `emit`.
174
-
175
- ## Public Instance Shape
176
-
177
- A sigma-state instance exposes:
178
-
179
- - one readonly enumerable own property for every state property
180
- - one tracked non-enumerable getter for every computed
181
- - one method for every query
182
- - one method for every action
183
- - `get(key): ReadonlySignal<...>` for state-property and computed keys
184
- - `setup(...args): () => void` when the builder has at least one setup handler
185
- - `on(name, listener): () => void`
186
- - `Object.keys(instance)` includes only top-level state properties
187
-
188
- ## Reactivity Model
189
-
190
- - each top-level state property is backed by its own Preact signal
191
- - public state reads are reactive
192
- - signal access is reactive, so reading `.value` tracks like any other Preact signal read
193
- - computed getters are reactive and lazily memoized
194
- - queries are reactive at the call site, including queries with arguments
195
- - query calls are not memoized across invocations; each call uses a fresh `computed(...)` wrapper and does not retain that signal
196
-
197
- ## `get(key)`
198
-
199
- `instance.get(key)` returns the underlying `ReadonlySignal` for one top-level state property or computed.
200
-
201
- Behavior:
202
-
203
- - state-property keys return that property's signal
204
- - computed keys return that computed getter's signal
205
-
206
- ## `computed`
207
-
208
- Computeds are added with `.computed({ ... })`.
209
-
210
- Behavior:
211
-
212
- - each computed is exposed as a tracked getter property
213
- - computed getters are non-enumerable on the public instance
214
- - `this` inside a computed exposes readonly state plus other computeds
215
- - computeds do not receive query or action methods on `this`
216
- - computeds cannot accept arguments
217
-
218
- ## `queries`
219
-
220
- Queries are added with `.queries({ ... })`.
221
-
222
- Behavior:
223
-
224
- - queries may accept arbitrary parameters
225
- - `this` inside a query exposes readonly state, computeds, and other queries
226
- - queries do not receive action methods on `this`
227
- - when a query runs inside an action, it reads from the current draft-aware state
228
- - query results are reactive at the call site but are not memoized across calls
229
- - prefer `.queries({ ... })` for commonly needed instance methods
230
- - not every calculation belongs in `.queries({ ... })`; keeping a calculation local to the module that uses it is often clearer until it becomes a common use case
231
- - query wrappers are shared across instances
232
- - query typing only exposes computeds and queries that were already present when its `.queries(...)` call happened
233
-
234
- ## `actions`
235
-
236
- Actions are added with `.actions({ ... })`.
237
-
238
- Behavior:
239
-
240
- - actions create drafts lazily when reads or writes need draft-backed mutation semantics
241
- - actions may call other actions, queries, and computeds
242
- - same-instance sync nested action calls reuse the current draft
243
- - any other action call starts a different invocation and is a draft boundary
244
- - `emit()` is a draft boundary
245
- - `await` inside an async action is a draft boundary
246
- - `this.commit()` publishes the current draft immediately
247
- - `this.commit()` is only needed when the current action has unpublished draft changes and is about to cross a draft boundary
248
- - a synchronous action does not need `this.commit()` when it finishes without crossing a draft boundary
249
- - declared async actions publish their initial synchronous draft on return
250
- - after an async action resumes from `await`, top-level reads of draftable state and state writes may open a hidden draft for that async invocation
251
- - non-async actions must stay synchronous; if one returns a promise, sigma throws
252
- - if an async action reaches `await` or `return` with unpublished changes, the action promise rejects when it settles
253
- - if an action crosses a boundary while it owns unpublished changes, sigma throws until `this.commit()` publishes them
254
- - if a different invocation crosses a boundary while unpublished changes still exist, sigma warns and discards them before continuing
255
- - successful publishes deep-freeze draftable public state and write it back to per-property signals while auto-freezing is enabled
256
- - custom classes participate in Immer drafting only when the class opts into drafting with `[immerable] = true`
257
- - actions can emit typed events with `this.emit(...)`
258
- - action wrappers are shared across instances
259
- - action typing only exposes computeds, queries, and actions that were already present when its `.actions(...)` call happened
260
-
261
- Nested sigma states stored in state stay usable as values. Actions do not proxy direct mutation into a nested sigma state's internals.
262
-
263
- ## `observe`
264
-
265
- Observers are added with `.observe(listener, options?)`.
266
-
267
- Behavior:
268
-
269
- - each observer runs after a successful action commit that changes base state
270
- - observers do not run for actions that leave base state unchanged
271
- - `change.newState` is the committed base-state snapshot for that action
272
- - `change.oldState` is the base-state snapshot from before that action started
273
- - `this` inside an observer exposes readonly state, computeds, and queries
274
- - observers do not receive action methods or `emit(...)` on `this`
275
- - same-instance sync nested action calls produce one observer notification after the outer action commits
276
- - patch generation is opt-in with `{ patches: true }`
277
- - when patch generation is enabled, `change.patches` and `change.inversePatches` come from Immer
278
- - applications are responsible for calling `enablePatches()` before using observer patch generation
279
- - observer typing only exposes computeds and queries that were already present when that `.observe(...)` call happened
280
-
281
- ## `setup`
282
-
283
- Setup is added with `.setup(fn)`.
284
-
285
- Behavior:
286
-
287
- - setup is explicit; a new instance does not run setup automatically
288
- - each `.setup(...)` call adds another setup handler
289
- - `useSigma(...)` calls `.setup(...)` for component-owned instances that define setup
290
- - calling `.setup(...)` again cleans up the previous setup first
291
- - one `.setup(...)` call runs every registered setup handler in definition order
292
- - the public `.setup(...)` method always returns one cleanup function
293
- - `this` inside a setup handler exposes the public instance plus `emit(...)` and `act(fn)`
294
- - `this.act(fn)` inside setup runs `fn` with normal action semantics without adding a public action method
295
- - use `this.act(fn)` for setup-time initialization work or setup-owned callbacks that need action semantics
296
- - pass a normal `function () {}` to `this.act(...)` so callback `this` receives the action context
297
- - `this.act(fn)` functions must stay synchronous
298
- - each setup handler returns an array of cleanup resources
299
- - setup typing only exposes computeds, queries, and actions that were already present when that `.setup(...)` call happened
300
-
301
- Supported cleanup resources:
302
-
303
- - cleanup functions
304
- - objects with `dispose()`
305
- - objects with `[Symbol.dispose]()`
306
- - `AbortController`
307
-
308
- When a parent setup wants to own a nested sigma state's setup, call the child sigma state's `setup(...)` method and return that cleanup function.
309
-
310
- Cleanup runs in reverse order. If multiple cleanup steps throw, cleanup rethrows an `AggregateError`.
311
-
312
- ## Events
313
-
314
- Events are emitted from actions or setup through `this.emit(name, payload?)`.
315
-
316
- Behavior:
317
-
318
- - the event map controls allowed event names and payload types
319
- - `void` events emit no payload
320
- - object events emit one payload object
321
- - `.on(name, listener)` returns an unsubscribe function
322
- - listeners receive the payload directly, or no argument for `void` events
323
-
324
- ## `immerable`
325
-
326
- `immerable` is re-exported from Immer so custom classes can opt into drafting with `[immerable] = true`.
327
-
328
- Behavior:
329
-
330
- - unmarked custom class instances stay outside Immer drafting
331
- - marking a class with `[immerable] = true` makes that class participate in Immer drafting
332
- - sigma only freezes published values that Immer considers draftable
333
- - custom class instances without a true `[immerable]` property stay outside that freeze path
334
- - plain objects, arrays, `Map`, and `Set` already participate in normal Immer drafting without extra markers
335
-
336
- ## `SigmaRef<T>`
337
-
338
- `SigmaRef<T>` marks a value's type so sigma's `Draft` and `Immutable` helpers keep that value by reference.
339
-
340
- Behavior:
341
-
342
- - assigning to a `SigmaRef<T>`-typed value has no runtime effect
343
- - it only changes sigma's local `Draft` and `Immutable` typing
344
- - it prevents type-level recursive immerization for that value
345
- - it does not change whether Immer drafts or freezes the value at runtime
346
-
347
- ## `query(fn)`
348
-
349
- `query(fn)` creates a standalone tracked query helper.
350
-
351
- Behavior:
352
-
353
- - it returns a function with the same parameter and return types as `fn`
354
- - it evaluates `fn` inside a signal `computed(...)`
355
- - its calls are reactive but not memoized across invocations
356
- - it does not use an instance receiver
357
- - prefer `.queries({ ... })` for commonly needed instance methods
358
- - use `query(fn)` when a tracked helper is large, rarely needed, or better kept local to a consumer module for tree-shaking
359
- - query helpers do not need to live on the sigma state or in the same module as it
360
-
361
- ## `setAutoFreeze`
362
-
363
- `setAutoFreeze(autoFreeze)` controls whether sigma deep-freezes published public state at runtime.
364
-
365
- Behavior:
366
-
367
- - auto-freezing starts enabled
368
- - `setAutoFreeze(false)` leaves later published draftable public state unfrozen
369
- - `setAutoFreeze(true)` restores deep freezing for later published draftable state
370
- - the setting is shared across sigma state instances
371
-
372
- ## `snapshot`
373
-
374
- `snapshot(instance)` returns a shallow snapshot of an instance's committed public state.
375
-
376
- Behavior:
377
-
378
- - the snapshot includes one own property for each top-level state key
379
- - each value comes from the current committed public state
380
- - the snapshot does not include computeds, queries, actions, events, or setup helpers
381
- - nested sigma states remain as referenced values; `snapshot(...)` does not recurse into their internal state
382
- - the return type is inferred from the instance's sigma-state definition
383
-
384
- ## `replaceState`
385
-
386
- `replaceState(instance, snapshot)` replaces an instance's committed public state from a snapshot object.
387
-
388
- Behavior:
389
-
390
- - the replacement snapshot must be a plain object with exactly the instance's top-level state keys
391
- - it updates the committed public state without going through an action method
392
- - it notifies observers when the committed state changes
393
- - when observer patch generation is enabled, `replaceState(...)` also delivers patches and inverse patches
394
- - it throws if an action still owns unpublished changes
395
- - the snapshot parameter type is inferred from the instance's sigma-state definition
396
-
397
- ## Passthrough Exports
398
-
399
- - `action`, `batch`, `computed`, `effect`, and `untracked` are re-exported from `@preact/signals`.
400
- - `freeze` is re-exported from `immer`. A frozen object cannot be mutated through Immer drafts, including inside sigma actions.
401
-
402
- ## `useSigma`
403
-
404
- `useSigma(create, setupParams?)` creates one sigma-state instance for a component and manages setup cleanup.
405
-
406
- Behavior:
407
-
408
- - calls `create()` once per mounted component instance
409
- - returns the same sigma-state instance for the component lifetime
410
- - if the sigma state defines setup, calls `sigmaState.setup(...setupParams)` in an effect
411
- - reruns setup when `setupParams` change
412
- - the cleanup returned by `setup(...)` runs when `setupParams` change or when the component unmounts
413
-
414
- ## `listen`
415
-
416
- `listen(target, name, listener)` adds an event listener and returns a cleanup function.
417
-
418
- Behavior:
419
-
420
- - it subscribes with `addEventListener(...)` and returns a cleanup function that removes that listener
421
- - for sigma-state targets, the listener receives the typed payload directly
422
- - for DOM targets, the listener receives the typed DOM event object
423
-
424
- ## `useListener`
425
-
426
- `useListener(target, name, listener)` attaches an event listener inside a component.
427
-
428
- Behavior:
429
-
430
- - subscribes in `useEffect`
431
- - unsubscribes automatically when `target` or `name` changes or when the component unmounts
432
- - keeps the latest listener callback without requiring it in the effect dependency list
433
- - passing `null` disables the listener
434
-
435
- ## `isSigmaState`
436
-
437
- `isSigmaState(value)` checks whether a value is a sigma-state instance.