circuitjson-toolkit 1.3.0 → 1.4.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 CHANGED
@@ -8,8 +8,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
8
8
 
9
9
  CircuitJSON Toolkit is the dependency-free common runtime for the ECAD toolkit
10
10
  family. It provides the same parser, project, rendering, interaction, query,
11
- manufacturing, simulation, 3D scene, capability, error, and worker contracts
12
- used by `gerber-toolkit`, `altium-toolkit`, and `kicad-toolkit`.
11
+ manufacturing, simulation, 3D scene, self-adjusting computation, capability,
12
+ error, and worker contracts used by `gerber-toolkit`, `altium-toolkit`, and
13
+ `kicad-toolkit`.
13
14
 
14
15
  CircuitJSON is the shared immutable model. Source-format packages keep their
15
16
  native decoders and fidelity data in explicit extension namespaces while
@@ -69,6 +70,19 @@ input. Cross-realm and altered-prototype buffers, typed views, byte limits,
69
70
  defensive copies, and all existing return shapes remain supported.
70
71
  See the [1.3.0 release notes](docs/release-notes-v1.3.0.md).
71
72
 
73
+ Version 1.4.0 adds cooperative structured-clone preparation and a documented
74
+ owned-document construction path for source toolkits. Hosts can yield
75
+ throughout large extension traversal, binary protection, and property locking,
76
+ and format parsers can retain the identity of graphs they just created instead
77
+ of defensively copying them again. The resulting model, extensions,
78
+ parameters, and `DocumentResult` shape are unchanged. See the
79
+ [1.4.0 release notes](docs/release-notes-v1.4.0.md).
80
+
81
+ Version 1.4.1 adds the canonical `SelfAdjustingComputation` runtime with
82
+ dynamic data/control dependencies, changed-root reader lists, stale-trace
83
+ replacement, explicit reclamation, and from-scratch consistency coverage. See
84
+ the [1.4.1 release notes](docs/release-notes-v1.4.1.md).
85
+
72
86
  Before 1.1.0:
73
87
 
74
88
  ```js
@@ -115,6 +129,8 @@ const model = document.model
115
129
  cancellation, and controlled buffer transfer
116
130
  - One-pass ownership for selected source extensions, with a separate 128 MiB /
117
131
  4,000,000-item bound and exact direct/worker result parity
132
+ - Cooperative structured-clone preparation with in-place ordinary records and
133
+ clean alias/cycle-preserving dense-array normalization
118
134
  - Machine-readable capability inventory and packed downstream conformance
119
135
  harness
120
136
  - Explicit `/extensions` surface retaining every previous specialized API
@@ -157,6 +173,43 @@ const components = QueryService.create(context).query({
157
173
  console.log(document.model, svg, hits, components.items)
158
174
  ```
159
175
 
176
+ Browser hosts that receive the exact result of a platform structured clone can
177
+ prepare it cooperatively:
178
+
179
+ ```js
180
+ const context = await CircuitJsonDocumentContext.prepareStructuredCloneAsync(
181
+ message.data,
182
+ {
183
+ indexes: ['elements', 'relations'],
184
+ ownership: 'exclusive',
185
+ yield: () => scheduler.yield()
186
+ }
187
+ )
188
+ ```
189
+
190
+ `ownership: 'exclusive'` is required and transfers the graph destructively.
191
+ The caller must relinquish every alias, including shared-memory writers, until
192
+ the promise settles. A mutation to a node that has not been acquired yet cannot
193
+ be reconstructed or detected later, and rejection may leave part of the graph
194
+ locked. Use `prepareStructuredClone()` for uninterrupted same-thread adoption,
195
+ or `prepare()` for arbitrary caller-owned values.
196
+
197
+ An already prepared `CircuitJsonDocumentContext` is reused immediately and
198
+ does not require an ownership declaration because no graph is transferred.
199
+ Ordinary extension records are adopted in place. Dense arrays are normalized
200
+ into clean arrays while preserving aliases and cycles, preventing unsupported
201
+ hidden properties from carrying mutable state into the context.
202
+
203
+ The model is validated and frozen before extension adoption begins. Dense
204
+ arrays, Map/Set normalization, immutable text accounting, binary copying,
205
+ binary installation, and property locking are divided into bounded slices.
206
+ Individual plain extension records are limited to 16,384 properties on this
207
+ cooperative path. The method returns the same
208
+ `CircuitJsonDocumentContext` shape as the synchronous preparation methods.
209
+ Omit `yield` to use `scheduler.yield()` when available and a zero-delay host
210
+ task otherwise. This entry point accepts only exact platform structured-clone
211
+ results with ordinary enumerable string properties.
212
+
160
213
  `Parser.parse()` returns the exact clone-safe `ecad-toolkit.document.v1`
