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 +1 -1
- package/package.json +4 -4
- package/src/discover.mjs +122 -4
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`
|
|
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
|
+
"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.
|
|
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
|
-
|
|
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) ||
|