preact-sigma 2.0.0 → 2.1.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 CHANGED
@@ -4,6 +4,19 @@
4
4
 
5
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.
6
6
 
7
+ ## Getting Started
8
+
9
+ To add `preact-sigma` to your project:
10
+
11
+ ```bash
12
+ npm install preact-sigma
13
+ ```
14
+
15
+ If you use AI coding agents, this repo also includes agent-oriented guidance:
16
+
17
+ - [llms.txt](./llms.txt) provides a compact overview of the API and recommended patterns.
18
+ - Companion skills are available via `npx skills add alloc/preact-sigma`.
19
+
7
20
  ## What It Is
8
21
 
9
22
  At its core, `preact-sigma` lets you describe a stateful model as a constructor:
@@ -151,5 +164,6 @@ In Preact, the same constructor can be used with `useSigma(() => new TodoList(),
151
164
  - Use `computed(...)` for argument-free derived state, and use queries for reactive reads that need parameters.
152
165
  - Put writes in actions. A draft boundary is any point where sigma cannot keep reusing the current draft. `emit()`, `await`, and any action call other than a same-instance sync nested action call are draft boundaries, so call `this.commit()` before those boundaries when pending writes should become public.
153
166
  - Use `snapshot(instance)` and `replaceState(instance, snapshot)` for committed-state replay. They work on top-level state keys and stay outside action semantics.
167
+ - Use `ref(value)` when a value should stay by reference in sigma's `Draft` and `Immutable` types. It only changes typing and does not change Immer's runtime drafting or freezing behavior.
154
168
  - Use `immerable` on custom classes only when they should participate in Immer drafting. `setAutoFreeze(false)` disables sigma's runtime deep-freezing when you need published state to stay unfrozen.
155
169
  - Use `setup(...)` for owned side effects, and always return cleanup resources for anything the instance starts.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { ReadonlySignal, action, batch, computed, effect, untracked } from "@preact/signals";
2
2
  import { Patch, freeze, immerable } from "immer";
3
3
 
4
+ //#region src/internal/symbols.d.ts
5
+ declare const sigmaStateBrand: unique symbol;
6
+ declare const sigmaEventsBrand: unique symbol;
7
+ declare const sigmaRefBrand: unique symbol;
8
+ //#endregion
4
9
  //#region src/immer.d.ts
5
10
  type PrimitiveType = number | string | boolean;
6
11
  /** Object types that should never be mapped */
@@ -18,6 +23,7 @@ type IfAvailable<T, Fallback = void> = true | false extends (T extends never ? t
18
23
  * Set
19
24
  */
20
25
  type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>;
26
+ type HasSigmaRefBrand<T> = [T] extends [object] ? typeof sigmaRefBrand extends keyof T ? true : false : false;
21
27
  type WritableDraft<T> = T extends any[] ? number extends T["length"] ? Draft<T[number]>[] : WritableNonArrayDraft<T> : WritableNonArrayDraft<T>;
22
28
  type WritableNonArrayDraft<T> = { -readonly [K in keyof T]: T[K] extends infer V ? (V extends object ? Draft<V> : V) : never };
23
29
  /**
@@ -25,17 +31,13 @@ type WritableNonArrayDraft<T> = { -readonly [K in keyof T]: T[K] extends infer V
25
31
  *
26
32
  * Use this instead of `immer.Draft`
27
33
  */
28
- type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? Map<Draft<K>, Draft<V>> : T extends ReadonlySet<infer V> ? Set<Draft<V>> : T extends WeakReferences ? T : T extends object ? WritableDraft<T> : T;
34
+ type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : HasSigmaRefBrand<T> extends true ? T : T extends ReadonlyMap<infer K, infer V> ? Map<Draft<K>, Draft<V>> : T extends ReadonlySet<infer V> ? Set<Draft<V>> : T extends WeakReferences ? T : T extends object ? WritableDraft<T> : T;
29
35
  /**
30
36
  * Convert a mutable type into a readonly type.
31
37
  *
32
38
  * Use this instead of `immer.Immutable`
33
39
  */
34
- type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } : T;
35
- //#endregion
36
- //#region src/internal/symbols.d.ts
37
- declare const sigmaStateBrand: unique symbol;
38
- declare const sigmaEventsBrand: unique symbol;
40
+ type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : HasSigmaRefBrand<T> extends true ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } : T;
39
41
  //#endregion
40
42
  //#region src/internal/types.d.ts
41
43
  type AnyFunction = (...args: any[]) => any;
@@ -45,6 +47,7 @@ type DefaultStateValue<TValue> = TValue | DefaultStateInitializer<TValue>;
45
47
  type Disposable = {
46
48
  [Symbol.dispose](): void;
47
49
  };
50
+ /** A type brand added by `ref(...)`. */
48
51
  /** The event map shape used by sigma types. */
49
52
  type AnyEvents = Record<string, object | void>;
50
53
  /** The top-level state object shape used by sigma types. */
@@ -143,6 +146,13 @@ declare function replaceState<T extends AnySigmaState>(publicInstance: T, nextSt
143
146
  //#region src/framework.d.ts
144
147
  /** Checks whether a value is a sigma-state instance. */
145
148
  declare function isSigmaState(value: unknown): value is AnySigmaState;
149
+ /**
150
+ * Returns `value` unchanged and marks its type so sigma's Draft and Immutable
151
+ * helpers keep it by reference instead of recursively immerizing it.
152
+ */
153
+ declare function ref<T extends object>(value: T): T & {
154
+ [sigmaRefBrand]?: true;
155
+ };
146
156
  /** Creates a standalone tracked query function with the same signature as `fn`. */
147
157
  declare function query<TArgs extends any[], TResult>(fn: (this: void, ...args: TArgs) => TResult): typeof fn;
148
158
  /**
@@ -226,4 +236,4 @@ declare function useSigma<T extends AnySigmaState>(create: () => T, setupArgs?:
226
236
  /** Attaches an event listener in a component and cleans it up when dependencies change. */
227
237
  declare function useListener<TTarget extends EventTarget | AnySigmaState, TEvent extends InferEventType<TTarget>>(target: TTarget | null, name: TEvent, listener: InferListener<TTarget, TEvent>): void;
228
238
  //#endregion
229
- export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaState, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
239
+ export { type AnyDefaultState, type AnyEvents, type AnyResource, type AnySigmaState, type AnySigmaStateWithEvents, type AnyState, InferEventType, InferListener, type InferSetupArgs, type SigmaObserveChange, type SigmaObserveOptions, type SigmaState, SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, ref, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
package/dist/index.mjs CHANGED
@@ -520,6 +520,13 @@ Object.defineProperty(Sigma.prototype, sigmaStateBrand, { value: true });
520
520
  function isSigmaState(value) {
521
521
  return Boolean(value && typeof value === "object" && value[sigmaStateBrand]);
522
522
  }
523
+ /**
524
+ * Returns `value` unchanged and marks its type so sigma's Draft and Immutable
525
+ * helpers keep it by reference instead of recursively immerizing it.
526
+ */
527
+ function ref(value) {
528
+ return value;
529
+ }
523
530
  /** Creates a standalone tracked query function with the same signature as `fn`. */
524
531
  function query(fn) {
525
532
  return ((...args) => computed$1(() => fn(...args)).value);
@@ -626,4 +633,4 @@ function useListener(target, name, listener) {
626
633
  }, [target, name]);
627
634
  }
628
635
  //#endregion
629
- export { SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
636
+ export { SigmaType, action, batch, computed, effect, freeze, immerable, isSigmaState, listen, query, ref, replaceState, setAutoFreeze, snapshot, untracked, useListener, useSigma };
package/llms.txt CHANGED
@@ -7,6 +7,7 @@
7
7
  - `state property`: A top-level property from `TState`, such as `draft` in `{ draft: string }`.
8
8
  - `computed`: A tracked getter declared with `.computed({ ... })`.
9
9
  - `query`: A tracked method declared with `.queries({ ... })` or created with `query(fn)`.
10
+ - `ref`: A helper that keeps a value by reference in sigma's `Draft` and `Immutable` types without changing runtime behavior.
10
11
  - `action`: A method declared with `.actions({ ... })` that reads and writes through one Immer draft for one synchronous call.
11
12
  - `draft boundary`: Any point where sigma cannot keep reusing the current draft.
12
13
  - `setup handler`: A function declared with `.setup(fn)` that returns an array of cleanup resources.
@@ -17,6 +18,7 @@
17
18
 
18
19
  - For state shape, inference, and instance shape, read `Start Here`, `Inference`, `SigmaType`, and `Public Instance Shape`.
19
20
  - For mutation semantics, read `Critical Rules`, `actions`, `immerable`, and `setAutoFreeze`.
21
+ - For type-level by-reference values, read `ref`.
20
22
  - For side effects and events, read `setup`, `Events`, `listen`, `useListener`, and `useSigma`.
21
23
  - For committed-state utilities, read `observe`, `snapshot`, and `replaceState`.
22
24
 
@@ -60,6 +62,7 @@ import {
60
62
  isSigmaState,
61
63
  listen,
62
64
  query,
65
+ ref,
63
66
  replaceState,
64
67
  setAutoFreeze,
65
68
  snapshot,
@@ -325,6 +328,17 @@ Behavior:
325
328
  - custom class instances without a true `[immerable]` property stay outside that freeze path
326
329
  - plain objects, arrays, `Map`, and `Set` already participate in normal Immer drafting without extra markers
327
330
 
331
+ ## `ref(value)`
332
+
333
+ `ref(value)` returns `value` unchanged and marks its type so sigma's `Draft` and `Immutable` helpers keep that value by reference.
334
+
335
+ Behavior:
336
+
337
+ - it has no runtime effect
338
+ - it only changes sigma's local `Draft` and `Immutable` typing
339
+ - it prevents type-level recursive immerization for that value
340
+ - it does not change whether Immer drafts or freezes the value at runtime
341
+
328
342
  ## `query(fn)`
329
343
 
330
344
  `query(fn)` creates a standalone tracked query helper.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preact-sigma",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",