161
214
  envelope:
162
215
 
@@ -338,6 +391,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
338
391
  - [1.2.0 release notes](docs/release-notes-v1.2.0.md)
339
392
  - [1.2.1 release notes](docs/release-notes-v1.2.1.md)
340
393
  - [1.3.0 release notes](docs/release-notes-v1.3.0.md)
394
+ - [1.4.0 release notes](docs/release-notes-v1.4.0.md)
395
+ - [1.4.1 release notes](docs/release-notes-v1.4.1.md)
341
396
  - [Library scope](spec/library-scope.md)
342
397
 
343
398
  ## Package scope
package/docs/api.md CHANGED
@@ -16,6 +16,38 @@ this document for new code. Thirty-seven previous CircuitJSON-specific classes
16
16
  remain under `circuitjson-toolkit/extensions`; the three documented viewer
17
17
  compatibility classes remain on the root. See [migration.md](migration.md).
18
18
 
19
+ ### `SelfAdjustingComputation`
20
+
21
+ The root API exports the shared synchronous change-propagation runtime used by
22
+ ECAD Forge and reusable toolkit consumers. It records dynamic property reads
23
+ for stable named computations, maintains reverse reader lists, validates only
24
+ the traces reached from explicit changed roots, and replaces stale control-flow
25
+ dependencies after re-execution.
26
+
27
+ ```js
28
+ import { SelfAdjustingComputation } from 'circuitjson-toolkit'
29
+
30
+ const runtime = new SelfAdjustingComputation()
31
+ const results = runtime.propagate(
32
+ { locale: 'en', status: 'ready' },
33
+ [['status']],
34
+ [
35
+ {
36
+ name: 'status-label',
37
+ computation: (state) => state.locale + ':' + state.status
38
+ }
39
+ ]
40
+ )
41
+ ```
42
+
43
+ Computations must be synchronous and treat their tracked input as read-only.
44
+ `forget(name)` reclaims one trace, `clear()` reclaims the graph, and
45
+ `getStatistics()` exposes bounded trace counts. Plain objects and arrays are
46
+ traversed; non-plain objects are atomic identity dependencies unless the
47
+ constructor's `isAtomic(value, path)` option selects an earlier boundary.
48
+ Callers must supply conservative changed paths and test propagated results
49
+ against a fresh execution for the same input.
50
+
19
51
  ## Common conventions
20
52
 
21
53
  ### Document input
@@ -131,9 +163,47 @@ cross-realm, proxy-backed, or prototype-modified input. The general path keeps
131
163
  intrinsic binary slots authoritative and preserves altered-prototype
132
164
  `ArrayBuffer`, `SharedArrayBuffer`, typed-array, and `DataView` values.
133
165
 
