@zeix/cause-effect 1.0.0 → 1.0.1

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.
Files changed (47) hide show
  1. package/.github/copilot-instructions.md +2 -1
  2. package/.zed/settings.json +24 -1
  3. package/ARCHITECTURE.md +1 -1
  4. package/CHANGELOG.md +15 -0
  5. package/README.md +41 -0
  6. package/REQUIREMENTS.md +3 -3
  7. package/eslint.config.js +2 -1
  8. package/package.json +5 -4
  9. package/skills/cause-effect/SKILL.md +69 -0
  10. package/skills/cause-effect/agents/openai.yaml +4 -0
  11. package/skills/cause-effect/references/api-facts.md +179 -0
  12. package/skills/cause-effect/references/error-classes.md +153 -0
  13. package/skills/cause-effect/references/non-obvious-behaviors.md +173 -0
  14. package/skills/cause-effect/references/signal-types.md +288 -0
  15. package/skills/cause-effect/workflows/answer-question.md +54 -0
  16. package/skills/cause-effect/workflows/debug.md +71 -0
  17. package/skills/cause-effect/workflows/use-api.md +63 -0
  18. package/skills/cause-effect-dev/SKILL.md +61 -100
  19. package/skills/cause-effect-dev/references/api-facts.md +96 -0
  20. package/skills/cause-effect-dev/references/error-classes.md +97 -0
  21. package/skills/cause-effect-dev/references/internal-types.md +54 -0
  22. package/skills/cause-effect-dev/references/non-obvious-behaviors.md +146 -0
  23. package/skills/cause-effect-dev/references/source-map.md +45 -0
  24. package/skills/cause-effect-dev/workflows/answer-question.md +55 -0
  25. package/skills/cause-effect-dev/workflows/fix-bug.md +63 -0
  26. package/skills/cause-effect-dev/workflows/implement-feature.md +46 -0
  27. package/skills/cause-effect-dev/workflows/write-tests.md +64 -0
  28. package/skills/changelog-keeper/SKILL.md +47 -37
  29. package/skills/tech-writer/SKILL.md +94 -0
  30. package/skills/tech-writer/references/document-map.md +199 -0
  31. package/skills/tech-writer/references/tone-guide.md +189 -0
  32. package/skills/tech-writer/workflows/consistency-review.md +98 -0
  33. package/skills/tech-writer/workflows/update-after-change.md +65 -0
  34. package/skills/tech-writer/workflows/update-agent-docs.md +77 -0
  35. package/skills/tech-writer/workflows/update-architecture.md +61 -0
  36. package/skills/tech-writer/workflows/update-jsdoc.md +72 -0
  37. package/skills/tech-writer/workflows/update-public-api.md +59 -0
  38. package/skills/tech-writer/workflows/update-requirements.md +80 -0
  39. package/src/graph.ts +2 -0
  40. package/src/nodes/collection.ts +38 -0
  41. package/src/nodes/effect.ts +13 -1
  42. package/src/nodes/list.ts +23 -2
  43. package/src/nodes/memo.ts +0 -1
  44. package/src/nodes/sensor.ts +10 -4
  45. package/src/nodes/store.ts +11 -0
  46. package/src/signal.ts +6 -0
  47. package/tsconfig.json +9 -0
@@ -10,7 +10,7 @@ Cause & Effect is a reactive state management library for JavaScript/TypeScript
10
10
  - **Nodes**: StateNode (source + equality), MemoNode (source + sink), TaskNode (source + sink + async), EffectNode (sink + owner)
11
11
  - **Edges**: Doubly-linked list connecting sources to sinks
12
12
  - **Operations**: `link()` creates edges, `propagate()` flags sinks dirty, `flush()` runs queued effects, `batch()` defers flushing
