circuitjson-toolkit 1.2.1 → 1.4.0

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
@@ -61,6 +61,22 @@ million structured items, while exact project envelopes receive independent
61
61
  per-document, project-metadata, aggregate-byte, and repeated-work limits. See
62
62
  the [1.2.1 release notes](docs/release-notes-v1.2.1.md).
63
63
 
64
+ Version 1.3.0 adds an explicit fast preparation path for documents received
65
+ through platform structured clone. Large native extension graphs can be owned
66
+ without exception-driven binary probing of every ordinary record, while the
67
+ general preparation path remains prototype-independent for arbitrary caller
68
+ input. Cross-realm and altered-prototype buffers, typed views, byte limits,
69
+ defensive copies, and all existing return shapes remain supported.
70
+ See the [1.3.0 release notes](docs/release-notes-v1.3.0.md).
71
+
72
+ Version 1.4.0 adds cooperative structured-clone preparation and a documented
73
+ owned-document construction path for source toolkits. Hosts can yield
74
+ throughout large extension traversal, binary protection, and property locking,
75
+ and format parsers can retain the identity of graphs they just created instead
76
+ of defensively copying them again. The resulting model, extensions,
77
+ parameters, and `DocumentResult` shape are unchanged. See the
78
+ [1.4.0 release notes](docs/release-notes-v1.4.0.md).
79
+
64
80
  Before 1.1.0:
65
81
 
66
82
  ```js
@@ -107,6 +123,8 @@ const model = document.model
107
123
  cancellation, and controlled buffer transfer
108
124
  - One-pass ownership for selected source extensions, with a separate 128 MiB /
109
125
  4,000,000-item bound and exact direct/worker result parity
126
+ - Cooperative structured-clone preparation with in-place ordinary records and
127
+ clean alias/cycle-preserving dense-array normalization
110
128
  - Machine-readable capability inventory and packed downstream conformance
111
129
  harness
112
130
  - Explicit `/extensions` surface retaining every previous specialized API
@@ -149,6 +167,43 @@ const components = QueryService.create(context).query({
149
167
  console.log(document.model, svg, hits, components.items)
150
168
  ```
151
169
 
170
+ Browser hosts that receive the exact result of a platform structured clone can
171
+ prepare it cooperatively:
172
+
173
+ ```js
174
+ const context = await CircuitJsonDocumentContext.prepareStructuredCloneAsync(
175
+ message.data,
176
+ {
177
+ indexes: ['elements', 'relations'],
178
+ ownership: 'exclusive',
179
+ yield: () => scheduler.yield()
180
+ }
181
+ )
182
+ ```
183
+
184
+ `ownership: 'exclusive'` is required and transfers the graph destructively.
185
+ The caller must relinquish every alias, including shared-memory writers, until
186
+ the promise settles. A mutation to a node that has not been acquired yet cannot
187
+ be reconstructed or detected later, and rejection may leave part of the graph
188
+ locked. Use `prepareStructuredClone()` for uninterrupted same-thread adoption,
189
+ or `prepare()` for arbitrary caller-owned values.
190
+
191
+ An already prepared `CircuitJsonDocumentContext` is reused immediately and
192
+ does not require an ownership declaration because no graph is transferred.
193
+ Ordinary extension records are adopted in place. Dense arrays are normalized
194
+ into clean arrays while preserving aliases and cycles, preventing unsupported
195
+ hidden properties from carrying mutable state into the context.
196
+
197
+ The model is validated and frozen before extension adoption begins. Dense
198
+ arrays, Map/Set normalization, immutable text accounting, binary copying,
199
+ binary installation, and property locking are divided into bounded slices.
200
+ Individual plain extension records are limited to 16,384 properties on this
201
+ cooperative path. The method returns the same
202
+ `CircuitJsonDocumentContext` shape as the synchronous preparation methods.
203
+ Omit `yield` to use `scheduler.yield()` when available and a zero-delay host
204
+ task otherwise. This entry point accepts only exact platform structured-clone
205
+ results with ordinary enumerable string properties.
206
+
152
207
  `Parser.parse()` returns the exact clone-safe `ecad-toolkit.document.v1`
153
208
  envelope:
154
209
 
@@ -329,6 +384,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
329
384
  - [1.1.2 release notes](docs/release-notes-v1.1.2.md)
