@cplieger/reactive 2.1.2 → 2.2.0-dev.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.
- package/README.md +98 -134
- package/jsr.json +1 -1
- package/package.json +8 -10
- package/src/signal.ts +3 -5
package/README.md
CHANGED
|
@@ -2,56 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@cplieger/reactive) [](https://jsr.io/@cplieger/reactive) [](https://github.com/cplieger/reactive/issues?q=label%3Astryker-tracker)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
reactive lets you build a framework-free TypeScript browser UI with signals, keyed list rendering and in-place DOM updates, in one package with no runtime dependencies.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
It replaces the change listeners, list re-renders and element builder you would otherwise write between state and the page. It ships as ESM TypeScript source rather than compiled JavaScript, so it needs a bundler or runtime that compiles TypeScript. Releases follow semantic versioning, and it is licensed under Apache-2.0.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- a typed per-key reactive store
|
|
11
|
-
- a dynamic per-id signal registry (`SignalMap`)
|
|
12
|
-
- a reactive ordered keyed `Collection`
|
|
13
|
-
- a two-tier list render binding (`bindList`)
|
|
14
|
-
- a CSP-safe element factory (`el`)
|
|
15
|
-
- keyed-list DOM reconciliation
|
|
16
|
-
- structural tree-diffing
|
|
17
|
-
- a typed event bus (`createBus`)
|
|
9
|
+
## Why use it
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
reactive is built for a small browser app in plain TypeScript that needs no component framework.
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
- Signals, computed values and effects track their dependencies on their own. When a write returns, every effect that depends on it has already run.
|
|
14
|
+
- `createCollection` and `bindList` render a keyed list. Changing one item repaints only its row, and a reorder keeps the existing rows.
|
|
15
|
+
- `el` builds elements without HTML strings, so it works under a strict Content Security Policy. `patch` updates a subtree by reusing the elements on the page, so focus and scroll position survive a re-render.
|
|
16
|
+
- The signals, stores, collections and event bus need no DOM, so they also run under Node, Deno, Bun or Workers.
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Flush model
|
|
26
|
-
|
|
27
|
-
One phase, and this is the whole of it.
|
|
28
|
-
|
|
29
|
-
**A write flushes.** When `sig.value = x` returns, every effect that depends on
|
|
30
|
-
`sig` has already re-run. The setter notifies and drains before it returns, so
|
|
31
|
-
there is no pending-effect state a caller can observe and no queue to drain. Read
|
|
32
|
-
the DOM on the next statement and you are reading what the effects wrote. Same
|
|
33
|
-
contract as [@preact/signals-core](https://github.com/preactjs/signals/blob/main/packages/core/README.md),
|
|
34
|
-
this engine's baseline.
|
|
35
|
-
|
|
36
|
-
**`batch(fn)` is the only deferral.** Inside a batch, writes coalesce and effects
|
|
37
|
-
are held; they run once, synchronously, when the _outermost_ batch returns. A
|
|
38
|
-
nested batch defers to its outermost, and a throwing body still flushes.
|
|
39
|
-
|
|
40
|
-
**Deferral beyond that is yours.** To get effects on a later task, write on a
|
|
41
|
-
later task:
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
queueMicrotask(() => {
|
|
45
|
-
count.value = next; // effects run inside this microtask
|
|
46
|
-
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
There is deliberately **no flush barrier** — no `flushSync()`, no `nextTick()`.
|
|
50
|
-
A barrier is the API a _deferred_ model owes its callers (Vue's `nextTick`,
|
|
51
|
-
React's `flushSync`); an eager model never holds work, so it has nothing to
|
|
52
|
-
report on and nothing to force. `flushSync()` existed through v1.x, could never
|
|
53
|
-
find work pending, and refused to act when work was pending inside a batch; it
|
|
54
|
-
was removed in v2.0.0. If you were calling it, delete the call — it was a no-op.
|
|
18
|
+
Consider [Solid](https://github.com/solidjs/solid) if you want a full UI framework, with components, JSX, Suspense for async data and server-side rendering.
|
|
55
19
|
|
|
56
20
|
## Install
|
|
57
21
|
|
|
@@ -63,132 +27,132 @@ npm i @cplieger/reactive
|
|
|
63
27
|
|
|
64
28
|
## Usage
|
|
65
29
|
|
|
30
|
+
A signal holds a value, a computed value derives from signals, and an effect re-runs when what it read changes:
|
|
31
|
+
|
|
66
32
|
```typescript
|
|
67
|
-
import {
|
|
68
|
-
signal,
|
|
69
|
-
effect,
|
|
70
|
-
batch,
|
|
71
|
-
computed,
|
|
72
|
-
untracked,
|
|
73
|
-
subscribe,
|
|
74
|
-
isSignal,
|
|
75
|
-
isComputed,
|
|
76
|
-
} from "@cplieger/reactive";
|
|
33
|
+
import { signal, computed, effect, batch } from "@cplieger/reactive";
|
|
77
34
|
|
|
78
35
|
const count = signal(0);
|
|
79
36
|
const doubled = computed(() => count.value * 2);
|
|
80
37
|
|
|
81
|
-
effect(() => {
|
|
82
|
-
console.log(
|
|
83
|
-
return undefined;
|
|
38
|
+
const dispose = effect(() => {
|
|
39
|
+
console.log(count.value, doubled.value);
|
|
40
|
+
return undefined; // an effect returns undefined or a cleanup function
|
|
84
41
|
});
|
|
85
42
|
|
|
86
43
|
batch(() => {
|
|
87
44
|
count.value = 1;
|
|
88
|
-
count.value = 2;
|
|
45
|
+
count.value = 2;
|
|
46
|
+
}); // the effect ran once, with 2 and 4, before batch returned
|
|
47
|
+
|
|
48
|
+
dispose();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A collection and `bindList` render a list. Updating one item repaints only its row:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { bindList, createCollection, el } from "@cplieger/reactive";
|
|
55
|
+
|
|
56
|
+
interface Todo {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
done: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const todos = createCollection<Todo>((todo) => todo.id);
|
|
63
|
+
const list = el("ul");
|
|
64
|
+
document.body.append(list);
|
|
65
|
+
|
|
66
|
+
bindList(list, todos, {
|
|
67
|
+
mount: () => el("li"),
|
|
68
|
+
update: (row, todo) => {
|
|
69
|
+
row.textContent = todo.done ? `${todo.title} (done)` : todo.title;
|
|
70
|
+
},
|
|
89
71
|
});
|
|
90
72
|
|
|
91
|
-
|
|
73
|
+
todos.setAll([
|
|
74
|
+
{ id: "a", title: "Write docs", done: false },
|
|
75
|
+
{ id: "b", title: "Ship", done: false },
|
|
76
|
+
]);
|
|
77
|
+
todos.update("a", (todo) => ({ ...todo, done: true })); // only the first row repaints
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`patch` takes freshly built elements and applies them to a subtree, keeping the elements already on the page:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { effect, el, patch, signal } from "@cplieger/reactive";
|
|
84
|
+
|
|
85
|
+
const name = signal("world");
|
|
86
|
+
const header = el("header");
|
|
87
|
+
document.body.append(header);
|
|
88
|
+
|
|
92
89
|
effect(() => {
|
|
93
|
-
|
|
94
|
-
const notTracked = untracked(() => doubled.value);
|
|
95
|
-
console.log(tracked, notTracked);
|
|
90
|
+
patch(header, el("h1", { className: "title" }, `Hello, ${name.value}`));
|
|
96
91
|
return undefined;
|
|
97
92
|
});
|
|
98
93
|
|
|
99
|
-
//
|
|
100
|
-
const dispose = subscribe(count, (v) => console.log("count changed:", v));
|
|
101
|
-
dispose();
|
|
102
|
-
|
|
103
|
-
// Type guards
|
|
104
|
-
isSignal(count); // true
|
|
105
|
-
isComputed(doubled); // true
|
|
94
|
+
name.value = "reactive"; // the same <h1> stays on the page with its new text
|
|
106
95
|
```
|
|
107
96
|
|
|
108
97
|
## API
|
|
109
98
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- `
|
|
113
|
-
-
|
|
114
|
-
- `effect(fn: () => Cleanup): () => void`: auto-tracking side-effect with cleanup support. `fn` must return `undefined` or a cleanup function.
|
|
115
|
-
- `batch(fn)`: coalesce signal writes; effects flush synchronously at end of outermost batch. The only deferral — see [Flush model](#flush-model)
|
|
116
|
-
- `untracked<T>(fn): T`: run fn without tracking signal reads (like Preact `untracked()` / Solid `untrack()`)
|
|
117
|
-
- `touch(...signals): void`: read signals for their tracking effect alone, discarding the values — the complement of `peek()` (`peek` reads without subscribing, `touch` subscribes without keeping). `undefined` entries are skipped, so a per-key lookup that may miss (`Collection.signalFor(id)`, `SignalMap.get(id)`) can be passed straight in. Use it where a scope must re-run on a signal whose value it has no use for: most often a two-tier read, where a computed or effect takes per-entity values through `signalFor` and so must depend on the structure signal (`Collection.ids`) separately to re-derive when the id set changes. Unlike `on()` it ADDS a dependency and leaves every other read in the scope tracked
|
|
118
|
-
- `on(deps, fn, options?): () => U`: explicit dependency declaration helper (like Solid `on()`); pass into `effect()` or `computed()`. Its body runs untracked, so the declared deps are the scope's ONLY dependencies — reach for `touch()` instead when the rest of the scope should stay tracked. Passed to `effect()`, the body owes an explicit `return undefined` (or a cleanup function) like any effect callback, since a body with no return statement infers `() => void`, which the `Cleanup` contract rejects. With `{ defer: true }` the accessor returns `() => U | undefined`: the deferred first call yields `undefined`.
|
|
119
|
-
- `subscribe(signal, cb): () => void`: subscribe to a signal, calling cb immediately and on every change. The callback runs untracked: signals read inside it do not become dependencies of the subscription
|
|
120
|
-
- `isSignal(value): boolean`: type guard for signals created by `signal()`
|
|
121
|
-
- `isComputed(value): boolean`: type guard for computed signals
|
|
122
|
-
- `setEffectErrorHandler(handler): EffectErrorHandler`: set global error handler for effects; returns the previous handler
|
|
99
|
+
- Signals: `signal`, `computed`, `effect`, `batch`, `untracked`, `touch`, `on`, `subscribe`, `isSignal`, `isComputed` and `setEffectErrorHandler`.
|
|
100
|
+
- State: `createStore` for a fixed set of typed keys, `SignalMap` for keys known only at run time, and `createCollection` for an ordered list of keyed items.
|
|
101
|
+
- DOM: `el` builds elements, `reconcile` and `bindList` render keyed lists, and `patch` updates a subtree in place. `trackHandler` registers an `on*` handler on an element you built without `el`, so `patch` keeps it in step.
|
|
102
|
+
- Events: `createBus` for typed events that hold no value.
|
|
123
103
|
|
|
124
|
-
|
|
104
|
+
The full reference is on [JSR](https://jsr.io/@cplieger/reactive/doc). [Signals, stores and collections](docs/signals.md) and [Building and updating the DOM](docs/dom.md) describe how each one behaves.
|
|
125
105
|
|
|
126
|
-
|
|
127
|
-
- `reconcile<T>(parent, items, spec)`: keyed-list DOM reconciliation with mount/update/onRemove lifecycle
|
|
128
|
-
- `patch(parent, ...children)`: structural tree-diff, replacing a parent's children with reconciled new nodes. Correspondence between a child the parent has and a child you asked for is decided strongest-first: the same **node** (you handed back a node that is already a child of `parent` — it is placed at the index you passed it at, unmodified, so handing a parent its own children reordered is supported), then the same **key** (a `*-id` attribute, a specific entity id which takes precedence, or failing that `data-col`; duplicate-key siblings match in document order), then the same **position**. Any node that is not already a child of `parent` is a TEMPLATE: its shape may be copied into a reused child and the node itself never inserted. A reused element takes the new node's attributes, its tracked `on*` handlers, and the unreflected live properties the attribute pass cannot reach: `checked` (input), `selected` (option) and the `value` of an `input`/`textarea`. Those reflect to `defaultChecked`/`defaultSelected`/`defaultValue`, so without this a re-render could never turn a checkbox off or replace the text in a field. Syncing `value` overwrites what a user has typed and moves the caret to the end, which is correct only where a re-render cannot land mid-keystroke: a form re-rendered by a click is safe, one re-rendered by a timer or a server push is not, and that shape must drive the field from an effect instead (see [Unsupported by Design](#unsupported-by-design)).
|
|
129
|
-
- `trackHandler(el, key)`: register an `on*` property for handler reconciliation during tree-diff
|
|
106
|
+
## A write runs its effects before it returns
|
|
130
107
|
|
|
131
|
-
|
|
108
|
+
When `count.value = 1` returns, every effect that depends on `count` has already run, so the next line reads the DOM those effects wrote. There is no queue and no pending state to wait for.
|
|
132
109
|
|
|
133
|
-
`
|
|
110
|
+
`batch(fn)` is the only way to defer. Writes inside it coalesce, and the held effects run once, synchronously, when the outermost batch returns, even when the body throws. To run effects on a later task, make the write on a later task:
|
|
134
111
|
|
|
135
|
-
|
|
136
|
-
|
|
112
|
+
```typescript
|
|
113
|
+
queueMicrotask(() => {
|
|
114
|
+
count.value = next; // effects run inside this microtask
|
|
115
|
+
});
|
|
116
|
+
```
|
|
137
117
|
|
|
138
|
-
|
|
118
|
+
This is the contract [@preact/signals-core](https://github.com/preactjs/signals/blob/main/packages/core/README.md) documents too. The package has no `flushSync()` or `nextTick()`, because it never holds work, so there is nothing to flush.
|
|
139
119
|
|
|
140
|
-
|
|
120
|
+
## Correctness guarantees
|
|
141
121
|
|
|
142
|
-
- `
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
- `ids: ReadonlySignal<readonly string[]>`: structure tier (add/remove/reorder only)
|
|
148
|
-
- `items()`: ordered reactive snapshot (tracks order + every entity)
|
|
122
|
+
- Effects downstream of a `computed` run only when its value changes, compared with `Object.is` unless you pass `equals`. A diamond-shaped dependency graph never shows a half-updated value.
|
|
123
|
+
- Reading a `computed` while it is computing throws `Error("Cycle detected")`.
|
|
124
|
+
- When a computed function throws, every read rethrows that error until a dependency changes.
|
|
125
|
+
- Setting `.value` on a `computed` throws `Error("Cannot set a computed signal")`.
|
|
126
|
+
- `batch()` runs the held effects synchronously at the end of the outermost batch, as @preact/signals-core and solid-js do.
|
|
149
127
|
|
|
150
|
-
|
|
128
|
+
## Unsupported by design
|
|
151
129
|
|
|
152
|
-
|
|
130
|
+
The package leaves these out on purpose. [Non-goals](docs/non-goals.md) gives the reason for each.
|
|
153
131
|
|
|
154
|
-
|
|
132
|
+
- An ownership tree or `createRoot`, and automatic disposal of nested effects.
|
|
133
|
+
- Lazy activation and an `onMount` lifecycle.
|
|
134
|
+
- `Signal.subtle.Watcher` and notify-on-dirty.
|
|
135
|
+
- Introspection APIs, and explicit disposal of a computed.
|
|
136
|
+
- Server-side rendering and per-request isolation.
|
|
137
|
+
- Async signals and resources.
|
|
138
|
+
- Transactions, a custom scheduler, and a flush barrier such as `flushSync` or `nextTick`.
|
|
139
|
+
- Controlled form inputs. `patch` syncs form values, so if a view can re-render while a user types, drive that field from an effect instead.
|
|
140
|
+
- Writing a signal from inside a computed. The computed's own subscribers are not woken, so make that write in an effect.
|
|
155
141
|
|
|
156
|
-
|
|
142
|
+
## Documentation
|
|
157
143
|
|
|
158
|
-
-
|
|
144
|
+
- [Signals, stores and collections](docs/signals.md) covers the flush model, effects, stores, collections and the event bus, for a reader writing state code.
|
|
145
|
+
- [Building and updating the DOM](docs/dom.md) covers how `el`, `reconcile`, `bindList` and `patch` decide what to keep, for a reader rendering with them.
|
|
146
|
+
- [Non-goals](docs/non-goals.md) lists what the package leaves out and why.
|
|
159
147
|
|
|
160
|
-
##
|
|
148
|
+
## Credits
|
|
161
149
|
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
- **Error caching**: If a computed's fn throws, the error is cached and rethrown on subsequent reads until dependencies change.
|
|
165
|
-
- **Computed is read-only**: Setting `.value` on a computed throws `Error("Cannot set a computed signal")`.
|
|
166
|
-
- **Synchronous batch**: `batch()` flushes effects synchronously at the end of the outermost batch (matching @preact/signals-core and solid-js). It is the only deferral the library has; a bare write flushes before it returns. See [Flush model](#flush-model).
|
|
167
|
-
|
|
168
|
-
## Unsupported by Design
|
|
169
|
-
|
|
170
|
-
The following features are intentionally NOT implemented:
|
|
171
|
-
|
|
172
|
-
| Feature | Reason |
|
|
173
|
-
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
174
|
-
| **Effect ownership tree / `createRoot`** | Consumers manage disposal explicitly. Adding ownership changes the mental model. |
|
|
175
|
-
| **Nested effect auto-disposal** | Each effect is independent and returns its own dispose function. Compose with arrays or helper functions. |
|
|
176
|
-
| **Lazy activation / `onMount` lifecycle** | Signals are always active. Resource management is the consumer's responsibility. |
|
|
177
|
-
| **`Signal.subtle.Watcher` / notify-on-dirty** | This library IS the framework layer. The Watcher pattern is for frameworks that sit on top of a signals primitive. |
|
|
178
|
-
| **Introspection APIs** | Dev-tools concern. Not needed for production. |
|
|
179
|
-
| **Explicit computed disposal** | Computed signals are GC'd when unreferenced. No explicit teardown needed. |
|
|
180
|
-
| **SSR / server-side isolation** | Client-side library. Server usage should instantiate fresh signal graphs per request. |
|
|
181
|
-
| **Async signals / resources** | Out of scope. Use effects + manual signal writes for async data loading. |
|
|
182
|
-
| **Transactions** | Framework-level concern. Not a signals primitive. |
|
|
183
|
-
| **Custom scheduler / `setScheduler()`** | Batch is synchronous and a write flushes before it returns, so there is no queue to schedule. Deferral is the caller's: write on a later task. See [Flush model](#flush-model). |
|
|
184
|
-
| **A flush barrier (`flushSync`, `nextTick`)** | A barrier is what a deferred model owes its callers; this model never holds work, so a barrier has nothing to report on. `flushSync()` shipped through v1.x and was a no-op from every position a caller could occupy; removed in v2.0.0. |
|
|
185
|
-
| **Controlled form inputs** | `patch()` syncs `checked`, `selected` and `input`/`textarea` `value`, so the render owns them. It has no change routing or state ownership, so a surface re-rendered while a user types must own the field itself. |
|
|
186
|
-
| **Waking subscribers of a self-writing computed** | A computed that writes a signal it reads is re-read correctly (it is marked stale so the next read re-runs it), but its own subscribers are not woken, so an effect depending on it does not re-run until something else notifies it. Writing a source from a computed body is outside the intended shape; do it in an effect. |
|
|
150
|
+
- The signal engine follows the design of [@preact/signals-core](https://github.com/preactjs/signals) in its dependency graph, its glitch-free computed refresh, its run-on-write effects and its untracked `subscribe` callback.
|
|
151
|
+
- The `on()` helper follows [Solid](https://github.com/solidjs/solid)'s `on()`, and `untracked()` is Solid's `untrack()` under the name Preact gives it.
|
|
187
152
|
|
|
188
153
|
## Contributing
|
|
189
154
|
|
|
190
|
-
Issues and
|
|
191
|
-
conventions and how to run the checks locally.
|
|
155
|
+
Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
192
156
|
|
|
193
157
|
## Disclaimer
|
|
194
158
|
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/reactive",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.0-dev.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -21,17 +21,15 @@
|
|
|
21
21
|
"@stryker-mutator/typescript-checker": "10.0.0",
|
|
22
22
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
23
23
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
24
|
-
"@vitest/browser": "5.0.
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"fast-check": "4.10.1",
|
|
29
|
-
"knip": "6.36.0",
|
|
24
|
+
"@vitest/browser-playwright": "5.0.3",
|
|
25
|
+
"eslint": "10.12.0",
|
|
26
|
+
"fast-check": "4.10.2",
|
|
27
|
+
"knip": "6.39.0",
|
|
30
28
|
"playwright": "1.63.0",
|
|
31
|
-
"prettier": "3.9.
|
|
29
|
+
"prettier": "3.9.9",
|
|
32
30
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
33
|
-
"typescript-eslint": "8.
|
|
34
|
-
"vitest": "5.0.
|
|
31
|
+
"typescript-eslint": "8.71.0",
|
|
32
|
+
"vitest": "5.0.3"
|
|
35
33
|
},
|
|
36
34
|
"overrides": {
|
|
37
35
|
"qs": "^6.15.2"
|
package/src/signal.ts
CHANGED
|
@@ -682,9 +682,8 @@ export function untracked<T>(fn: () => T): T {
|
|
|
682
682
|
* This exists because the two-tier shape `createCollection` and `bindList` are
|
|
683
683
|
* built on produces it: a scope reads per-entity values through `signalFor`,
|
|
684
684
|
* so it must depend on the structure signal separately to re-run when the id
|
|
685
|
-
* set changes, and that dependency has no value the body wants.
|
|
686
|
-
*
|
|
687
|
-
* are accepted so a per-key lookup that may miss (`signalFor(id)`,
|
|
685
|
+
* set changes, and that dependency has no value the body wants. `undefined`
|
|
686
|
+
* entries are accepted so a per-key lookup that may miss (`signalFor(id)`,
|
|
688
687
|
* `SignalMap.get(id)`) can be passed straight in.
|
|
689
688
|
*
|
|
690
689
|
* Not a substitute for `on()`, which declares an exhaustive dependency list
|
|
@@ -698,8 +697,7 @@ export function touch(
|
|
|
698
697
|
}
|
|
699
698
|
}
|
|
700
699
|
|
|
701
|
-
//
|
|
702
|
-
// deliberately unused, and returning it is what keeps that legible.
|
|
700
|
+
// deadset:ignore DS1803 -- returning the value keeps the tracking read from being a discarded expression; typescript-eslint rejects void on a property read
|
|
703
701
|
function readForDependency(sig: Signal<unknown> | ReadonlySignal<unknown> | undefined): unknown {
|
|
704
702
|
return sig?.value;
|
|
705
703
|
}
|