13
- - **Flags**: FLAG_CLEAN, FLAG_CHECK, FLAG_DIRTY, FLAG_RUNNING for efficient dirty checking
13
+ - **Flags**: FLAG_CLEAN, FLAG_CHECK, FLAG_DIRTY, FLAG_RUNNING, FLAG_RELINK for efficient dirty checking
14
14
 
15
15
  ### Signal Types (all in `src/nodes/`)
16
16
  - **State** (`createState`): Mutable signals for values (`get`, `set`, `update`)
@@ -36,6 +36,7 @@ Cause & Effect is a reactive state management library for JavaScript/TypeScript
36
36
  - `src/nodes/list.ts` - createList, isList, List type
37
37
  - `src/nodes/collection.ts` - createCollection, isCollection, Collection type, deriveCollection (internal)
38
38
  - `src/nodes/slot.ts` - createSlot, isSlot, Slot type
39
+ - `src/signal.ts` - Polymorphic factories (createSignal, createMutableSignal, createComputed) and type predicates (isSignal, isMutableSignal, isComputed)
39
40
  - `src/util.ts` - Utility functions and type checks
40
41
  - `index.ts` - Entry point / main export file
41
42
 
@@ -1,3 +1,26 @@
1
1
  {
2
- "project_name": "Cause & Effect"
2
+ "project_name": "Cause & Effect",
3
+ "languages": {
4
+ "TypeScript": {
5
+ "language_servers": ["!eslint", "..."],
6
+ "code_actions_on_format": {
7
+ "source.fixAll.biome": true,
8
+ "source.organizeImports.biome": true,
9
+ },
10
+ },
11
+ "JavaScript": {
12
+ "language_servers": ["!eslint", "..."],
13
+ "code_actions_on_format": {
14
+ "source.fixAll.biome": true,
15
+ "source.organizeImports.biome": true,
16
+ },
17
+ },
18
+ "TSX": {
19
+ "language_servers": ["!eslint", "..."],
20
+ "code_actions_on_format": {
21
+ "source.fixAll.biome": true,
22
+ "source.organizeImports.biome": true,
23
+ },
24
+ },
25
+ },
3
26
  }
package/ARCHITECTURE.md CHANGED
@@ -178,7 +178,7 @@ Creates an ownership scope without an effect. The scope becomes `activeOwner` du
178
178
 
179
179
  ### State (`src/nodes/state.ts`)
180
180
 
181
- **Graph node**: `MemoNode<T>` (source + sink, single delegated dependency)
181
+ **Graph node**: `StateNode<T>` (source only)
182
182
 
183
183
  A mutable value container. The simplest signal type — `get()` links and returns the value, `set()` validates, calls `setState()`, which propagates changes to dependents.
184
184
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Added
6
+
7
+ - **`cause-effect` skill for consumer projects**: New Claude Code skill with self-contained API knowledge in `references/` — no library source access required. Covers three workflows: `use-api`, `debug`, and `answer-question`.
8
+ - **`README.md` Utilities section**: Documents the previously undocumented `createSignal`, `createMutableSignal`, `createComputed` factories and `isSignal`, `isMutableSignal`, `isComputed` predicates exported from `index.ts`.
9
+
10
+ ### Changed
11
+
12
+ - **`cause-effect-dev` skill restructured**: Refactored to progressive disclosure pattern with separate `workflows/` and `references/` modules. Scoped explicitly to library development; external references to `REQUIREMENTS.md`, `ARCHITECTURE.md`, and `src/` are now clearly library-repo-only.
13
+ - **Documentation alignment**: Corrected wrong graph node type for `State` in `ARCHITECTURE.md`; added missing `FLAG_RELINK` and `src/signal.ts` to `copilot-instructions.md`; updated `REQUIREMENTS.md` stability section to reflect 1.0 release; completed and corrected JSDoc across `Sensor`, `Memo`, `Store`, `List`, `Collection`, and utility types. No runtime behaviour changed.
14
+ - **TypeScript 6 compatibility**: Added `erasableSyntaxOnly` to `tsconfig.json` (requires TS ≥5.8); replaced `@types/bun` with `bun-types` directly and added `"types": ["bun-types"]` to `tsconfig.json` to fix module resolution under TypeScript 6.
15
+ - **Package management cleanup**: Added `typescript` to `devDependencies` (was only in `peerDependencies`, causing stale version installs); updated `peerDependencies` range to `>=5.8.0`; removed `package-lock.json` and gitignored npm/yarn/pnpm lockfiles — Bun is required for development.
16
+ - **Zed editor configuration**: Disabled ESLint language server for JS/TS/TSX in `.zed/settings.json` — project uses Biome for linting.
17
+
3
18
  ## 1.0.0
