solid-deps 0.0.1 → 0.0.2

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
@@ -7,7 +7,13 @@ Attach additional reactive dependencies to an existing **Solid 2 effect**—even
7
7
  ## Installation
8
8
 
9
9
  ```sh
10
- npm install solid-deps@0.0.1 solid-js@2.0.0-rc.17 @solidjs/signals@2.0.0-rc.17
10
+ npm install solid-deps@0.0.2 solid-js@2.0.0-rc.17 @solidjs/signals@2.0.0-rc.17
11
+ ```
12
+
13
+ With Bun:
14
+
15
+ ```sh
16
+ bun add solid-deps@0.0.2 solid-js@2.0.0-rc.17 @solidjs/signals@2.0.0-rc.17
11
17
  ```
12
18
 
13
19
  **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).
@@ -184,9 +190,13 @@ createEffect(async () => {
184
190
 
185
191
  `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
192
 
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.
193
+ 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. The library rejects reader callbacks that create owned children or register cleanups, and rejects sources owned by its temporary observer, to prevent subscriptions to disposed computations.
194
+
195
+ Pending async memo reads can throw `NotReadyError`; a failed read does not attach dependencies. A memo with a `loadingValue` can return that fallback while pending, but `attachRead` does not restore the target observer's original suspension or transition context. Treat resources, loading boundaries, held transactions, and optimistic state as **unsupported beyond ordinary settled reads**.
196
+
197
+ An `attachRead()` subscription is prospective: signal updates that happen before attachment are not replayed. Grafted dependencies can be removed by the target's next normal recomputation. For async effects, reattach on each pass using `captureObserverPass()`.
188
198
 
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.
199
+ Reads that create computations/cleanups, write signals, or depend on `getOwner()`/`getObserver()` seeing the original effect are unsupported, because the callback executes under a temporary observer. Server rendering and independently bundled copies of the signals runtime are not supported. Do not assume compatibility with other Solid versions.
190
200
 
191
201
  ## License
192
202
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-deps",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Experimental direct dependency attachment for Solid 2 reactive observers after async boundaries",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,10 +36,14 @@
36
36
  ],
37
37
  "scripts": {
38
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",
39
+ "test": "node --conditions=browser --test test/test.mjs test/test-async.mjs test/test-tuple.mjs test/test-manifest.mjs test/test-graph-hardening.mjs test/test-async-transitions.mjs test/test-transaction-integrity.mjs test/test-scheduler-lifecycle.mjs test/test-release-hardening.mjs",
40
+ "test:dev": "node --conditions=browser --conditions=development --test test/test.mjs test/test-async.mjs test/test-tuple.mjs test/test-manifest.mjs test/test-graph-hardening.mjs test/test-async-transitions.mjs test/test-transaction-integrity.mjs test/test-scheduler-lifecycle.mjs test/test-release-hardening.mjs",
41
+ "test:observe": "node --conditions=browser --conditions=observe --test test/test.mjs test/test-async.mjs test/test-tuple.mjs test/test-manifest.mjs test/test-graph-hardening.mjs test/test-async-transitions.mjs test/test-transaction-integrity.mjs test/test-scheduler-lifecycle.mjs test/test-release-hardening.mjs",
42
42
  "test:all": "node scripts/run-tests.mjs",
43
43
  "test:package": "node scripts/smoke-package.mjs"
44
+ },
45
+ "devDependencies": {
46
+ "@solidjs/signals": "2.0.0-rc.17",
47
+ "solid-js": "2.0.0-rc.17"
44
48
  }
45
49
  }
@@ -8,15 +8,27 @@
8
8
  import { createRoot, createReaction, getObserver, isDisposed } from '@solidjs/signals';
9
9
 
10
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' }
11
+ dev: { deps: '_deps', tail: '_depsTail', gen: '_depGen', flags: '_flags', dep: '_dep', sub: '_sub', next: '_nextDep', linkGen: '_gen', child: '_firstChild', disposal: '_disposal' },
12
+ prod: { deps: 'C', tail: '_', gen: 'Ne', flags: 'R', dep: 'S', sub: 'u', next: 'G', linkGen: 'Te', child: 'De', disposal: 'He' },
13
+ observe: { deps: 'C', tail: '_', gen: 'Ne', flags: 'R', dep: 'S', sub: 'u', next: 'G', linkGen: 'Te', child: 'ze', disposal: 'rt' }
13
14
  };
14
15
  function layoutOf(node) {
15
- const shape = node && (('_deps' in node) ? layouts.dev : ('C' in node) ? layouts.prod : null);
16
+ const shape = node && (('_deps' in node) ? layouts.dev : ('C' in node) ? ('ze' in node ? layouts.observe : layouts.prod) : null);
16
17
  if (!shape || typeof node[shape.gen] !== 'number' || !(shape.tail in node))
17
18
  throw new TypeError('Expected a tracking Computed from @solidjs/signals rc.17, not a plain Owner');
18
19
  return shape;
19
20
  }
21
+ // Probe-owned sources disappear when the temporary tracking root is disposed.
22
+ // Their dependency edges must never be transferred to a longer-lived target.
23
+ function ownedBy(node, ancestor) {
24
+ const seen = new Set();
25
+ for (let owner = node; owner != null; owner = owner._parent) {
26
+ if (owner === ancestor) return true;
27
+ if (seen.has(owner)) throw new Error('Cyclic owner ancestry');
28
+ seen.add(owner);
29
+ }
30
+ return false;
31
+ }
20
32
  function links(owner, layout) {
21
33
  const out = [];
22
34
  let cur = owner[layout.deps];
@@ -91,6 +103,19 @@ export function attachRead(observerOrPass, read) {
91
103
  throw new Error('Target dependency chain changed during capture');
92
104
 
93
105
  const captured = links(probe, s);
106
+ // `read()` must not allocate owned children or register cleanups. A child
107
+ // can disappear when the probe is disposed, and a cleanup can throw AFTER
108
+ // a successful graft, leaving the target changed despite a failed call.
109
+ if (probe[s.child] != null || probe[s.disposal] != null)
110
+ throw new Error('attachRead read() cannot create owned children or register cleanups');
111
+ // Guard before touching either graph. A memo or signal created inside
112
+ // `read()` may be owned by the disposable probe, and disposing that probe
113
+ // would otherwise leave the target subscribed to a disposed source.
114
+ for (const edge of captured) {
115
+ const source = edge[s.dep];
116
+ if (isDisposed(source) || ownedBy(source, probe))
117
+ throw new Error('Cannot attach a disposed or temporary-observer-owned source');
118
+ }
94
119
  const seen = new Set(valid.map(edge => edge[s.dep]));
95
120
  const staleBySource = new Map(stale.map(edge => [edge[s.dep], edge]));
96
121
  const moved = [];