circuitjson-toolkit 1.2.0 → 1.3.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 +25 -6
- package/docs/api.md +28 -5
- package/docs/model-format.md +12 -8
- package/docs/release-notes-v1.2.1.md +26 -0
- package/docs/release-notes-v1.3.0.md +45 -0
- package/docs/testing.md +14 -0
- package/package.json +4 -2
- package/src/core/context/BinaryDataSnapshot.mjs +183 -43
- package/src/core/context/CircuitJsonDocumentContext.mjs +31 -4
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +22 -9
- package/src/core/context/CircuitJsonValidationProof.mjs +4 -2
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +2 -2
- package/src/core/context/StructuredDataSnapshot.mjs +11 -6
- package/src/core/worker/WorkerRequestData.mjs +401 -55
- package/src/core/worker/WorkerResultShape.mjs +222 -0
package/README.md
CHANGED
|
@@ -55,6 +55,20 @@ metadata ownership traversals now support deeply nested, valid ECAD documents
|
|
|
55
55
|
up to 256 levels. See the
|
|
56
56
|
[1.2.0 release notes](docs/release-notes-v1.2.0.md).
|
|
57
57
|
|
|
58
|
+
Version 1.2.1 keeps large canonical documents and multi-document projects
|
|
59
|
+
inside explicit worker budgets. Selected extensions may contain up to four
|
|
60
|
+
million structured items, while exact project envelopes receive independent
|
|
61
|
+
per-document, project-metadata, aggregate-byte, and repeated-work limits. See
|
|
62
|
+
the [1.2.1 release notes](docs/release-notes-v1.2.1.md).
|
|
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
|
+
|
|
58
72
|
Before 1.1.0:
|
|
59
73
|
|
|
60
74
|
```js
|
|
@@ -100,7 +114,7 @@ const model = document.model
|
|
|
100
114
|
- Strict `ecad-toolkit.worker.v1` parsing/project protocol with progress,
|
|
101
115
|
cancellation, and controlled buffer transfer
|
|
102
116
|
- One-pass ownership for selected source extensions, with a separate 128 MiB /
|
|
103
|
-
|
|
117
|
+
4,000,000-item bound and exact direct/worker result parity
|
|
104
118
|
- Machine-readable capability inventory and packed downstream conformance
|
|
105
119
|
harness
|
|
106
120
|
- Explicit `/extensions` surface retaining every previous specialized API
|
|
@@ -201,11 +215,14 @@ falls back to direct execution only when worker construction is unavailable;
|
|
|
201
215
|
explicit worker and runtime failures remain visible.
|
|
202
216
|
|
|
203
217
|
Selected source-native extensions are captured once into an immutable owned
|
|
204
|
-
snapshot. Their separate 128 MiB payload and
|
|
205
|
-
realistic renderer/model graphs
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
snapshot. Their separate 128 MiB payload and 4,000,000-item ceilings permit
|
|
219
|
+
realistic renderer/model graphs. Canonical standalone documents remain within
|
|
220
|
+
a 250 MB result ceiling, while exact multi-document project envelopes use a
|
|
221
|
+
256 MiB aggregate ceiling plus independent 250 MB per-document and metadata
|
|
222
|
+
ceilings. Item and repeated-alias work budgets remain separately bounded; an
|
|
223
|
+
over-limit extension or result fails visibly instead of being silently
|
|
224
|
+
truncated. Binary extension values remain byte-backed and return defensive
|
|
225
|
+
copies instead of expanding into JavaScript number arrays.
|
|
209
226
|
When extensions are disabled, native documents and projects return the exact
|
|
210
227
|
empty map `{}`. Bounded ZIP consumers can preflight local/central filenames and
|
|
211
228
|
CRC32/size metadata with
|
|
@@ -319,6 +336,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
|
|
|
319
336
|
- [1.1.1 release notes](docs/release-notes-v1.1.1.md)
|
|
320
337
|
- [1.1.2 release notes](docs/release-notes-v1.1.2.md)
|
|
321
338
|
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
339
|
+
- [1.2.1 release notes](docs/release-notes-v1.2.1.md)
|
|
340
|
+
- [1.3.0 release notes](docs/release-notes-v1.3.0.md)
|
|
322
341
|
- [Library scope](spec/library-scope.md)
|
|
323
342
|
|
|
324
343
|
## Package scope
|
package/docs/api.md
CHANGED
|
@@ -53,12 +53,13 @@ make the common shape smaller.
|
|
|
53
53
|
|
|
54
54
|
Validated source documents own selected extension values as one deeply
|
|
55
55
|
immutable, descriptor-safe snapshot. This uses a distinct 128 MiB payload and
|
|
56
|
-
|
|
56
|
+
4,000,000-item ceiling so large native renderer graphs do not inherit the
|
|
57
57
|
compact metadata limit. The shared worker protocol applies the same ownership
|
|
58
|
-
boundary after structured cloning
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
boundary after structured cloning. Canonical standalone documents retain a
|
|
59
|
+
250 MB byte ceiling; exact multi-document projects use a 256 MiB aggregate
|
|
60
|
+
ceiling with independent 250 MB document and project-metadata limits. Binary
|
|
61
|
+
values preserve their `ArrayBuffer`/typed-view shape behind defensive copy
|
|
62
|
+
access, so payload bytes do not inflate into one JavaScript number per byte.
|
|
62
63
|
|
|
63
64
|
### Errors
|
|
64
65
|
|
|
@@ -108,6 +109,28 @@ Validation, indexes, render primitives, and other derived values are cached by
|
|
|
108
109
|
the context. Public outputs remain detached or immutable; a caller cannot
|
|
109
110
|
mutate later results through a returned object.
|
|
110
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
|
+
|
|
111
134
|
## Root entrypoint
|
|
112
135
|
|
|
113
136
|
`circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
|
package/docs/model-format.md
CHANGED
|
@@ -102,15 +102,19 @@ must not duplicate the CircuitJSON model or keep a renamed renderer model.
|
|
|
102
102
|
Default parsing excludes raw/base64/full-payload graphs.
|
|
103
103
|
|
|
104
104
|
When a caller explicitly selects a native extension, the toolkit captures and
|
|
105
|
-
freezes that graph once under a distinct ceiling of
|
|
105
|
+
freezes that graph once under a distinct ceiling of 4,000,000 structured items
|
|
106
106
|
and 128 MiB of string or binary content. The ceiling is shared across the
|
|
107
|
-
selected namespace
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
selected namespace and applies equally after worker transfer. Canonical
|
|
108
|
+
standalone documents allow the extension plus envelope overhead within five
|
|
109
|
+
million result values and 250 MB. Exact project envelopes allow eight million
|
|
110
|
+
aggregate values and 256 MiB, while each document stays within five million
|
|
111
|
+
values and 250 MB and non-document project metadata stays within two million
|
|
112
|
+
values and 250 MB. Reused graphs are charged independently to each local scope,
|
|
113
|
+
and repeated accounting work is itself bounded by the aggregate project
|
|
114
|
+
budget. Oversized graphs are rejected; they are never truncated or partially
|
|
115
|
+
retained. Binary extension values remain byte-backed with their common
|
|
116
|
+
buffer/view type and are exposed as defensive copies, preserving mutation
|
|
117
|
+
isolation without expanding bytes into plain numeric arrays.
|
|
114
118
|
|
|
115
119
|
Supported pre-union CircuitJSON aliases may be projected through
|
|
116
120
|
`CircuitJsonDocument.normalizeModel()`. The copy-on-write form preserves exact
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# circuitjson-toolkit 1.2.1
|
|
2
|
+
|
|
3
|
+
## Large canonical worker results
|
|
4
|
+
|
|
5
|
+
This patch release keeps valid high-fidelity documents and multi-document
|
|
6
|
+
projects inside explicit, format-neutral worker limits:
|
|
7
|
+
|
|
8
|
+
- Selected native extensions may contain up to 4,000,000 structured items and
|
|
9
|
+
128 MiB of string or binary content.
|
|
10
|
+
- A canonical standalone document may contain up to 5,000,000 result values
|
|
11
|
+
within the existing 250 MB byte ceiling.
|
|
12
|
+
- An exact canonical project may contain up to 8,000,000 aggregate values and
|
|
13
|
+
256 MiB, while every document retains its own 5,000,000-value and 250 MB
|
|
14
|
+
limits.
|
|
15
|
+
- Non-document project metadata retains the generic 2,000,000-value and 250 MB
|
|
16
|
+
limits.
|
|
17
|
+
|
|
18
|
+
Elevated budgets require the complete canonical document or project envelope;
|
|
19
|
+
a schema string alone does not change generic result limits. Reused object
|
|
20
|
+
graphs are charged independently to document and project-metadata scopes in a
|
|
21
|
+
property-order-independent way. Repeated alias-accounting work is also capped
|
|
22
|
+
by the project aggregate budget, keeping transport time bounded.
|
|
23
|
+
|
|
24
|
+
No public class, method, package subpath, parameter, or result field is removed
|
|
25
|
+
or renamed. Oversized results continue to fail visibly rather than being
|
|
26
|
+
truncated.
|
|
@@ -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.
|
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.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"circuitjson",
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"docs/release-notes-v1.1.1.md",
|
|
55
55
|
"docs/release-notes-v1.1.2.md",
|
|
56
56
|
"docs/release-notes-v1.2.0.md",
|
|
57
|
+
"docs/release-notes-v1.2.1.md",
|
|
58
|
+
"docs/release-notes-v1.3.0.md",
|
|
57
59
|
"docs/testing.md",
|
|
58
60
|
"spec",
|
|
59
61
|
"LICENSE",
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
"REUSE.toml"
|
|
67
69
|
],
|
|
68
70
|
"scripts": {
|
|
69
|
-
"test": "node
|
|
71
|
+
"test": "node scripts/run-tests.mjs",
|
|
70
72
|
"format": "prettier --write .",
|
|
71
73
|
"check:format": "prettier --check .",
|
|
72
74
|
"sync:schema": "node scripts/sync-circuit-json-schema.mjs",
|
|
@@ -22,6 +22,10 @@ const TYPED_ARRAY_LENGTH = Object.getOwnPropertyDescriptor(
|
|
|
22
22
|
TYPED_ARRAY_PROTOTYPE,
|
|
23
23
|
'byteLength'
|
|
24
24
|
)?.get
|
|
25
|
+
const TYPED_ARRAY_TAG = Object.getOwnPropertyDescriptor(
|
|
26
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
27
|
+
Symbol.toStringTag
|
|
28
|
+
)?.get
|
|
25
29
|
const DATA_VIEW_BUFFER = Object.getOwnPropertyDescriptor(
|
|
26
30
|
DataView.prototype,
|
|
27
31
|
'buffer'
|
|
@@ -34,7 +38,40 @@ const DATA_VIEW_LENGTH = Object.getOwnPropertyDescriptor(
|
|
|
34
38
|
DataView.prototype,
|
|
35
39
|
'byteLength'
|
|
36
40
|
)?.get
|
|
41
|
+
const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView
|
|
42
|
+
const ARRAY_IS_ARRAY = Array.isArray
|
|
43
|
+
const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf
|
|
44
|
+
const REFLECT_APPLY = Reflect.apply
|
|
45
|
+
const ARRAY_BUFFER_PROTOTYPE = ArrayBuffer.prototype
|
|
46
|
+
const SHARED_ARRAY_BUFFER_PROTOTYPE =
|
|
47
|
+
typeof SharedArrayBuffer === 'function' ? SharedArrayBuffer.prototype : null
|
|
48
|
+
const ARRAY_PROTOTYPE = Array.prototype
|
|
49
|
+
const OBJECT_PROTOTYPE = Object.prototype
|
|
50
|
+
const DATE_PROTOTYPE = Date.prototype
|
|
51
|
+
const REGEXP_PROTOTYPE = RegExp.prototype
|
|
52
|
+
const MAP_PROTOTYPE = Map.prototype
|
|
53
|
+
const SET_PROTOTYPE = Set.prototype
|
|
37
54
|
const UINT8_ARRAY_SET = Uint8Array.prototype.set
|
|
55
|
+
const TYPED_ARRAY_CONSTRUCTORS = new Map([
|
|
56
|
+
['Int8Array', Int8Array],
|
|
57
|
+
['Uint8Array', Uint8Array],
|
|
58
|
+
['Uint8ClampedArray', Uint8ClampedArray],
|
|
59
|
+
['Int16Array', Int16Array],
|
|
60
|
+
['Uint16Array', Uint16Array],
|
|
61
|
+
['Int32Array', Int32Array],
|
|
62
|
+
['Uint32Array', Uint32Array],
|
|
63
|
+
...(typeof globalThis.Float16Array === 'function'
|
|
64
|
+
? [['Float16Array', globalThis.Float16Array]]
|
|
65
|
+
: []),
|
|
66
|
+
['Float32Array', Float32Array],
|
|
67
|
+
['Float64Array', Float64Array],
|
|
68
|
+
...(typeof BigInt64Array === 'function'
|
|
69
|
+
? [
|
|
70
|
+
['BigInt64Array', BigInt64Array],
|
|
71
|
+
['BigUint64Array', BigUint64Array]
|
|
72
|
+
]
|
|
73
|
+
: [])
|
|
74
|
+
])
|
|
38
75
|
|
|
39
76
|
/**
|
|
40
77
|
* Reads and copies binary platform objects through captured intrinsic slots.
|
|
@@ -46,6 +83,104 @@ export class BinaryDataSnapshot {
|
|
|
46
83
|
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
|
|
47
84
|
*/
|
|
48
85
|
static describe(value) {
|
|
86
|
+
return BinaryDataSnapshot.#describe(value, false)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Describes binary data in a graph whose platform built-ins are proven to
|
|
91
|
+
* have their standard local prototypes.
|
|
92
|
+
* @param {unknown} value Binary candidate from a normalized graph.
|
|
93
|
+
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
|
|
94
|
+
*/
|
|
95
|
+
static describeStandard(value) {
|
|
96
|
+
return BinaryDataSnapshot.#describe(value, true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Classifies one binary candidate with either exact or proven-standard
|
|
101
|
+
* raw-buffer handling.
|
|
102
|
+
* @param {unknown} value Binary candidate.
|
|
103
|
+
* @param {boolean} standardBuiltins Whether local prototype identity is proven.
|
|
104
|
+
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
|
|
105
|
+
*/
|
|
106
|
+
static #describe(value, standardBuiltins) {
|
|
107
|
+
if (value === null || typeof value !== 'object') return null
|
|
108
|
+
|
|
109
|
+
if (BinaryDataSnapshot.#isView(value)) {
|
|
110
|
+
const typed = BinaryDataSnapshot.#view(
|
|
111
|
+
value,
|
|
112
|
+
TYPED_ARRAY_BUFFER,
|
|
113
|
+
TYPED_ARRAY_OFFSET,
|
|
114
|
+
TYPED_ARRAY_LENGTH,
|
|
115
|
+
'typed-array'
|
|
116
|
+
)
|
|
117
|
+
if (typed) return typed
|
|
118
|
+
return BinaryDataSnapshot.#view(
|
|
119
|
+
value,
|
|
120
|
+
DATA_VIEW_BUFFER,
|
|
121
|
+
DATA_VIEW_OFFSET,
|
|
122
|
+
DATA_VIEW_LENGTH,
|
|
123
|
+
'data-view'
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!standardBuiltins) {
|
|
128
|
+
return BinaryDataSnapshot.#rawBuffer(value)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let prototype
|
|
132
|
+
try {
|
|
133
|
+
prototype = OBJECT_GET_PROTOTYPE_OF(value)
|
|
134
|
+
} catch {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (prototype === ARRAY_BUFFER_PROTOTYPE) {
|
|
139
|
+
const byteLength = BinaryDataSnapshot.#callLength(
|
|
140
|
+
ARRAY_BUFFER_LENGTH,
|
|
141
|
+
value
|
|
142
|
+
)
|
|
143
|
+
return byteLength === null
|
|
144
|
+
? null
|
|
145
|
+
: {
|
|
146
|
+
buffer: value,
|
|
147
|
+
byteOffset: 0,
|
|
148
|
+
byteLength,
|
|
149
|
+
kind: 'buffer'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
SHARED_ARRAY_BUFFER_PROTOTYPE &&
|
|
154
|
+
prototype === SHARED_ARRAY_BUFFER_PROTOTYPE
|
|
155
|
+
) {
|
|
156
|
+
const byteLength = BinaryDataSnapshot.#callLength(
|
|
157
|
+
SHARED_ARRAY_BUFFER_LENGTH,
|
|
158
|
+
value
|
|
159
|
+
)
|
|
160
|
+
return byteLength === null
|
|
161
|
+
? null
|
|
162
|
+
: {
|
|
163
|
+
buffer: value,
|
|
164
|
+
byteOffset: 0,
|
|
165
|
+
byteLength,
|
|
166
|
+
kind: 'buffer'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (BinaryDataSnapshot.#isKnownNonBinary(value, prototype)) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Unknown prototypes can represent genuine buffers from another realm.
|
|
174
|
+
// Keep intrinsic brand checks as the authority for that uncommon path.
|
|
175
|
+
return BinaryDataSnapshot.#rawBuffer(value)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Applies exact raw ArrayBuffer and SharedArrayBuffer brand getters.
|
|
180
|
+
* @param {object} value Raw-buffer candidate.
|
|
181
|
+
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: 0, byteLength: number, kind: 'buffer' } | null} Raw buffer range.
|
|
182
|
+
*/
|
|
183
|
+
static #rawBuffer(value) {
|
|
49
184
|
const arrayBufferLength = BinaryDataSnapshot.#callLength(
|
|
50
185
|
ARRAY_BUFFER_LENGTH,
|
|
51
186
|
value
|
|
@@ -71,21 +206,7 @@ export class BinaryDataSnapshot {
|
|
|
71
206
|
}
|
|
72
207
|
}
|
|
73
208
|
|
|
74
|
-
|
|
75
|
-
value,
|
|
76
|
-
TYPED_ARRAY_BUFFER,
|
|
77
|
-
TYPED_ARRAY_OFFSET,
|
|
78
|
-
TYPED_ARRAY_LENGTH,
|
|
79
|
-
'typed-array'
|
|
80
|
-
)
|
|
81
|
-
if (typed) return typed
|
|
82
|
-
return BinaryDataSnapshot.#view(
|
|
83
|
-
value,
|
|
84
|
-
DATA_VIEW_BUFFER,
|
|
85
|
-
DATA_VIEW_OFFSET,
|
|
86
|
-
DATA_VIEW_LENGTH,
|
|
87
|
-
'data-view'
|
|
88
|
-
)
|
|
209
|
+
return null
|
|
89
210
|
}
|
|
90
211
|
|
|
91
212
|
/**
|
|
@@ -133,8 +254,7 @@ export class BinaryDataSnapshot {
|
|
|
133
254
|
if (range.kind === 'buffer') return bytes.buffer
|
|
134
255
|
if (range.kind === 'data-view') return new DataView(bytes.buffer)
|
|
135
256
|
|
|
136
|
-
const
|
|
137
|
-
const Constructor = BinaryDataSnapshot.#typedArrayConstructor(prototype)
|
|
257
|
+
const Constructor = BinaryDataSnapshot.#typedArrayConstructor(value)
|
|
138
258
|
if (!Constructor || Constructor === Uint8Array) return bytes
|
|
139
259
|
const bytesPerElement = Constructor.BYTES_PER_ELEMENT
|
|
140
260
|
if (bytes.byteLength % bytesPerElement !== 0) return bytes
|
|
@@ -150,12 +270,44 @@ export class BinaryDataSnapshot {
|
|
|
150
270
|
static #callLength(getter, value) {
|
|
151
271
|
if (typeof getter !== 'function') return null
|
|
152
272
|
try {
|
|
153
|
-
return getter
|
|
273
|
+
return REFLECT_APPLY(getter, value, [])
|
|
154
274
|
} catch {
|
|
155
275
|
return null
|
|
156
276
|
}
|
|
157
277
|
}
|
|
158
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Uses the platform's side-effect-free view brand classifier.
|
|
281
|
+
* @param {unknown} value Candidate value.
|
|
282
|
+
* @returns {boolean} Whether the value has ArrayBuffer view slots.
|
|
283
|
+
*/
|
|
284
|
+
static #isView(value) {
|
|
285
|
+
try {
|
|
286
|
+
return REFLECT_APPLY(ARRAY_BUFFER_IS_VIEW, ArrayBuffer, [value])
|
|
287
|
+
} catch {
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Returns true for common local containers that cannot be binary objects.
|
|
294
|
+
* @param {object} value Candidate object.
|
|
295
|
+
* @param {object | null} prototype Captured prototype.
|
|
296
|
+
* @returns {boolean} Whether intrinsic buffer probing can be skipped.
|
|
297
|
+
*/
|
|
298
|
+
static #isKnownNonBinary(value, prototype) {
|
|
299
|
+
return (
|
|
300
|
+
ARRAY_IS_ARRAY(value) ||
|
|
301
|
+
prototype === ARRAY_PROTOTYPE ||
|
|
302
|
+
prototype === OBJECT_PROTOTYPE ||
|
|
303
|
+
prototype === null ||
|
|
304
|
+
prototype === DATE_PROTOTYPE ||
|
|
305
|
+
prototype === REGEXP_PROTOTYPE ||
|
|
306
|
+
prototype === MAP_PROTOTYPE ||
|
|
307
|
+
prototype === SET_PROTOTYPE
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
159
311
|
/**
|
|
160
312
|
* Reads captured intrinsic view slots without ordinary property access.
|
|
161
313
|
* @param {unknown} value View candidate.
|
|
@@ -175,9 +327,9 @@ export class BinaryDataSnapshot {
|
|
|
175
327
|
}
|
|
176
328
|
try {
|
|
177
329
|
return {
|
|
178
|
-
buffer: bufferGetter
|
|
179
|
-
byteOffset: offsetGetter
|
|
180
|
-
byteLength: lengthGetter
|
|
330
|
+
buffer: REFLECT_APPLY(bufferGetter, value, []),
|
|
331
|
+
byteOffset: REFLECT_APPLY(offsetGetter, value, []),
|
|
332
|
+
byteLength: REFLECT_APPLY(lengthGetter, value, []),
|
|
181
333
|
kind
|
|
182
334
|
}
|
|
183
335
|
} catch {
|
|
@@ -186,30 +338,18 @@ export class BinaryDataSnapshot {
|
|
|
186
338
|
}
|
|
187
339
|
|
|
188
340
|
/**
|
|
189
|
-
* Maps a genuine typed
|
|
190
|
-
* @param {
|
|
341
|
+
* Maps a genuine typed array's intrinsic tag to a local constructor.
|
|
342
|
+
* @param {unknown} value Genuine typed-array value.
|
|
191
343
|
* @returns {Function | null} Matching typed-array constructor.
|
|
192
344
|
*/
|
|
193
|
-
static #typedArrayConstructor(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Uint32Array,
|
|
202
|
-
Float32Array,
|
|
203
|
-
Float64Array,
|
|
204
|
-
...(typeof BigInt64Array === 'function'
|
|
205
|
-
? [BigInt64Array, BigUint64Array]
|
|
206
|
-
: [])
|
|
207
|
-
]
|
|
208
|
-
return (
|
|
209
|
-
constructors.find(
|
|
210
|
-
(Constructor) => Constructor.prototype === prototype
|
|
211
|
-
) || null
|
|
212
|
-
)
|
|
345
|
+
static #typedArrayConstructor(value) {
|
|
346
|
+
if (typeof TYPED_ARRAY_TAG !== 'function') return null
|
|
347
|
+
try {
|
|
348
|
+
const tag = REFLECT_APPLY(TYPED_ARRAY_TAG, value, [])
|
|
349
|
+
return TYPED_ARRAY_CONSTRUCTORS.get(tag) || null
|
|
350
|
+
} catch {
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
213
353
|
}
|
|
214
354
|
}
|
|
215
355
|
|
|
@@ -74,9 +74,31 @@ export class CircuitJsonDocumentContext {
|
|
|
74
74
|
* @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
|
|
75
75
|
*/
|
|
76
76
|
static prepare(input, options = {}) {
|
|
77
|
+
return CircuitJsonDocumentContext.#prepare(input, options, false)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Prepares a document whose platform built-ins were normalized by the
|
|
82
|
+
* structured-clone algorithm.
|
|
83
|
+
* @param {unknown} input Structured-cloned document result or existing context.
|
|
84
|
+
* @param {{ indexes?: unknown }} [options] Requested context options.
|
|
85
|
+
* @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
|
|
86
|
+
*/
|
|
87
|
+
static prepareStructuredClone(input, options = {}) {
|
|
88
|
+
return CircuitJsonDocumentContext.#prepare(input, options, true)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Prepares one context with explicit metadata provenance.
|
|
93
|
+
* @param {unknown} input Document result, CircuitJSON model, or context.
|
|
94
|
+
* @param {{ indexes?: unknown }} options Requested context options.
|
|
95
|
+
* @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
|
|
96
|
+
* @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
|
|
97
|
+
*/
|
|
98
|
+
static #prepare(input, options, standardBuiltins) {
|
|
77
99
|
const context = CircuitJsonDocumentContext.#isContext(input)
|
|
78
100
|
? input
|
|
79
|
-
: CircuitJsonDocumentContext.#fromInput(input)
|
|
101
|
+
: CircuitJsonDocumentContext.#fromInput(input, standardBuiltins)
|
|
80
102
|
context.#indexes.ensure(options?.indexes || [])
|
|
81
103
|
return context
|
|
82
104
|
}
|
|
@@ -181,15 +203,20 @@ export class CircuitJsonDocumentContext {
|
|
|
181
203
|
/**
|
|
182
204
|
* Creates one context and establishes a matching immutable model proof.
|
|
183
205
|
* @param {unknown} input Document result or CircuitJSON model.
|
|
206
|
+
* @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
|
|
184
207
|
* @returns {CircuitJsonDocumentContext} New context.
|
|
185
208
|
*/
|
|
186
|
-
static #fromInput(input) {
|
|
209
|
+
static #fromInput(input, standardBuiltins) {
|
|
187
210
|
const document = CircuitJsonDocumentContext.#normalizeDocument(input)
|
|
188
211
|
const validationPasses = CircuitJsonValidationProof.has(document)
|
|
189
212
|
? 0
|
|
190
213
|
: 1
|
|
191
|
-
const readonlyDocument =
|
|
192
|
-
|
|
214
|
+
const readonlyDocument = CircuitJsonValidationProof.validateAndAttach(
|
|
215
|
+
document,
|
|
216
|
+
{
|
|
217
|
+
standardBuiltins
|
|
218
|
+
}
|
|
219
|
+
)
|
|
193
220
|
const model = CircuitJsonDocumentContext.#ownData(
|
|
194
221
|
readonlyDocument,
|
|
195
222
|
'model'
|