330
385
  - [1.2.0 release notes](docs/release-notes-v1.2.0.md)
331
386
  - [1.2.1 release notes](docs/release-notes-v1.2.1.md)
387
+ - [1.3.0 release notes](docs/release-notes-v1.3.0.md)
388
+ - [1.4.0 release notes](docs/release-notes-v1.4.0.md)
332
389
  - [Library scope](spec/library-scope.md)
333
390
 
334
391
  ## Package scope
package/docs/api.md CHANGED
@@ -109,6 +109,66 @@ Validation, indexes, render primitives, and other derived values are cached by
109
109
  the context. Public outputs remain detached or immutable; a caller cannot
110
110
  mutate later results through a returned object.
111
111
 
112
+ Hosts that receive a document from the platform structured-clone algorithm may
113
+ use the explicit provenance-aware entry point:
114
+
115
+ ```js
116
+ const context = CircuitJsonDocumentContext.prepareStructuredClone(
117
+ message.data,
118
+ {
119
+ indexes: ['elements', 'relations']
120
+ }
121
+ )
122
+ ```
123
+
124
+ `prepareStructuredClone()` has the same options, return type, validation,
125
+ ownership, limits, and derived-cache behavior as `prepare()`. It avoids
126
+ exception-driven raw-buffer brand checks for ordinary objects by relying on the
127
+ standard local prototypes guaranteed by a completed platform structured clone.
128
+ Use it only for the exact structured-cloned result (or a graph created entirely
129
+ by the toolkit after that boundary). Use `prepare()` for arbitrary caller-owned,
130
+ cross-realm, proxy-backed, or prototype-modified input. The general path keeps
131
+ intrinsic binary slots authoritative and preserves altered-prototype
132
+ `ArrayBuffer`, `SharedArrayBuffer`, typed-array, and `DataView` values.
133
+
134
+ To split model validation and extension sealing across host tasks, use the
135
+ cooperative equivalent:
136
+
137
+ ```js
138
+ const context = await CircuitJsonDocumentContext.prepareStructuredCloneAsync(
139
+ message.data,
140
+ {
141
+ indexes: ['elements', 'relations'],
142
+ ownership: 'exclusive',
143
+ yield: () => scheduler.yield()
144
+ }
145
+ )
146
+ ```
147
+
148
+ `ownership: 'exclusive'` is mandatory. It transfers the exact platform
149
+ structured-clone graph destructively; the caller must relinquish all aliases
150
+ and shared-memory writers until the promise settles. The optional `yield`
151
+ callback is awaited after model validation and repeatedly between bounded
152
+ slices of dense-array traversal, Map/Set normalization, immutable text
153
+ accounting, binary copying and installation, and property locking. Individual
154
+ plain extension records are limited to 16,384 properties on this path.
155
+
156
+ Existing `CircuitJsonDocumentContext` inputs are already immutable and are
157
+ reused immediately without an ownership declaration. During a new transfer,
158
+ ordinary extension records retain their identity. Dense arrays are normalized
159
+ into clean arrays while retaining graph aliases and cycles, so non-clone hidden
160
+ properties cannot leave mutable state in the prepared context.
161
+
162
+ Acquired containers are shape-locked and their descriptors are checked during
163
+ sealing. This is not a transactional snapshot of retained aliases: a caller
164
+ that violates exclusive ownership can change a node before it is acquired, and
165
+ a rejected transfer may leave part of the graph locked. Without an injected
166
+ callback the method uses `scheduler.yield()` when available, then falls back to
167
+ a zero-delay host task. The promise resolves to the same prepared context
168
+ returned by the synchronous methods, including the same requested indexes and
169
+ cache behavior. Use `prepareStructuredClone()` for uninterrupted same-thread
170
+ adoption or `prepare()` for arbitrary caller-owned graphs.
171
+
112
172
  ## Root entrypoint
113
173
 
