<overview>
Key API constraints, defaults, and callback patterns for @zeix/cause-effect.
This is the shared base reference for both consumer and developer contexts.
For consumer projects, this is self-contained. For library development, see
cause-effect-dev/references/ for additional internal details.
</overview>

<type_constraint>
**`T extends {}`** — all signal generics exclude `null` and `undefined` at the type level.
This is intentional: signals always have a value; absence must be modelled explicitly.

```typescript
// Wrong — TypeScript will reject this
const count = createState<number | null>(null)

// Correct — use a sentinel or a wrapper type
const count = createState<number>(0)
const selected = createState<{ id: string } | { id: never }>({ id: '' })
```
</type_constraint>

<core_functions>
**`createScope(fn, options?)`**
- Returns a single `Cleanup` function
- `fn` receives no arguments and may return an optional cleanup that runs when the scope is disposed
- Used to group effects and control their shared lifetime
- `options.root = true` (`ScopeOptions`) — suppresses parent-owner registration; the returned `dispose` is the sole teardown mechanism. Use for scopes whose lifecycle is controlled externally (e.g. a web component's `disconnectedCallback`)

```typescript
const dispose = createScope(() => {
  createEffect(() => console.log(count.get()))
  // all effects inside are disposed when dispose() is called
})
dispose() // cleans up everything inside
```

**`createEffect(fn)`**
- Returns a `Cleanup` function
- **Must be called inside an owner** (another effect or a scope) — throws `RequiredOwnerError` otherwise
- `fn` runs immediately and re-runs whenever its tracked dependencies change
- Registers cleanup with the current `activeOwner`

**`batch(fn)`**
- Defers the reactive flush until `fn` returns
- Multiple state writes inside `fn` coalesce into a single propagation pass
- Use when updating several signals that feed the same downstream computation

```typescript
batch(() => {
  x.set(1)
  y.set(2)
  z.set(3)
  // only one propagation pass runs after all three writes
})
```

**`untrack(fn)`**
- Runs `fn` without recording dependency edges (nulls `activeSink`)
- Reads inside `fn` do not subscribe the current computation to those signals
- Use to read a signal's current value without creating a reactive dependency

```typescript
createEffect(() => {
  const a = reactive.get()           // tracked — effect re-runs when reactive changes
  const b = untrack(() => other.get()) // untracked — no dependency on other
  render(a, b)
})
```

**`unown(fn)`**
- Runs `fn` without registering cleanups in the current owner (nulls `activeOwner`)
- For creating a scope with an external lifecycle authority, prefer `createScope(fn, { root: true })` — it is equivalent to `unown(() => createScope(fn))` but more readable
- Use `unown` directly when detaching non-scope computations from the current owner
</core_functions>

<options>
**`equals`**
- Available on `createState`, `createSensor`, `createMemo`, `createTask`
- Default: strict equality (`===`)
- When a new value is considered equal to the previous one, propagation stops —
  downstream nodes are not re-run
- **`SKIP_EQUALITY`** — special sentinel value for `equals`; forces propagation on every
  update regardless of value. Use with mutable-reference sensors where the reference
  never changes but the contents do:

```typescript
import { createSensor, SKIP_EQUALITY } from '@zeix/cause-effect'

const mouse = createSensor<{ x: number; y: number }>(
  set => {
    const handler = (e: MouseEvent) => set({ x: e.clientX, y: e.clientY })
    window.addEventListener('mousemove', handler)
    return () => window.removeEventListener('mousemove', handler)
  },
  { equals: SKIP_EQUALITY } // new object every time, so skip reference equality
)
```

**`guard`**
- Available on `createState`, `createSensor`
- A predicate `(value: unknown) => value is T`
- Throws `InvalidSignalValueError` if a set value fails the predicate
- Use to enforce runtime type safety at signal boundaries

```typescript
const age = createState(0, {
  guard: (v): v is number => typeof v === 'number' && v >= 0,
})
```
</options>

<callback_patterns>
**Memo and Task callbacks receive `prev`**
- Signature: `(prev: T) => T` for Memo; `(prev: T, signal: AbortSignal) => Promise<T>` for Task
- `prev` is the previous computed value, enabling reducer-style patterns without external state:

```typescript
const runningTotal = createMemo((prev: number) => prev + newValue.get())
```

**Task carries an `AbortSignal`**
- The second argument to the Task callback is an `AbortSignal`
- The signal is aborted when dependencies change before the previous async run completes
- Always forward it to any `fetch` or cancellable async operation:

```typescript
const results = createTask(async (prev, signal) => {
  const res = await fetch(`/api/search?q=${query.get()}`, { signal })
  return res.json()
})
```

**`Slot` is a property descriptor**
- Has `get`, `set`, `configurable`, `enumerable` fields — pass directly to `Object.defineProperty()`
- Delegates reads and writes to a swappable backing signal; use `replace(nextSignal)` to swap
- Is a forwarding layer, not a value owner — has no `update()` method

```typescript
const nameState = createState('Alice')
const nameSlot = createSlot(nameState)
Object.defineProperty(element, 'name', nameSlot)
```
</callback_patterns>

<match_helper>
`match` reads one or more Sensor/Task signals and routes to a handler based on signal state.

**Routing precedence:** `nil` > `err` > `stale` > `ok`

**Handlers:**
- `nil` — at least one signal has no value yet (loading)
- `err` — at least one signal has an error
- `stale` — all signals have a value but at least one Task is re-fetching (`isPending() === true`). Omitting `stale` falls back to `ok`, showing retained data unchanged. Cleanup returned by `stale` runs before the next handler fires.
- `ok` — all signals have a settled value

**Single-signal form** — `ok` receives the value directly, `err` a single `Error`:

```typescript
createEffect(() => {
  match(task, {
    ok:    data  => render(data),
    stale: ()    => {
      dimContent()
      return clearDimmed
    },
    nil:   ()    => showSpinner(),
    err:   error => showError(error),
  })
})
```

**Tuple form** — for two or more signals; `ok` receives a typed tuple, `err` an `Error[]`:

```typescript
createEffect(() => {
  match([task, sensor], {
    ok:  ([result, value]) => render(result, value),
    nil: () => showSpinner(),
  })
})
```

Read all signals eagerly in the signals argument — not inside branches. See
non-obvious-behaviors.md for details on conditional reads.
</match_helper>

<lifecycle_summary>
| Function | Must be in owner? | Returns | Re-runs on dependency change? |
|---|---|---|---|
| `createScope(fn, options?)` | No | `Cleanup` | No (fn runs once) |
| `createEffect(fn)` | **Yes** | `Cleanup` | Yes |
| `createMemo(fn)` | No | `Memo<T>` | Lazily (on read) |
| `createTask(fn)` | No | `Task<T>` | Yes (async) |
| `createState(value)` | No | `State<T>` | Source — never recomputes |
| `createSensor(setup)` | No | `Sensor<T>` | Source — set by external callback |
| `createSlot(signal)` | No | `Slot<T>` | Forwarding — delegates to backing signal |
| `createStore(value)` | No | `Store<T>` | Source — proxy-based |
| `createList(items, options?)` | No | `List<T>` | Source — keyed array |
| `createCollection(entries, options?)` | No | `Collection<K, V>` | Source — keyed map |
| `deriveCollection(source, callback)` | No | `Collection<K, V>` | Derived — from another reactive source |
</lifecycle_summary>
