solid-deps 0.0.3 → 0.0.4

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
@@ -16,7 +16,7 @@ Or with Bun:
16
16
  bun add solid-deps solid-js@2.0.0-rc.17 @solidjs/signals@2.0.0-rc.17
17
17
  ```
18
18
 
19
- **Compatibility:** Tested with Solid 2.0.0-rc.17 production, development and observe builds. `solid-deps` now discovers observer graph fields at runtime, rather than using fixed field-name mappings. Other Solid 2 versions may work if they retain compatible internal graph semantics, but are not yet verified. Your app and `solid-deps` must share one `@solidjs/signals` runtime instance. SSR is not supported.
19
+ **Compatibility:** Tested with Solid 2.0.0-rc.17 production, development and observe builds. `solid-deps` recognizes validated descriptive development fields directly and discovers unknown or minified graph fields at runtime. Other Solid 2 versions may work if they retain compatible internal graph semantics, but are not yet verified. Your app and `solid-deps` must share one `@solidjs/signals` runtime instance. SSR is not supported.
20
20
 
21
21
  ## Quick start: `createScope()`
22
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-deps",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Experimental direct dependency attachment for Solid 2 reactive observers after async boundaries",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,9 +37,9 @@
37
37
  ],
38
38
  "scripts": {
39
39
  "setup": "node scripts/setup.mjs",
40
- "test": "node --conditions=browser --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs",
41
- "test:dev": "node --conditions=browser --conditions=development --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs",
42
- "test:observe": "node --conditions=browser --conditions=observe --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs",
40
+ "test": "node --conditions=browser --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs test/test-fastpath.mjs test/test-owner-bound-research.mjs",
41
+ "test:dev": "node --conditions=browser --conditions=development --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs test/test-fastpath.mjs test/test-owner-bound-research.mjs",
42
+ "test:observe": "node --conditions=browser --conditions=observe --test test/test.mjs test/test-async.mjs test/test-return.mjs test/test-scope.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 test/test-discovery.mjs test/test-fastpath.mjs test/test-owner-bound-research.mjs",
43
43
  "test:all": "node scripts/run-tests.mjs",
44
44
  "test:package": "node scripts/smoke-package.mjs",
45
45
  "test:browser": "node --test test/browser/playwright.test.mjs",
package/src/discover.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Discover the private linked dependency graph from controlled, disposable
3
- * Solid computations. No minified or development property names are encoded.
3
+ * Solid computations. Only recognizable descriptive development property names
4
+ * are encoded; all unknown layouts use the name-independent inference.
4
5
  *
5
6
  * This is topology inference, not a guarantee of compatibility with a new
6
7
  * algorithm or a property-mangling transform that changes graph semantics.
@@ -11,6 +12,111 @@ import {
11
12
  } from '@solidjs/signals';
12
13
 
13
14
  let inferred;
15
+ const diagnostics = { knownDev: 0, inferred: 0 };
16
+
17
+ /** Internal test-only counters. Not exported from the package entry point. */
18
+ export function _discoveryDiagnostics() {
19
+ return { ...diagnostics, origin: inferred?.origin ?? null };
20
+ }
21
+
22
+ // Development field names are a candidate, never evidence of graph compatibility.
23
+ // Minified field names are deliberately not recorded here.
24
+ const knownDev = Object.freeze({
25
+ deps: '_deps', tail: '_depsTail', gen: '_depGen', flags: '_flags',
26
+ dep: '_dep', sub: '_sub', next: '_nextDep', linkGen: '_gen',
27
+ parent: '_parent', child: '_firstChild', disposal: '_disposal',
28
+ recomputingBit: 4
29
+ });
30
+ const devFields = ['deps', 'tail', 'gen', 'flags', 'parent', 'child', 'disposal'];
31
+
32
+ // Only inspect own *data* properties to avoid executing an arbitrary getter
33
+ // during compatibility detection. A missing data field is not silently read.
34
+ function data(node, key) {
35
+ if (!obj(node)) return { exists: false };
36
+ const descriptor = Object.getOwnPropertyDescriptor(node, key);
37
+ return descriptor && 'value' in descriptor
38
+ ? { exists: true, value: descriptor.value }
39
+ : { exists: false };
40
+ }
41
+
42
+ /**
43
+ * Pure, non-mutating candidate check; the initial observer may have zero deps,
44
+ * or a validated prefix followed by an older-generation stale suffix.
45
+ * `null` means unknown or partially renamed; corrupted complete layouts throw.
46
+ * Kept as an internal module export for the source-tree regression suite.
47
+ */
48
+ export function tryKnownDevLayout(node) {
49
+ if (!obj(node)) return null;
50
+ const present = devFields.filter(role => Object.hasOwn(node, knownDev[role]));
51
+ if (present.length !== devFields.length) return null;
52
+
53
+ const read = (object, key, role) => {
54
+ const slot = data(object, key);
55
+ if (!slot.exists) throw new Error(`solid-deps: invalid known development ${role}; refusing graph mutation`);
56
+ return slot.value;
57
+ };
58
+ const fail = why => { throw new Error(`solid-deps: invalid known development graph (${why}); refusing graph mutation`); };
59
+ const gen = read(node, knownDev.gen, 'generation');
60
+ const flags = read(node, knownDev.flags, 'flags');
61
+ const head = read(node, knownDev.deps, 'head');
62
+ const tail = read(node, knownDev.tail, 'tail');
63
+ const parent = read(node, knownDev.parent, 'parent');
64
+ const child = read(node, knownDev.child, 'child');
65
+ const disposal = read(node, knownDev.disposal, 'disposal');
66
+
67
+ if (!Number.isSafeInteger(gen) || gen < 0 || !Number.isSafeInteger(flags) || flags < 0)
68
+ fail('generation or flags is not a nonnegative integer');
69
+ // Multiple onCleanup() calls create an array of disposal callbacks.
70
+ // Check their data slots without invoking a potentially shadowed .every().
71
+ let validDisposal = disposal == null || typeof disposal === 'function';
72
+ if (Array.isArray(disposal)) {
73
+ validDisposal = disposal.length > 0;
74
+ for (let i = 0; i < disposal.length; i++) {
75
+ const callback = data(disposal, String(i));
76
+ if (!callback.exists || typeof callback.value !== 'function') validDisposal = false;
77
+ }
78
+ }
79
+ if (head != null && !obj(head) || tail != null && !obj(tail) ||
80
+ parent != null && !obj(parent) || child != null && !obj(child) ||
81
+ !validDisposal)
82
+ fail('invalid dependency or lifecycle value');
83
+ if (head == null && tail != null) fail('tail exists without a head');
84
+
85
+ let edge = head;
86
+ let tailReached = tail == null;
87
+ const visited = new Set();
88
+ while (edge != null) {
89
+ if (!obj(edge) || visited.has(edge)) fail('non-link or cyclic dependency chain');
90
+ visited.add(edge);
91
+ const source = read(edge, knownDev.dep, 'link source');
92
+ const subscriber = read(edge, knownDev.sub, 'link subscriber');
93
+ const linkGen = read(edge, knownDev.linkGen, 'link generation');
94
+ const next = read(edge, knownDev.next, 'next link');
95
+ if (!obj(source) || subscriber !== node || !Number.isSafeInteger(linkGen) ||
96
+ linkGen < 0 || linkGen > gen || next != null && !obj(next))
97
+ fail('inconsistent dependency link');
98
+
99
+ // Check both sides of the source subscription list. In rc.17 dev builds,
100
+ // source._subs and edge._nextSub connect each signal back to its readers.
101
+ // This is a read-only check, never an attempt to repair graph structure.
102
+ let sub = read(source, '_subs', 'source subscriber head');
103
+ const subscribers = new Set();
104
+ let found = false;
105
+ while (sub != null) {
106
+ if (!obj(sub) || subscribers.has(sub)) fail('invalid source subscriber list');
107
+ subscribers.add(sub);
108
+ if (sub === edge) found = true;
109
+ sub = read(sub, '_nextSub', 'subscriber next');
110
+ }
111
+ if (!found) fail('source is not subscribed back to dependency link');
112
+
113
+ if (edge === tail) tailReached = true;
114
+ edge = next;
115
+ }
116
+ if (!tailReached) fail('tail not reachable from dependency head');
117
+ return knownDev;
118
+ }
119
+
14
120
  const own = (obj) => obj && typeof obj === 'object'
15
121
  ? Reflect.ownKeys(obj).filter(key => Object.prototype.propertyIsEnumerable.call(obj, key)) : [];
16
122
  const entries = obj => own(obj).map(key => [key, obj[key]]);
@@ -157,12 +263,24 @@ function discover() {
157
263
  }
158
264
  }
159
265
 
160
- export function internalLayout() {
161
- return inferred ??= discover();
266
+ export function internalLayout(node) {
267
+ if (inferred) return inferred.layout;
268
+ // Called with the real target by layoutOf; keep the optional no-argument
269
+ // form for internal source-tree tests and unknown-layout calibration.
270
+ const dev = node == null ? null : tryKnownDevLayout(node);
271
+ if (dev) {
272
+ diagnostics.knownDev++;
273
+ inferred = { layout: dev, origin: 'known-dev' };
274
+ } else {
275
+ const layout = discover();
276
+ diagnostics.inferred++;
277
+ inferred = { layout, origin: 'inferred' };
278
+ }
279
+ return inferred.layout;
162
280
  }
163
281
 
164
282
  export function layoutOf(node) {
165
- const shape = internalLayout();
283
+ const shape = internalLayout(node);
166
284
  if (!obj(node) || typeof node[shape.gen] !== 'number' ||
167
285
  !(shape.deps in node) || !(shape.tail in node) ||
168
286
  typeof node[shape.flags] !== 'number' || !(shape.child in node) ||