solid-deps 0.0.0-stage → 0.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.
- package/LICENSE +21 -0
- package/README.md +192 -2
- package/package.json +43 -4
- package/src/attach-read.mjs +150 -0
- package/src/index.mjs +1 -0
- package/types/index.d.mts +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 solid-deps contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
|
-
#
|
|
1
|
+
# solid-deps
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Attach additional reactive dependencies to an existing **Solid 2 effect**—even after an `await`—without re-running the effect at the moment of attachment.
|
|
4
|
+
|
|
5
|
+
`solid-deps` is an experimental, browser-oriented utility for Solid 2. It lets a synchronous callback read signals, memos, or store properties and registers those reads as dependencies of an existing tracked computation. When an attached dependency changes, Solid can rerun that computation normally.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install solid-deps@0.0.1 solid-js@2.0.0-rc.17 @solidjs/signals@2.0.0-rc.17
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Compatibility:** This release targets **Solid 2.0.0-rc.17** (`solid-js` and `@solidjs/signals`). Both the app and `solid-deps` must resolve to the **same instance** of `@solidjs/signals`. Server-side rendering is not supported. See [Limitations](#limitations).
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Capture the computation before `await`, then attach the reads after the asynchronous work finishes.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { createEffect, createSignal, getOwner } from "solid-js";
|
|
21
|
+
import { attachRead, captureObserverPass } from "solid-deps";
|
|
22
|
+
|
|
23
|
+
export default function Counter() {
|
|
24
|
+
const [count, setCount] = createSignal(0);
|
|
25
|
+
|
|
26
|
+
createEffect(
|
|
27
|
+
async () => {
|
|
28
|
+
// Capture the current effect pass while it is still tracking.
|
|
29
|
+
const pass = captureObserverPass(getOwner());
|
|
30
|
+
|
|
31
|
+
await Promise.resolve();
|
|
32
|
+
|
|
33
|
+
// count() becomes a dependency of this effect.
|
|
34
|
+
const [value, result] = attachRead(pass, () => count());
|
|
35
|
+
|
|
36
|
+
// An older async run may finish after a newer one, or after disposal.
|
|
37
|
+
if (result.stale || result.disposed) return;
|
|
38
|
+
|
|
39
|
+
console.log("Count:", value);
|
|
40
|
+
},
|
|
41
|
+
() => {}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button type="button" onClick={() => setCount(value => value + 1)}>
|
|
46
|
+
Increment: {count()}
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The first late read adds `count` as a dependency. Later changes to `count` can rerun the effect; each new pass captures its own token and reattaches the read after `await`.
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
The package exports `attachRead`, `captureObserverPass`, and `inspectDeps` from its main entry point.
|
|
57
|
+
|
|
58
|
+
### `attachRead(observerOrPass, read)`
|
|
59
|
+
|
|
60
|
+
Runs a **synchronous** reader and attaches the reactive sources it accesses to the specified existing computation.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const [retValue, result] = attachRead(pass, () => {
|
|
64
|
+
const countValue = count();
|
|
65
|
+
const nameValue = name();
|
|
66
|
+
return { countValue, nameValue };
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The return value is always a tuple:
|
|
71
|
+
|
|
72
|
+
- **`retValue`** — the exact value returned by `read()`, or `undefined` if the read was skipped.
|
|
73
|
+
- **`result`** — an `AttachReadResult` object describing the attachment.
|
|
74
|
+
|
|
75
|
+
| Result field | Type | Meaning |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `added` | `number` | Newly attached or revalidated dependencies, including promoted ones. |
|
|
78
|
+
| `duplicate` | `number` | Dependencies already valid for the current effect pass. |
|
|
79
|
+
| `promoted` | `number` | Previously attached dependencies reused for this pass; included in `added`. |
|
|
80
|
+
| `disposed` | `boolean` | Target computation was disposed; `read()` was not called. |
|
|
81
|
+
| `stale` | `boolean` | Captured pass has been superseded; `read()` was not called. |
|
|
82
|
+
|
|
83
|
+
A disposed or stale pass returns `[undefined, result]` without executing `read()`. Errors thrown by the reader propagate to the caller. A reader returning a Promise or thenable is rejected with a `TypeError`; awaiting work **before** calling `attachRead()` is supported, but passing an `async` reader is not.
|
|
84
|
+
|
|
85
|
+
You may also pass a captured computation directly:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { getOwner } from "solid-js";
|
|
89
|
+
|
|
90
|
+
// `owner` must have been captured inside a tracked computation.
|
|
91
|
+
const [value, result] = attachRead(owner, () => count());
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For async effects, prefer a pass token so an older continuation cannot attach dependencies to a newer execution.
|
|
95
|
+
|
|
96
|
+
### `captureObserverPass(observer)`
|
|
97
|
+
|
|
98
|
+
Captures a token identifying the current execution of a tracked computation. Call it **during the computation's tracked phase, before an `await`**.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const pass = captureObserverPass(getOwner());
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Pass the token to `attachRead()`. If a newer execution has started, the older token produces `result.stale === true` and the reader is skipped.
|
|
105
|
+
|
|
106
|
+
`getOwner()` must refer to an actual tracking computation. An ordinary component/root owner is not a suitable attachment target.
|
|
107
|
+
|
|
108
|
+
### `inspectDeps(observer)`
|
|
109
|
+
|
|
110
|
+
Returns an array of the current dependency **source objects** for the supplied computation:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const sources = inspectDeps(owner);
|
|
114
|
+
console.log(sources.length);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This is a diagnostic helper, not an accessor-value API. Treat returned source objects as read-only.
|
|
118
|
+
|
|
119
|
+
## TypeScript
|
|
120
|
+
|
|
121
|
+
Type declarations are included. No separate `@types` package is needed.
|
|
122
|
+
|
|
123
|
+
The package exports the following types and typed function signatures:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import type { Owner } from "@solidjs/signals";
|
|
127
|
+
|
|
128
|
+
export interface ObserverPass {
|
|
129
|
+
readonly __solidDepsObserverPass: unique symbol;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface AttachReadResult {
|
|
133
|
+
readonly added: number;
|
|
134
|
+
readonly duplicate: number;
|
|
135
|
+
readonly promoted: number;
|
|
136
|
+
readonly disposed: boolean;
|
|
137
|
+
readonly stale: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export declare function captureObserverPass(observer: Owner | null): ObserverPass;
|
|
141
|
+
|
|
142
|
+
export declare function attachRead<T>(
|
|
143
|
+
observerOrPass: Owner | ObserverPass | null,
|
|
144
|
+
read: () => T
|
|
145
|
+
): [retValue: T | undefined, result: AttachReadResult];
|
|
146
|
+
|
|
147
|
+
export declare function inspectDeps(observer: Owner): unknown[];
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For example, TypeScript preserves the reader's return type:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { createEffect, createSignal, getOwner } from "solid-js";
|
|
154
|
+
import { attachRead, captureObserverPass } from "solid-deps";
|
|
155
|
+
import type { AttachReadResult, ObserverPass } from "solid-deps";
|
|
156
|
+
|
|
157
|
+
const [count] = createSignal(0);
|
|
158
|
+
|
|
159
|
+
createEffect(async () => {
|
|
160
|
+
const pass: ObserverPass = captureObserverPass(getOwner());
|
|
161
|
+
await Promise.resolve();
|
|
162
|
+
|
|
163
|
+
const [value, result] = attachRead(pass, () => count());
|
|
164
|
+
// value: number | undefined
|
|
165
|
+
// result: AttachReadResult
|
|
166
|
+
|
|
167
|
+
if (!result.stale && !result.disposed && value !== undefined) {
|
|
168
|
+
console.log(value.toFixed(0));
|
|
169
|
+
}
|
|
170
|
+
}, () => {});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`ObserverPass` is an opaque type. Obtain one from `captureObserverPass()` rather than constructing it yourself.
|
|
174
|
+
|
|
175
|
+
## Dependency lifetime
|
|
176
|
+
|
|
177
|
+
- Reads are attached **without immediately re-running** the target computation.
|
|
178
|
+
- Source changes that occurred **before** attachment are not replayed.
|
|
179
|
+
- The attached dependencies participate in subsequent normal updates. On the next computation pass, Solid may remove dependencies that are not read or reattached for that pass.
|
|
180
|
+
- Use `captureObserverPass()` for async work that may overlap with newer effect executions.
|
|
181
|
+
- Disposed computations cannot receive new dependencies.
|
|
182
|
+
|
|
183
|
+
## Limitations
|
|
184
|
+
|
|
185
|
+
`solid-deps` is experimental and restricted to Solid **2.0.0-rc.17** in browser-oriented applications. Dependency registration requires an existing **tracked computation**, not simply any owner.
|
|
186
|
+
|
|
187
|
+
Keep `read()` synchronous and limited to settled reactive reads, such as signals, memos, or store properties. Do not use it to create computations, install cleanups, write signals, or perform side effects. It does not run in the target's original reactive context.
|
|
188
|
+
|
|
189
|
+
Pending resources, transitions, reentrant reactive updates, server rendering, and independently bundled copies of the signals runtime are not supported. Do not assume compatibility with other Solid versions.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid-deps",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Experimental direct dependency attachment for Solid 2 reactive observers after async boundaries",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.12.0"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./types/index.d.mts",
|
|
13
|
+
"default": "./src/index.mjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"types": "./types/index.d.mts",
|
|
17
|
+
"files": [
|
|
18
|
+
"src/attach-read.mjs",
|
|
19
|
+
"src/index.mjs",
|
|
20
|
+
"types/index.d.mts",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@solidjs/signals": "2.0.0-rc.17"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"solid",
|
|
30
|
+
"solidjs",
|
|
31
|
+
"signals",
|
|
32
|
+
"reactivity",
|
|
33
|
+
"async",
|
|
34
|
+
"observer",
|
|
35
|
+
"dependency-tracking"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"setup": "node scripts/setup.mjs",
|
|
39
|
+
"test": "node --conditions=browser --test test/test.mjs test/test-async.mjs test/test-tuple.mjs",
|
|
40
|
+
"test:dev": "node --conditions=browser --conditions=development --test test/test.mjs test/test-async.mjs test/test-tuple.mjs",
|
|
41
|
+
"test:observe": "node --conditions=browser --conditions=observe --test test/test.mjs test/test-async.mjs test/test-tuple.mjs",
|
|
42
|
+
"test:all": "node scripts/run-tests.mjs",
|
|
43
|
+
"test:package": "node scripts/smoke-package.mjs"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental RC.17 graph surgery. No changes/forks of Solid.
|
|
3
|
+
* Target must be captured using getObserver() while its tracking function runs.
|
|
4
|
+
* Scope: synchronous, settled signal and memo reads, outside a target recompute.
|
|
5
|
+
* Maintenance/compatibility: see HANDOFF.md and docs/INTERNALS.md.
|
|
6
|
+
* This is link transfer, NOT restoration of the original observer context.
|
|
7
|
+
*/
|
|
8
|
+
import { createRoot, createReaction, getObserver, isDisposed } from '@solidjs/signals';
|
|
9
|
+
|
|
10
|
+
const layouts = {
|
|
11
|
+
dev: { deps: '_deps', tail: '_depsTail', gen: '_depGen', flags: '_flags', dep: '_dep', sub: '_sub', next: '_nextDep', linkGen: '_gen' },
|
|
12
|
+
prod: { deps: 'C', tail: '_', gen: 'Ne', flags: 'R', dep: 'S', sub: 'u', next: 'G', linkGen: 'Te' }
|
|
13
|
+
};
|
|
14
|
+
function layoutOf(node) {
|
|
15
|
+
const shape = node && (('_deps' in node) ? layouts.dev : ('C' in node) ? layouts.prod : null);
|
|
16
|
+
if (!shape || typeof node[shape.gen] !== 'number' || !(shape.tail in node))
|
|
17
|
+
throw new TypeError('Expected a tracking Computed from @solidjs/signals rc.17, not a plain Owner');
|
|
18
|
+
return shape;
|
|
19
|
+
}
|
|
20
|
+
function links(owner, layout) {
|
|
21
|
+
const out = [];
|
|
22
|
+
let cur = owner[layout.deps];
|
|
23
|
+
const visited = new Set();
|
|
24
|
+
while (cur != null) {
|
|
25
|
+
if (visited.has(cur)) throw new Error('Cyclic dependency list');
|
|
26
|
+
visited.add(cur);
|
|
27
|
+
out.push(cur);
|
|
28
|
+
cur = cur[layout.next];
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
// Capture a generation before an await, so a superseded async invocation
|
|
33
|
+
// cannot attach subscriptions to a more recent pass of the same effect.
|
|
34
|
+
const observerPasses = new WeakMap();
|
|
35
|
+
export function captureObserverPass(observer) {
|
|
36
|
+
const shape = layoutOf(observer);
|
|
37
|
+
const token = Object.freeze({});
|
|
38
|
+
observerPasses.set(token, { observer, generation: observer[shape.gen] });
|
|
39
|
+
return token;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Attach dependencies read by `read` onto `target` without re-running target.
|
|
44
|
+
* Returns [retValue, result] where result describes the graft.
|
|
45
|
+
* They remain until target recomputes and performs normal dependency reconciliation.
|
|
46
|
+
*/
|
|
47
|
+
export function attachRead(observerOrPass, read) {
|
|
48
|
+
const pass = observerOrPass && observerPasses.get(observerOrPass);
|
|
49
|
+
const target = pass ? pass.observer : observerOrPass;
|
|
50
|
+
const s = layoutOf(target);
|
|
51
|
+
if (typeof read !== 'function') throw new TypeError('read must be a function');
|
|
52
|
+
if (isDisposed(target)) return [undefined, { added: 0, duplicate: 0, promoted: 0, disposed: true, stale: false }];
|
|
53
|
+
if (pass && target[s.gen] !== pass.generation)
|
|
54
|
+
return [undefined, { added: 0, duplicate: 0, promoted: 0, disposed: false, stale: true }];
|
|
55
|
+
if (target[s.flags] & 4) throw new Error('Target observer is currently recomputing');
|
|
56
|
+
const existing = links(target, s);
|
|
57
|
+
const beforeTail = target[s.tail];
|
|
58
|
+
const beforeGen = target[s.gen];
|
|
59
|
+
// Solid can retain an uncommitted stale suffix from a previous pass.
|
|
60
|
+
// The tail identifies the validated *prefix*, not necessarily the list end.
|
|
61
|
+
const prefixEnd = beforeTail == null ? -1 : existing.indexOf(beforeTail);
|
|
62
|
+
if (beforeTail !== null && prefixEnd < 0)
|
|
63
|
+
throw new Error('Target tail is not reachable from its dependency head');
|
|
64
|
+
const valid = existing.slice(0, prefixEnd + 1);
|
|
65
|
+
const stale = existing.slice(prefixEnd + 1);
|
|
66
|
+
let disposeRoot;
|
|
67
|
+
let probe;
|
|
68
|
+
let caught;
|
|
69
|
+
let retValue;
|
|
70
|
+
try {
|
|
71
|
+
createRoot(dispose => {
|
|
72
|
+
disposeRoot = dispose;
|
|
73
|
+
const track = createReaction(() => {});
|
|
74
|
+
track(() => {
|
|
75
|
+
probe = getObserver();
|
|
76
|
+
try { retValue = read(); } catch (err) { caught = [err]; }
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
if (caught) throw caught[0];
|
|
80
|
+
if (retValue != null && typeof retValue.then === 'function')
|
|
81
|
+
throw new TypeError('attachRead requires synchronous reads, not an async callback');
|
|
82
|
+
if (!probe) throw new Error('No temporary observer was captured');
|
|
83
|
+
const p = layoutOf(probe);
|
|
84
|
+
if (p !== s) throw new Error('Temporary observer and target use different Solid builds');
|
|
85
|
+
if (isDisposed(target)) return [undefined, { added: 0, duplicate: 0, promoted: 0, disposed: true, stale: false }];
|
|
86
|
+
if ((target[s.flags] & 4) || target[s.tail] !== beforeTail || target[s.gen] !== beforeGen ||
|
|
87
|
+
(target[s.deps] ?? null) !== (existing[0] ?? null))
|
|
88
|
+
throw new Error('Target changed during capture; refusing unsafe graft');
|
|
89
|
+
const after = links(target, s);
|
|
90
|
+
if (after.length !== existing.length || after.some((edge, i) => edge !== existing[i]))
|
|
91
|
+
throw new Error('Target dependency chain changed during capture');
|
|
92
|
+
|
|
93
|
+
const captured = links(probe, s);
|
|
94
|
+
const seen = new Set(valid.map(edge => edge[s.dep]));
|
|
95
|
+
const staleBySource = new Map(stale.map(edge => [edge[s.dep], edge]));
|
|
96
|
+
const moved = [];
|
|
97
|
+
const promoted = [];
|
|
98
|
+
const kept = [];
|
|
99
|
+
|
|
100
|
+
for (const edge of captured) {
|
|
101
|
+
const source = edge[s.dep];
|
|
102
|
+
if (seen.has(source)) {
|
|
103
|
+
kept.push(edge);
|
|
104
|
+
} else {
|
|
105
|
+
seen.add(source);
|
|
106
|
+
const old = staleBySource.get(source);
|
|
107
|
+
if (old) {
|
|
108
|
+
promoted.push(old);
|
|
109
|
+
kept.push(edge);
|
|
110
|
+
} else {
|
|
111
|
+
moved.push(edge);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Fix the temporary observer's list before its disposer walks it.
|
|
116
|
+
probe[s.deps] = kept[0] ?? null;
|
|
117
|
+
probe[s.tail] = kept.at(-1) ?? null;
|
|
118
|
+
for (let i = 0; i < kept.length; i++) kept[i][s.next] = kept[i+1] ?? null;
|
|
119
|
+
|
|
120
|
+
// Rebuild the target's list as:
|
|
121
|
+
// validated prefix -> newly validated links -> stale suffix.
|
|
122
|
+
// Trimming the suffix remains Solid's responsibility at commit/settle.
|
|
123
|
+
const promotedSet = new Set(promoted);
|
|
124
|
+
const staleRemainder = stale.filter(edge => !promotedSet.has(edge));
|
|
125
|
+
const additions = [...promoted, ...moved];
|
|
126
|
+
const validated = [...valid, ...additions];
|
|
127
|
+
const nextList = [...validated, ...staleRemainder];
|
|
128
|
+
for (const edge of additions) {
|
|
129
|
+
edge[s.sub] = target;
|
|
130
|
+
edge[s.linkGen] = beforeGen;
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < nextList.length; i++)
|
|
133
|
+
nextList[i][s.next] = nextList[i + 1] ?? null;
|
|
134
|
+
target[s.deps] = nextList[0] ?? null;
|
|
135
|
+
target[s.tail] = validated.at(-1) ?? null;
|
|
136
|
+
return [retValue, {
|
|
137
|
+
added: additions.length,
|
|
138
|
+
duplicate: kept.length - promoted.length,
|
|
139
|
+
promoted: promoted.length,
|
|
140
|
+
disposed: false,
|
|
141
|
+
stale: false
|
|
142
|
+
}];
|
|
143
|
+
} finally {
|
|
144
|
+
disposeRoot?.();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function inspectDeps(owner) {
|
|
148
|
+
const s = layoutOf(owner);
|
|
149
|
+
return links(owner, s).map(link => link[s.dep]);
|
|
150
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { attachRead, captureObserverPass, inspectDeps } from './attach-read.mjs';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Owner } from '@solidjs/signals';
|
|
2
|
+
|
|
3
|
+
/** Opaque identity of a specific observer recomputation, captured before await. */
|
|
4
|
+
export interface ObserverPass {
|
|
5
|
+
readonly __solidDepsObserverPass: unique symbol;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Metadata about a synchronous dependency graft. */
|
|
9
|
+
export interface AttachReadResult {
|
|
10
|
+
/** Newly attached/validated sources, INCLUDING promotions. */
|
|
11
|
+
readonly added: number;
|
|
12
|
+
/** Sources already attached as current validated dependencies. */
|
|
13
|
+
readonly duplicate: number;
|
|
14
|
+
/** Prior-generation links reused; a subset of added. */
|
|
15
|
+
readonly promoted: number;
|
|
16
|
+
/** Observer already disposed; callback was not invoked. */
|
|
17
|
+
readonly disposed: boolean;
|
|
18
|
+
/** A newer recomputation superseded the pass; callback was not invoked. */
|
|
19
|
+
readonly stale: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Add dependencies read by a synchronous callback to an existing tracking observer.
|
|
24
|
+
* Returns `[retValue, result]` (not `[result, retValue]`).
|
|
25
|
+
* If skipped for stale/disposed target, retValue is undefined.
|
|
26
|
+
* EXPERIMENTAL: accesses private Solid 2.0.0-rc.17 dependency graph layouts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function attachRead<T>(
|
|
29
|
+
observerOrPass: Owner | ObserverPass | null,
|
|
30
|
+
read: () => T
|
|
31
|
+
): [retValue: T | undefined, result: AttachReadResult];
|
|
32
|
+
|
|
33
|
+
/** Capture a specific recomputation inside a tracking callback (before await). */
|
|
34
|
+
export declare function captureObserverPass(observer: Owner | null): ObserverPass;
|
|
35
|
+
|
|
36
|
+
/** Return source node objects in an observer's dependency chain. Diagnostic only. */
|
|
37
|
+
export declare function inspectDeps(observer: Owner): unknown[];
|