166
+ To split model validation and extension sealing across host tasks, use the
167
+ cooperative equivalent:
168
+
169
+ ```js
170
+ const context = await CircuitJsonDocumentContext.prepareStructuredCloneAsync(
171
+ message.data,
172
+ {
173
+ indexes: ['elements', 'relations'],
174
+ ownership: 'exclusive',
175
+ yield: () => scheduler.yield()
176
+ }
177
+ )
178
+ ```
179
+
180
+ `ownership: 'exclusive'` is mandatory. It transfers the exact platform
181
+ structured-clone graph destructively; the caller must relinquish all aliases
182
+ and shared-memory writers until the promise settles. The optional `yield`
183
+ callback is awaited after model validation and repeatedly between bounded
184
+ slices of dense-array traversal, Map/Set normalization, immutable text
185
+ accounting, binary copying and installation, and property locking. Individual
186
+ plain extension records are limited to 16,384 properties on this path.
187
+
188
+ Existing `CircuitJsonDocumentContext` inputs are already immutable and are
189
+ reused immediately without an ownership declaration. During a new transfer,
190
+ ordinary extension records retain their identity. Dense arrays are normalized
191
+ into clean arrays while retaining graph aliases and cycles, so non-clone hidden
192
+ properties cannot leave mutable state in the prepared context.
193
+
194
+ Acquired containers are shape-locked and their descriptors are checked during
195
+ sealing. This is not a transactional snapshot of retained aliases: a caller
196
+ that violates exclusive ownership can change a node before it is acquired, and
197
+ a rejected transfer may leave part of the graph locked. Without an injected
198
+ callback the method uses `scheduler.yield()` when available, then falls back to
199
+ a zero-delay host task. The promise resolves to the same prepared context
200
+ returned by the synchronous methods, including the same requested indexes and
201
+ cache behavior. Use `prepareStructuredClone()` for uninterrupted same-thread
202
+ adoption or `prepare()` for arbitrary caller-owned graphs.
203
+
134
204
  ## Root entrypoint
135
205
 