4
19
 
5
20
  ### Changed
package/README.md CHANGED
@@ -399,6 +399,47 @@ createEffect(() => {
399
399
  })
400
400
  ```
401
401
 
402
+ ### Utilities
403
+
404
+ Polymorphic factories and type predicates for generic and library-author code.
405
+
406
+ **`createSignal(value)`** converts any value to its corresponding signal type:
407
+
408
+ ```ts
409
+ import { createSignal } from '@zeix/cause-effect'
410
+
411
+ createSignal(0) // → State<number>
412
+ createSignal([1, 2, 3]) // → List<number>
413
+ createSignal({ x: 0 }) // → Store<{ x: number }>
414
+ createSignal(() => x.get() * 2) // → Memo<number>
415
+ createSignal(async (_, s) =>
416
+ fetch('/api', { signal: s }).then(r => r.json())) // → Task<Response>
417
+ ```
418
+
419
+ If the value is already a signal, it is returned unchanged.
420
+
421
+ **`createMutableSignal(value)`** is the same, but restricted to mutable signals — returns `State`, `Store`, or `List`. Throws `InvalidSignalValueError` if passed a function or a read-only signal.
422
+
423
+ **`createComputed(callback, options?)`** creates a `Memo` or `Task` by detecting whether the callback is async:
424
+
425
+ ```ts
426
+ import { createComputed } from '@zeix/cause-effect'
427
+
428
+ const doubled = createComputed(() => count.get() * 2)
429
+ const data = createComputed(async (_, signal) =>
430
+ fetch(url.get(), { signal }).then(r => r.json()))
431
+ ```
432
+
433
+ **Type predicates**
434
+
435
+ | Predicate | True for |
436
+ |---|---|
437
+ | `isSignal(value)` | Any signal (all 9 types) |
438
+ | `isMutableSignal(value)` | `State`, `Store`, `List` — signals with `.set()` and `.update()` |
439
+ | `isComputed(value)` | `Memo`, `Task` — derived signals |
440
+
441
+ The `MutableSignal<T>` type is the corresponding TypeScript type for `isMutableSignal` — use it as a parameter type in generic code that accepts any writable signal.
442
+
402
443
  ## Choosing the Right Signal
403
444
 
404
445
  ```
package/REQUIREMENTS.md CHANGED
@@ -84,11 +84,11 @@ The following are explicitly out of scope and will not be added to the library:
84
84
 
85
85
  ## Stability
86
86
 
87
- Version 0.18 is the last pre-release before 1.0. The API surface — how signals are created and consumed — is considered stable. From 1.0 onward:
87
+ The library is stable at 1.0.0. The API surface — how signals are created and consumed — will not change except under the following conditions:
88
88
 
89
- - **Breaking changes** are expected only if major new features of the Web Platform shift the optimal way to achieve the goals this library already does.
89
+ - **Breaking changes** only if major new features of the Web Platform shift the optimal way to achieve the goals this library already covers.
90
90
  - **New features** are not expected. The signal type set is complete.
91
- - **Backward compatibility** becomes a concern at 1.0. Prior to that, all known consumers (Le Truc and one other library) are maintained by Zeix AG and can adapt to changes.
91
+ - **Backward compatibility** is maintained from 1.0 onward.
92
92
 