114
174
  `circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
@@ -181,6 +241,24 @@ Packed release checks reject any missing or additional root export.
181
241
 
182
242
  Import from the root or `circuitjson-toolkit/parser`.
183
243
 
244
+ ### `DocumentResult.createValidatedOwned(fields, runtime?)`
245
+
246
+ Creates the same validated `ecad-toolkit.document.v1` envelope as
247
+ `DocumentResult.createValidated(fields, runtime?)`, but adopts ordinary model
248
+ and extension graph nodes that the calling toolkit just constructed. The
249
+ model and retained extension nodes keep their identities and are validated and
250
+ deeply frozen in place. The envelope schema, parameters, source-reference
251
+ runtime option, and public return fields are unchanged. Binary payloads still
252
+ pass through the defensive binary-property boundary.
253
+
254
+ This is a destructive ownership transfer for source-toolkit convergence
255
+ builders. Call it only when the complete ordinary graph is newly created,
256
+ mutable, has standard local built-ins, and is no longer shared with code that
257
+ expects to mutate it. Raw parser input, arbitrary caller objects, cross-realm
258
+ values, proxies, and prototype-modified graphs must use
259
+ `DocumentResult.createValidated()` instead. The method is exported from
260
+ `circuitjson-toolkit/parser`, not from the exact root surface.
261
+
184
262
  ### `Parser.parse(input, options?)`
185
263
 
186
264
  Synchronously detects, decodes, validates, and returns one canonical document.
@@ -310,6 +388,34 @@ The class constructor is not a public construction path. It throws before
310
388
  observing caller input; use `prepare()` so every context carries a
311
389
  validation-bound authority that downstream viewers and applications can trust.
312
390
 
391
+ ### `CircuitJsonDocumentContext.prepareStructuredCloneAsync(input, options?)`
392
+
393
+ Cooperatively validates and adopts an exact platform structured-clone result.
394
+ `options.ownership` must be the literal string `exclusive`; it declares a
395
+ destructive transfer and requires the caller to relinquish every alias and
396
+ shared-memory writer until settlement. `options.indexes` accepts the same names
397
+ as `prepare()`. `options.yield` may be an async or synchronous function; it is
398
+ awaited after model validation and between bounded slices of extension
399
+ adoption and sealing. If omitted, the runtime uses `scheduler.yield()` when
400
+ present or a zero-delay host task. The method returns a promise for the same
401
+ immutable context shape. The cooperative path enforces the normal extension
402
+ limits plus a 16,384-property limit on each individual plain record.
403
+
404
+ When `input` is already a `CircuitJsonDocumentContext`, the method performs no
405
+ transfer and reuses it immediately, so `options.ownership` is not required.
406
+ Transferred ordinary records are adopted in place; dense arrays are normalized
407
+ to clean arrays with aliases and cycles preserved.
408
+
409
+ Use this only at a provenance boundary that guarantees standard local
410
+ built-ins and ordinary enumerable string properties. Large strings, binary
411
+ payloads, dense arrays, and Map/Set values are processed across cooperative
412
+ pauses, then acquired containers are progressively locked before the proof and
413
+ envelope are sealed. Mutation before a node is acquired is outside the
414
+ exclusive-transfer contract and cannot be detected reliably; rejection can
415
+ leave a partially locked graph. Use `prepareStructuredClone()` for
416
+ uninterrupted same-thread adoption and `prepare()` for untrusted or otherwise
417
+ arbitrary caller graphs.
418
+
313
419
  ### `context.getIndex(name)` and `context.hasIndex(name)`
314
420
 
315
421
  Access a prepared index or check its presence.
@@ -0,0 +1,45 @@
1
+ # circuitjson-toolkit 1.3.0
2
+
3
+ ## Faster canonical extension ownership
4
+
5
+ This minor release adds an explicit fast ownership path for large native
6
+ extension graphs received through the platform structured-clone algorithm.
7
+ Ordinary records, arrays, and standard local data containers are classified
8
+ without deliberately invoking incompatible intrinsic getters, while genuine
9
+ buffers and views still use captured platform slots as the final authority.
10
+
11
+ The new `CircuitJsonDocumentContext.prepareStructuredClone(document, options?)`
12
+ method has the same options and return shape as `prepare()`. It is intended for
13
+ the exact result of a completed platform structured clone, or for a graph
14
+ created entirely by the toolkit after that boundary. Hosts must continue to use
15
+ `prepare()` for arbitrary caller-owned, cross-realm, proxy-backed, or
16
+ prototype-modified input. The optimization is source-format-neutral and
17
+ requires no example-specific handling.
18
+
19
+ Measured on the deterministic standard-built-in metadata workload used during
20
+ development, capturing 50,000 populated records fell from about 3.7 seconds to
21
+ about 0.16 seconds. Browser timing for the combined library and ECAD Forge
22
+ release is documented by the application release because network transfer,
23
+ application interaction preparation, and SVG mounting are outside this
24
+ library's ownership boundary.
25
+
26
+ ## Compatibility and API changes
27
+
28
+ - No public class, package subpath, parameter, or return field is removed or
29
+ renamed.
30
+ - `CircuitJsonDocumentContext.prepareStructuredClone(document, options?)` is a
31
+ new opt-in method for the explicit structured-clone provenance contract.
32
+ - Parser, project, renderer, worker, and extension result shapes are unchanged.
33
+ - The existing `prepare()` path remains exact and prototype-independent.
34
+ Cross-realm and altered-prototype `ArrayBuffer`, `SharedArrayBuffer`, typed
35
+ array, and `DataView` values, resizable buffers, byte ceilings, and
36
+ defensive-copy behavior are retained.
37
+ - Proven standard plain-data graphs no longer generate caught exceptions merely
38
+ to prove that each ordinary node is not binary data.
39
+
40
+ The new regression coverage pauses on every thrown exception in an isolated
41
+ runtime, proving that a representative proven-standard metadata graph completes
42
+ with zero binary-probe exceptions. Exact-path regressions cover altered and
43
+ cross-realm binary objects plus proxy prototype traps. Existing adversarial and
44
+ full-suite contracts continue to cover hostile accessors, worker parity,
45
+ mutation isolation, and bounded extension ownership.
@@ -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.
package/docs/testing.md CHANGED
@@ -21,6 +21,13 @@ checked-in artifacts. Check mode is read-only. It also preserves upstream
21
21
  transform rejection boundaries, including malformed SI-unit strings that the
22
22
  upstream schema rejects by throwing instead of returning a failed parse.
23
23
 
24
+ `npm test` first runs the benchmark contract in an uncontended process, then
25
+ runs the functional test modules with normal Node test concurrency, and finally
26
+ runs the remaining elapsed-time modules in isolated phases. This preserves
27
+ complete test coverage while preventing unrelated CPU-heavy suites from
28
+ contaminating timing comparisons. `npm test -- <node-test-arguments>` still
29
+ forwards focused arguments directly to the Node test runner.
30
+
24
31
  The schema differential suite samples every upstream union leaf, compares
25
32
  required-field failures, exercises transform-owned resistor, capacitor,
26
33
  inductor, current-source, and crystal fields, and verifies that a fresh compile
@@ -92,6 +99,13 @@ round-trip behavior, and visible rejection beyond the separate 128 MiB
92
99
  extension payload ceiling. A 3 MiB binary regression additionally requires
93
100
  byte-backed `Uint8Array` shape, defensive-copy mutation isolation, direct and
94
101
  worker parity, bounded elapsed time, and bounded JavaScript heap growth.
102
+ An isolated inspector regression pauses on all thrown exceptions and requires
103
+ ordinary populated records and arrays in an explicitly proven standard-built-in
104
+ graph to complete ownership without exception-driven binary probing. Separate
105
+ exact-path regressions require cross-realm buffers, altered-prototype
106
+ `ArrayBuffer` and `SharedArrayBuffer` values, altered view prototypes, and
107
+ proxy-backed non-binary candidates to retain the prior prototype-independent
108
+ behavior.
95
109
 
96
110
  Variant-geometry tests also lock rotation-local polygon-plated pad extents and
97
111
  pill drill dimensions, independent outer/drill rotations, and every legal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -55,6 +55,8 @@
55
55
  "docs/release-notes-v1.1.2.md",
56
56
  "docs/release-notes-v1.2.0.md",
57
57
  "docs/release-notes-v1.2.1.md",
58
+ "docs/release-notes-v1.3.0.md",
59
+ "docs/release-notes-v1.4.0.md",
58
60
  "docs/testing.md",
59
61
  "spec",
60
62
  "LICENSE",
@@ -67,7 +69,7 @@
67
69
  "REUSE.toml"
68
70
  ],
69
71
  "scripts": {
70
- "test": "node --test",
72
+ "test": "node scripts/run-tests.mjs",
71
73
  "format": "prettier --write .",
72
74
  "check:format": "prettier --check .",
73
75
  "sync:schema": "node scripts/sync-circuit-json-schema.mjs",
@@ -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',