136
- `circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
206
+ `circuitjson-toolkit` has an exact 18-class root. The 15 canonical classes are:
137
207
 
138
208
  - `Parser`
139
209
  - `ProjectLoader`
@@ -147,6 +217,7 @@ intrinsic binary slots authoritative and preserves altered-prototype
147
217
  - `SimulationService`
148
218
  - `PcbScene3dBuilder`
149
219
  - `PcbScene3dPreparator`
220
+ - `SelfAdjustingComputation`
150
221
  - `ToolkitCapabilities`
151
222
  - `ToolkitError`
152
223
 
@@ -203,6 +274,24 @@ Packed release checks reject any missing or additional root export.
203
274
 
204
275
  Import from the root or `circuitjson-toolkit/parser`.
205
276
 
277
+ ### `DocumentResult.createValidatedOwned(fields, runtime?)`
278
+
279
+ Creates the same validated `ecad-toolkit.document.v1` envelope as
280
+ `DocumentResult.createValidated(fields, runtime?)`, but adopts ordinary model
281
+ and extension graph nodes that the calling toolkit just constructed. The
282
+ model and retained extension nodes keep their identities and are validated and
283
+ deeply frozen in place. The envelope schema, parameters, source-reference
284
+ runtime option, and public return fields are unchanged. Binary payloads still
285
+ pass through the defensive binary-property boundary.
286
+
287
+ This is a destructive ownership transfer for source-toolkit convergence
288
+ builders. Call it only when the complete ordinary graph is newly created,
289
+ mutable, has standard local built-ins, and is no longer shared with code that
290
+ expects to mutate it. Raw parser input, arbitrary caller objects, cross-realm
291
+ values, proxies, and prototype-modified graphs must use
292
+ `DocumentResult.createValidated()` instead. The method is exported from
293
+ `circuitjson-toolkit/parser`, not from the exact root surface.
294
+
206
295
  ### `Parser.parse(input, options?)`
207
296
 
208
297
  Synchronously detects, decodes, validates, and returns one canonical document.
@@ -332,6 +421,34 @@ The class constructor is not a public construction path. It throws before
332
421
  observing caller input; use `prepare()` so every context carries a
333
422
  validation-bound authority that downstream viewers and applications can trust.
334
423
 
424
+ ### `CircuitJsonDocumentContext.prepareStructuredCloneAsync(input, options?)`
425
+
426
+ Cooperatively validates and adopts an exact platform structured-clone result.
427
+ `options.ownership` must be the literal string `exclusive`; it declares a
428
+ destructive transfer and requires the caller to relinquish every alias and
429
+ shared-memory writer until settlement. `options.indexes` accepts the same names
430
+ as `prepare()`. `options.yield` may be an async or synchronous function; it is
431
+ awaited after model validation and between bounded slices of extension
432
+ adoption and sealing. If omitted, the runtime uses `scheduler.yield()` when
433
+ present or a zero-delay host task. The method returns a promise for the same
434
+ immutable context shape. The cooperative path enforces the normal extension
435
+ limits plus a 16,384-property limit on each individual plain record.
436
+
437
+ When `input` is already a `CircuitJsonDocumentContext`, the method performs no
438
+ transfer and reuses it immediately, so `options.ownership` is not required.
439
+ Transferred ordinary records are adopted in place; dense arrays are normalized
440
+ to clean arrays with aliases and cycles preserved.
441
+
442
+ Use this only at a provenance boundary that guarantees standard local
443
+ built-ins and ordinary enumerable string properties. Large strings, binary
444
+ payloads, dense arrays, and Map/Set values are processed across cooperative
445
+ pauses, then acquired containers are progressively locked before the proof and
446
+ envelope are sealed. Mutation before a node is acquired is outside the
447
+ exclusive-transfer contract and cannot be detected reliably; rejection can
448
+ leave a partially locked graph. Use `prepareStructuredClone()` for
449
+ uninterrupted same-thread adoption and `prepare()` for untrusted or otherwise
450
+ arbitrary caller graphs.
451
+
335
452
  ### `context.getIndex(name)` and `context.hasIndex(name)`
336
453
 
337
454
  Access a prepared index or check its presence.
@@ -0,0 +1,84 @@
1
+ # circuitjson-toolkit 1.4.0
2
+
3
+ This minor release removes redundant ownership work when a source toolkit or
4
+ browser worker has already established an exact graph provenance boundary. It
5
+ also lets browser hosts return control between validation and extension
6
+ sealing without changing the canonical result contract.
7
+
8
+ ## Owned document construction
9
+
10
+ - `DocumentResult.createValidatedOwned(fields, runtime?)` creates the same
11
+ validated `ecad-toolkit.document.v1` envelope as `createValidated()`.
12
+ - A source-toolkit convergence builder may transfer a newly constructed,
13
+ standard-built-in graph into the envelope. Ordinary model and extension
14
+ nodes retain their identities and are deeply frozen in place instead of
15
+ being copied into a second full graph.
16
+ - The method is intentionally destructive. It is only safe when the toolkit
17
+ exclusively owns the complete mutable graph and will not mutate it after the
18
+ call. Arbitrary caller values, raw untrusted input, cross-realm objects,
19
+ proxies, and altered prototypes must continue through `createValidated()`.
20
+ - Binary properties retain their defensive boundary and validation. Ownership
21
+ limits, validation proofs, and immutable-envelope guarantees are unchanged.
22
+
23
+ ## Cooperative structured-clone preparation
24
+
25
+ - `CircuitJsonDocumentContext.prepareStructuredCloneAsync(input, options?)`
26
+ accepts the same structured-clone input and `indexes` option as the
27
+ synchronous method, plus the required `ownership: 'exclusive'` declaration
28
+ and an optional `yield` scheduler.
29
+ - This is a destructive transfer. Callers must relinquish every alias and
30
+ shared-memory writer until settlement. Mutation before a node is acquired is
31
+ outside the contract and cannot be detected reliably; rejection can leave a
32
+ partially locked graph.
33
+ - The method validates and deeply freezes the model, then yields between
34
+ bounded slices of dense-array traversal, Map/Set normalization, immutable
35
+ text accounting, binary copying and installation, and property locking
36
+ before sealing the canonical envelope.
37
+ - Acquired containers are shape-locked and their descriptors are checked while
38
+ sealing. Individual plain extension records are capped at 16,384 properties
39
+ so one record cannot create an unbounded cooperative inspection step.
40
+ - Existing immutable contexts are reused without a transfer declaration.
41
+ Transferred ordinary records retain identity; dense arrays are normalized
42
+ into clean arrays while preserving aliases and cycles, preventing unsupported
43
+ hidden properties from leaking mutable state.
44
+ - An injected `yield` function is awaited at each scheduling boundary. Without
45
+ one, the toolkit prefers `scheduler.yield()` and otherwise uses a zero-delay
46
+ host task.
47
+ - The promise resolves to the same immutable `CircuitJsonDocumentContext`
48
+ shape with the same indexes, caches, limits, and validation authority.
49
+
50
+ ## Compatibility
51
+
52
+ - No parser option, package subpath, class, parameter, or return field is
53
+ removed or renamed.
54
+ - `DocumentResult` remains `ecad-toolkit.document.v1`; its `model`, `source`,
55
+ `extensions`, `assets`, `diagnostics`, and `statistics` fields are unchanged.
56
+ - `createValidatedOwned()` and `prepareStructuredCloneAsync()` are additive.
57
+ The defensive and synchronous APIs retain their previous behavior.
58
+
59
+ ## Verification and performance
60
+
61
+ Synthetic regression coverage verifies ordinary-record identity retention,
62
+ alias/cycle-preserving clean arrays, deep freeze, chunked text and binary
63
+ processing, dense-array and Map/Set scheduling,
64
+ cooperative yield ordering, progressive shape/property locking, the exclusive
65
+ ownership contract, and context reuse across document batches. The full
66
+ adversarial suite continues to cover hostile accessors, altered and cross-realm
67
+ built-ins, defensive binary ownership, worker parity, synchronous mutation
68
+ isolation, and bounded extension graphs.
69
+
70
+ On the same browser and machine, the exact large native-PCB deep link that
71
+ previously produced 3.29-second and 2.17-second renderer-main tasks retained
72
+ the same 25,729-element PCB SVG and view box after this release. A final fresh
73
+ open peaked at 17.7 milliseconds on the renderer main thread. A forced reload
74
+ peaked at 404.4 milliseconds, consisting of 196.8 milliseconds of browser
75
+ structured-clone deserialization plus 205.3 milliseconds of browser garbage
76
+ collection; no application JavaScript task approached the previous stalls.
77
+ The largest scheduled parser-worker task in that reload was 5.9 milliseconds.
78
+ A separate 150,000-record exclusive-adoption probe yielded 880 times and
79
+ completed in 487.35 milliseconds, with a 1.22-millisecond p95 slice and a
80
+ 6.94-millisecond maximum outlier. A 32 MiB immutable-text probe completed in
81
+ 8.13 milliseconds across 513 yields; a 32 MiB binary probe completed in 3.40
82
+ milliseconds across 514 yields. These figures describe fixed local workloads
83
+ and are not runtime guarantees; deterministic shape, validation, and ownership
84
+ tests remain the release gates.
@@ -0,0 +1,27 @@
1
+ # circuitjson-toolkit 1.4.1
2
+
3
+ This patch release adds a shared self-adjusting-computation runtime for
4
+ persistent toolkit and application state. It implements dynamic dependency
5
+ tracing and ordered change propagation without coupling the common package to
6
+ DOM, Three.js, or source-format parser state.
7
+
8
+ ## Self-adjusting computation
9
+
10
+ - `SelfAdjustingComputation` is available from the package root and is shared
11
+ by identity through the Gerber, Altium, KiCad, and PCB Scene3D packages.
12
+ - Named synchronous computations record the data and control-flow paths they
13
+ observe. Explicit changed roots start propagation from reverse reader lists.
14
+ - Potentially affected computations compare their previous observations and
15
+ reuse successful results when values, presence, key structure, and selected
16
+ atomic identities remain unchanged.
17
+ - Re-execution replaces the previous trace and its abandoned reader edges.
18
+ `forget()` and `clear()` reclaim trace storage explicitly.
19
+ - Tracked snapshots reject mutation and asynchronous trace escape. Callers can
20
+ choose an atomic boundary for immutable documents and native objects.
21
+
22
+ ## Verification
23
+
24
+ The unit suite covers nested and structural reads, control-flow replacement,
25
+ stale reader removal, atomic document identity, failed and asynchronous
26
+ computations, write rejection, explicit trace reclamation, and equality with a
27
+ fresh runtime after each propagated change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -56,6 +56,8 @@
56
56
  "docs/release-notes-v1.2.0.md",
57
57
  "docs/release-notes-v1.2.1.md",
58
58
  "docs/release-notes-v1.3.0.md",
59
+ "docs/release-notes-v1.4.0.md",
60
+ "docs/release-notes-v1.4.1.md",
59
61
  "docs/testing.md",
60
62
  "spec",
61
63
  "LICENSE",
@@ -211,7 +211,7 @@ export class Parser {
211
211
  normalized.options.retainSource === 'reference'
212
212
  ? { sourceReference: normalized.sourceReference }
213
213
  : {}
214
- return DocumentResult.createValidated(
214
+ return DocumentResult.createValidatedOwned(
215
215
  {
216
216
  fileName: normalized.input.fileName,
217
217
  fileType: 'circuitjson',