93
93
  ## Success Criteria
94
94
 
package/eslint.config.js CHANGED
@@ -4,8 +4,9 @@ import tseslint from 'typescript-eslint'
4
4
 
5
5
  /** @type {import('eslint').Linter.Config[]} */
6
6
  export default [
7
+ // Global ignores to prevent warnings about these files
7
8
  {
8
- ignores: ['index.js', '**/*.min.js'],
9
+ ignores: ['index.js', 'index.dev.js', 'types/**/*.d.ts', '**/*.min.js'],
9
10
  },
10
11
  {
11
12
  files: ['**/*.{js,mjs,cjs,ts}'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeix/cause-effect",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "author": "Esther Brunner",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -8,12 +8,13 @@
8
8
  "types": "types/index.d.ts",
9
9
  "devDependencies": {
10
10
  "@biomejs/biome": "2.4.6",
11
- "@types/bun": "latest",
11
+ "bun-types": "latest",
12
12
  "mitata": "^1.0.34",
13
- "random": "^5.4.1"
13
+ "random": "^5.4.1",
14
+ "typescript": "latest"
14
15
  },
15
16
  "peerDependencies": {
16
- "typescript": "^5.9.3"
17
+ "typescript": ">=5.8.0"
17
18
  },
18
19
  "description": "Cause & Effect - reactive state management primitives library for TypeScript.",
19
20
  "license": "MIT",
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: cause-effect
3
+ description: >
4
+ Expert guidance for using the @zeix/cause-effect reactive signals library in any project.
5
+ Use when implementing reactive patterns, debugging unexpected behavior, or answering
6
+ questions about the public API, signal types, or design decisions. Works in any project
7
+ that depends on @zeix/cause-effect — all knowledge is embedded, no library source required.
8
+ ---
9
+
10
+ <scope>
11
+ This skill is for **consumer projects** that use `@zeix/cause-effect` as a dependency.
12
+ All domain knowledge is embedded in `references/` — no library source files are required.
13
+
14
+ For development work on the library itself, use the `cause-effect-dev` skill instead.
15
+ </scope>
16
+
17
+ <essential_principles>
18
+ **`T extends {}`** — all signal generics exclude `null` and `undefined`. Use wrapper types or sentinel values to represent absence.
19
+
20
+ **`createEffect` must be inside an owner.** Always wrap effect creation in `createScope` or nest it inside another effect.
21
+
22
+ **Never guess API shapes or behaviors.** The answers are in `references/`. If the embedded knowledge is insufficient, check the library's README or GUIDE — do not invent behavior.
23
+ </essential_principles>
24
+
25
+ <intake>
26
+ What kind of task is this?
27
+
28
+ 1. **Use** — implement reactive patterns using the library's public API
29
+ 2. **Debug** — investigate unexpected or broken reactive behavior
30
+ 3. **Question** — understand the API, which signal to use, or a design decision
31
+
32
+ **Wait for response before proceeding.**
33
+ </intake>
34
+
35
+ <routing>
36
+ | Response | Workflow |
37
+ |---|---|
38
+ | 1, "use", "implement", "add", "build", "write" | workflows/use-api.md |
39
+ | 2, "debug", "fix", "broken", "not working", "wrong", "unexpected" | workflows/debug.md |
40
+ | 3, "question", "explain", "how", "why", "what", "which", "when" | workflows/answer-question.md |
41
+
42
+ **Intent-based routing** (if user provides clear context without selecting):
43
+ - Describes code to write or a feature to add → workflows/use-api.md
44
+ - Describes something not working as expected → workflows/debug.md
45
+ - Asks how something works or which signal to use → workflows/answer-question.md
46
+
47
+ **After identifying the workflow, read it and follow it exactly.**
48
+ </routing>
49
+
50
+ <reference_index>
51
+ All in `references/` — all knowledge is self-contained, no external files required:
52
+
53
+ | File | Contents |
54
+ |---|---|
55
+ | signal-types.md | What each signal is for, when to use each, decision guide |
56
+ | api-facts.md | Key API constraints, core functions, options, callback patterns |
57
+ | non-obvious-behaviors.md | Counterintuitive behaviors with correct vs incorrect examples |
58
+ | error-classes.md | Error classes, trigger conditions, and how to handle them |
59
+ </reference_index>
60
+
61
+ <workflows_index>
62
+ All in `workflows/`:
63
+
64
+ | Workflow | Purpose |
65
+ |---|---|
66
+ | use-api.md | Implement reactive patterns using the public API |
67
+ | debug.md | Diagnose and fix unexpected reactive behavior |
68
+ | answer-question.md | Answer questions about the API, signals, or design |
69
+ </workflows_index>
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Cause & Effect"
3
+ short_description: "Expert guidance for using the @zeix/cause-effect reactive signals library in your project"
4
+ default_prompt: "Use $cause-effect to implement reactive patterns, debug unexpected behavior, or answer questions about the @zeix/cause-effect public API."
@@ -0,0 +1,179 @@
1
+ <overview>
2
+ Key API constraints, defaults, and callback patterns for @zeix/cause-effect. All knowledge is
3
+ self-contained — no library source files required. Read this when writing or reviewing any
4
+ code that uses the public API.
5
+ </overview>
6
+
7
+ <type_constraint>
8
+ **`T extends {}`** — all signal generics exclude `null` and `undefined` at the type level.
9
+ This is intentional: signals always have a value; absence must be modelled explicitly.
10
+
11
+ ```typescript
12
+ // Wrong — TypeScript will reject this
13
+ const count = createState<number | null>(null)
14
+
15
+ // Correct — use a sentinel or a wrapper type
16
+ const count = createState<number>(0)
17
+ const selected = createState<{ id: string } | { id: never }>({ id: '' })
18
+ ```
19
+ </type_constraint>
20
+
21
+ <core_functions>
22
+ **`createScope(fn)`**
23
+ - Returns a single `Cleanup` function
24
+ - `fn` receives no arguments and may return an optional cleanup
25
+ - Use to group effects and control their shared lifetime
26
+
27
+ ```typescript
28
+ const dispose = createScope(() => {
29
+ createEffect(() => console.log(count.get()))
30
+ // all effects inside are disposed when dispose() is called
31
+ })
32
+ dispose() // cleans up everything inside
33
+ ```
34
+
35
+ **`createEffect(fn)`**
36
+ - Returns a `Cleanup` function
37
+ - **Must be called inside an owner** (a `createScope` callback or another `createEffect` callback)
38
+ - Throws `RequiredOwnerError` if called without an active owner
39
+ - Runs `fn` immediately, then re-runs whenever tracked dependencies change
40
+
41
+ **`batch(fn)`**
42
+ - Defers the reactive flush until `fn` returns
43
+ - Multiple state writes inside `fn` coalesce into a single propagation pass
44
+ - Use when updating several signals that feed the same downstream computation
45
+
46
+ ```typescript
47
+ batch(() => {
48
+ x.set(1)
49
+ y.set(2)
50
+ z.set(3)
51
+ // only one propagation pass runs after all three writes
52
+ })
53
+ ```
54
+
55
+ **`untrack(fn)`**
56
+ - Runs `fn` without recording dependency edges
57
+ - Reads inside `fn` do not subscribe the current computation to those signals
58
+ - Use to read a signal's current value without creating a reactive dependency
59
+
60
+ ```typescript
61
+ createEffect(() => {
62
+ const a = reactive.get() // tracked — effect re-runs when reactive changes
63
+ const b = untrack(() => other.get()) // untracked — no dependency on other
64
+ render(a, b)
65
+ })
66
+ ```
67
+
68
+ **`unown(fn)`**
69
+ - Runs `fn` without registering cleanups in the current owner
70
+ - Use in `connectedCallback` and similar DOM lifecycle methods where the DOM —
71
+ not the reactive graph — manages the element's lifetime
72
+
73
+ ```typescript
74
+ connectedCallback() {
75
+ // cleanup is tied to disconnectedCallback, not to a reactive owner
76
+ this.#cleanup = unown(() => createEffect(() => this.render()))
77
+ }
78
+ disconnectedCallback() {
79
+ this.#cleanup?.()
80
+ }
81
+ ```
82
+ </core_functions>
83
+
84
+ <options>
85
+ **`equals`**
86
+ - Available on `createState`, `createSensor`, `createMemo`, `createTask`
87
+ - Default: strict equality (`===`)
88
+ - When a new value is considered equal to the previous one, propagation stops —
89
+ downstream nodes are not re-run
90
+ - **`SKIP_EQUALITY`** — special sentinel value for `equals`; forces propagation on every
91
+ update regardless of value. Use with mutable-reference sensors where the object
92
+ reference never changes but the contents do:
93
+
94
+ ```typescript
95
+ import { createSensor, SKIP_EQUALITY } from '@zeix/cause-effect'
96
+
97
+ const mouse = createSensor<{ x: number; y: number }>(
98
+ set => {
99
+ const handler = (e: MouseEvent) => set({ x: e.clientX, y: e.clientY })
100
+ window.addEventListener('mousemove', handler)
101
+ return () => window.removeEventListener('mousemove', handler)
102
+ },
103
+ { equals: SKIP_EQUALITY } // new object every time, so skip reference equality
104
+ )
105
+ ```
106
+
107
+ **`guard`**
108
+ - Available on `createState`, `createSensor`
109
+ - A predicate `(value: unknown) => value is T`
110
+ - Throws `InvalidSignalValueError` if a set value fails the predicate
111
+ - Use to enforce runtime type safety at signal boundaries
112
+
113
+ ```typescript
114
+ const age = createState(0, {
115
+ guard: (v): v is number => typeof v === 'number' && v >= 0,
116
+ })
117
+ ```
118
+ </options>
119
+
120
+ <callback_patterns>
121
+ **Memo and Task callbacks receive `prev`**
122
+ - Signature: `(prev: T) => T` for Memo; `(prev: T, signal: AbortSignal) => Promise<T>` for Task
123
+ - `prev` is the previous computed value, enabling reducer-style patterns without external state:
124
+
125
+ ```typescript
126
+ const runningTotal = createMemo((prev: number) => prev + newValue.get())
127
+ ```
128
+
129
+ **Task carries an `AbortSignal`**
130
+ - The second argument to the Task callback is an `AbortSignal`
131
+ - The signal is aborted when dependencies change before the previous async run completes
132
+ - Always forward it to any `fetch` or cancellable async operation:
133
+
134
+ ```typescript
135
+ const results = createTask(async (prev, signal) => {
136
+ const res = await fetch(`/api/search?q=${query.get()}`, { signal })
137
+ return res.json()
138
+ })
139
+ ```
140
+
141
+ **`Slot` is a property descriptor**
142
+ - Has `get`, `set`, `configurable`, `enumerable` fields
143
+ - Can be passed directly to `Object.defineProperty()`:
144
+
145
+ ```typescript
146
+ const nameSlot = createSlot(store, 'name')
147
+ Object.defineProperty(element, 'name', nameSlot)
148
+ ```
149
+ </callback_patterns>
150
+
151
+ <match_helper>
152
+ `match` reads one or more Sensor/Task signals and routes to `ok` or `nil` based on whether
153
+ all signals have a value. Use it to safely handle the unset state without try/catch:
154
+
155
+ ```typescript
156
+ import { match } from '@zeix/cause-effect'
157
+
158
+ createEffect(() => {
159
+ match([task, sensor], {
160
+ ok: ([taskResult, sensorValue]) => render(taskResult, sensorValue),
161
+ nil: () => showSpinner(),
162
+ })
163
+ })
164
+ ```
165
+
166
+ Read signals you care about eagerly inside `match`'s array — not inside individual branches.
167
+ See `non-obvious-behaviors.md → conditional-reads-delay-watched` for why.
168
+ </match_helper>
169
+
170
+ <lifecycle_summary>
171
+ | Function | Requires owner? | Returns | Reactive? |
172
+ |---|---|---|---|
173
+ | `createScope(fn)` | No | `Cleanup` | No (fn runs once) |
174
+ | `createEffect(fn)` | **Yes** | `Cleanup` | Yes — re-runs on dependency change |
175
+ | `createMemo(fn)` | No | `Memo<T>` | Lazy — recomputes on read if stale |
176
+ | `createTask(fn)` | No | `Task<T>` | Yes — re-runs async on dependency change |
177
+ | `createState(value)` | No | `State<T>` | Source — never recomputes |
178
+ | `createSensor(setup)` | No | `Sensor<T>` | Source — set by external callback |
179
+ </lifecycle_summary>
@@ -0,0 +1,153 @@
1
+ <overview>
2
+ Error classes thrown by @zeix/cause-effect and the conditions that trigger them. All knowledge
3
+ is self-contained — no library source files required. Read this when writing error-handling
4
+ code, testing error conditions, or diagnosing an unexpected throw.
5
+ </overview>
6
+
7
+ <import>
8
+ All error classes are exported from the package root:
9
+
10
+ ```typescript
11
+ import {
12
+ NullishSignalValueError,
13
+ InvalidSignalValueError,
14
+ InvalidCallbackError,
15
+ DuplicateKeyError,
16
+ UnsetSignalValueError,
17
+ ReadonlySignalError,
18
+ RequiredOwnerError,
19
+ CircularDependencyError,
20
+ } from '@zeix/cause-effect'
21
+ ```
22
+ </import>
23
+
24
+ <error_table>
25
+ | Class | When thrown |
26
+ |---|---|
27
+ | `NullishSignalValueError` | Signal value is `null` or `undefined` |
28
+ | `InvalidSignalValueError` | Value fails the `guard` predicate |
29
+ | `InvalidCallbackError` | A required callback argument is not a function |
30
+ | `DuplicateKeyError` | List/Collection key collision on insert |
31
+ | `UnsetSignalValueError` | Reading a Sensor or Task before it has produced its first value |
32
+ | `ReadonlySignalError` | Attempting to write to a read-only signal |
33
+ | `RequiredOwnerError` | `createEffect` called outside an owner (scope or parent effect) |
34
+ | `CircularDependencyError` | A cycle is detected in the reactive graph |
35
+ </error_table>
36
+
37
+ <error_details>
38
+
39
+ <NullishSignalValueError>
40
+ Thrown when a signal's value is `null` or `undefined`. Because all signal generics use
41
+ `T extends {}`, nullish values are excluded by design — this error surfaces the constraint
42
+ at runtime if type safety is bypassed (e.g. via a type assertion or untyped interop).
43
+
44
+ **Prevention:** model absence explicitly with a sentinel value or wrapper type instead of `null`.
45
+ </NullishSignalValueError>
46
+
47
+ <InvalidSignalValueError>
48
+ Thrown when a value passed to `.set()` fails the `guard` predicate supplied in the signal's
49
+ options. This is the runtime enforcement of custom type narrowing at signal boundaries.
50
+
51
+ ```typescript
52
+ import { createState, InvalidSignalValueError } from '@zeix/cause-effect'
53
+
54
+ const age = createState(0, {
55
+ guard: (v): v is number => typeof v === 'number' && v >= 0,
56
+ })
57
+
58
+ age.set(-1) // throws InvalidSignalValueError
59
+ ```
60
+ </InvalidSignalValueError>
61
+
62
+ <InvalidCallbackError>
63
+ Thrown when a required callback argument — such as the computation function passed to
64
+ `createMemo`, `createTask`, or `createEffect` — is not a function. Catches programming
65
+ errors like passing `undefined` or a non-function value by mistake.
66
+ </InvalidCallbackError>
67
+
68
+ <DuplicateKeyError>
69
+ Thrown when inserting an item into a List or Collection whose key already exists. Keys must
70
+ be unique within a given List or Collection.
71
+
72
+ **Fix:** use the collection's update or set method to change an existing entry rather than
73
+ inserting a new one with the same key.
74
+ </DuplicateKeyError>
75
+
76
+ <UnsetSignalValueError>
77
+ Thrown when `.get()` is called on a Sensor or Task before it has emitted its first value.
78
+ Unlike State, Sensor and Task start in an explicitly unset state with no initial value.
79
+
80
+ **Fix:** use `match` to handle the unset state (`nil` branch) instead of calling `.get()`
81
+ directly:
82
+
83
+ ```typescript
84
+ import { match } from '@zeix/cause-effect'
85
+
86
+ createEffect(() => {
87
+ match([sensor, task], {
88
+ ok: ([s, t]) => render(s, t),
89
+ nil: () => showSpinner(),
90
+ })
91
+ })
92
+ ```
93
+ </UnsetSignalValueError>
94
+
95
+ <ReadonlySignalError>
96
+ Thrown when code attempts to call `.set()` on a read-only signal. Derived signals (Memo,
97
+ Task) are inherently read-only. Certain factory options may also produce read-only State
98
+ or Sensor instances.
99
+
100
+ **Fix:** only write to signals you own (State, Sensor via the internal setter callback).
101
+ </ReadonlySignalError>
102
+
103
+ <RequiredOwnerError>
104
+ Thrown when `createEffect` is called without an active owner in the current execution context.
105
+ Effects must be created inside a `createScope` callback or inside another `createEffect`
106
+ callback so their cleanup can be registered and managed.
107
+
108
+ ```typescript
109
+ import { createEffect, createScope } from '@zeix/cause-effect'
110
+
111
+ // Wrong — no active owner
112
+ createEffect(() => console.log('runs')) // throws RequiredOwnerError
113
+
114
+ // Correct — wrapped in a scope
115
+ const dispose = createScope(() => {
116
+ createEffect(() => console.log('runs'))
117
+ })
118
+ ```
119
+
120
+ **Exception:** use `unown` when the DOM manages the element's lifetime (e.g. inside
121
+ `connectedCallback`/`disconnectedCallback`) and you intentionally want to bypass owner
122
+ registration.
123
+ </RequiredOwnerError>
124
+
125
+ <CircularDependencyError>
126
+ Thrown when the graph engine detects a cycle during propagation — a signal that, directly
127
+ or transitively, depends on itself. Cycles make it impossible to determine a stable
128
+ evaluation order and are always a programming error.
129
+
130
+ **Common causes:**
131
+ - A Memo or Task that writes to a State it also reads
132
+ - Two Memos that read each other
133
+
134
+ **Fix:** restructure the data flow so that values move in one direction only.
135
+ </CircularDependencyError>
136
+
137
+ </error_details>
138
+
139
+ <testing_error_conditions>
140
+ Use `expect(() => ...).toThrow(ErrorClass)` to assert that a specific error is thrown.
141
+ Import the error class from the package root:
142
+
143
+ ```typescript
144
+ import { createState, InvalidSignalValueError } from '@zeix/cause-effect'
145
+
146
+ test('rejects negative age', () => {
147
+ const age = createState(0, {
148
+ guard: (v): v is number => typeof v === 'number' && v >= 0,
149
+ })
150
+ expect(() => age.set(-1)).toThrow(InvalidSignalValueError)
151
+ })
152
+ ```
153
+ </testing_error_conditions>