You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`preact-sigma`is a typed state-model builder for apps that want Preact's fine-grained reactivity, Immer-backed writes, and explicit lifecycle.
3
+
`preact-sigma`lets you define state once and reuse it as a model.
4
4
5
-
You define a reusable state type once, then create instances wherever they make sense: inside components, in shared modules, or in plain TypeScript code. Each instance exposes readonly public state, tracked derived reads, imperative actions, and optional setup and event APIs.
5
+
It is built for Preact and TypeScript, and combines:
6
6
7
-
## Getting Started
7
+
- fine-grained reactive reads
8
+
- Immer-style writes
9
+
- explicit setup and cleanup
10
+
- typed events
11
+
- a constructor you can instantiate anywhere
8
12
9
-
To add `preact-sigma` to your project:
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.
- 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(...)`
44
107
45
-
At its core, `preact-sigma` lets you describe a stateful model as a constructor:
108
+
Use queries for reactive reads that need arguments.
46
109
47
-
- top-level state stays reactive through one signal per state property
48
-
- computed values become tracked getters
49
-
- queries become tracked methods, including queries with arguments
50
-
- actions batch reads and writes through Immer drafts
51
-
- setup handlers own side effects and cleanup
52
-
- typed events let instances notify the outside world without exposing mutable internals
110
+
```ts
111
+
visibleTodos("open");
112
+
```
113
+
114
+
Queries are for reading, not writing.
53
115
54
-
The result feels like a small stateful object from application code, while still behaving like signal-driven state from rendering code.
116
+
### `actions(...)`
55
117
56
-
## What You Can Do With It
118
+
Actions are where state changes happen.
57
119
58
-
`preact-sigma` is useful when you want state logic to live in one reusable unit instead of being split across loose signals, reducers, and effect cleanup code.
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.
59
121
60
-
With it, you can:
122
+
```ts
123
+
.actions({
124
+
rename(title:string) {
125
+
this.title=title;
126
+
},
127
+
})
128
+
```
61
129
62
-
- model domain state as reusable constructors instead of one-off store objects
63
-
- read public state directly while keeping writes inside typed action methods
64
-
- derive reactive values with computed getters and parameterized queries
65
-
- publish state changes from synchronous or async actions
66
-
- observe committed state changes and optional Immer patches
67
-
- snapshot committed top-level state and replace committed state for undo-like flows
68
-
- manage timers, listeners, nested state setup, and teardown through explicit cleanup
69
-
- use the same model inside Preact components with `useSigma(...)` and `useListener(...)`
130
+
### `setup(...)`
70
131
71
-
## Why This Shape Exists
132
+
Setup is where side effects belong.
72
133
73
-
This package exists to keep stateful logic cohesive without giving up signal-level reactivity.
134
+
Use it for things like:
74
135
75
-
It is a good fit when plain signals start to sprawl across modules, but heavier store abstractions feel too opaque or too tied to component structure. `preact-sigma` keeps the "model object" ergonomics of a class-like API, while preserving readonly public reads, explicit write boundaries, and explicit ownership of side effects.
136
+
- timers
137
+
- event listeners
138
+
- subscriptions
139
+
- nested model setup
140
+
- storage sync
76
141
77
-
## Big Picture Example
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.
In Preact, the same constructor can be used with `useSigma(() => new TodoList(), ["todos-demo"])` so the component owns one instance and `setup(...)` cleanup runs automatically. Use `useListener(...)` when you want component-scoped event subscriptions with automatic teardown.
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:
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:
182
310
183
-
Cleanup resources can be returned as functions, `AbortController`, objects with `dispose()`, or objects with `Symbol.dispose`.
311
+
-**too small for a big store abstraction**
312
+
-**too stateful for a handful of loose signals**
184
313
185
-
Inside setup, `this` exposes the public instance plus `emit(...)` and `act(fn)`. Use `this.act(function () { ... })` when setup needs one synchronous anonymous action with normal draft, `commit()`, and `emit(...)` semantics, whether that work happens immediately in the setup body or later from a setup-owned callback, but should not become a public action method.
314
+
It keeps the ergonomics of working with a model object, while preserving:
186
315
187
-
## Constructor and Defaults
316
+
- readonly public reads
317
+
- explicit write boundaries
318
+
- fine-grained reactivity
319
+
- explicit ownership of side effects
188
320
189
-
-`defaultState` values may be plain values or zero-argument initializer functions. Use initializer functions when each instance needs a fresh object, array, or class instance.
190
-
- Constructor input shallowly overrides `defaultState`, so `new TodoList({ draft: "ready" })` replaces only the top-level keys you pass.
321
+
That makes it useful for app state that has real behavior, not just values.
191
322
192
-
## More Docs
323
+
## More docs
193
324
194
-
-[llms.txt](./llms.txt)provides the exhaustive machine-oriented API guide and terminology.
325
+
-[`llms.txt`](./llms.txt)contains the exhaustive API and behavior reference.
195
326
- Companion skills are available via `npx skills add alloc/preact-sigma`.
196
-
- The `preact-sigma` skill packages the procedural guidance and agent-oriented workflow for this library.
327
+
- The `preact-sigma` skill packages procedural guidance and agent-oriented workflow for the library.
0 commit comments