circuitjson-toolkit 1.2.1 → 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 +9 -0
- package/docs/api.md +22 -0
- package/docs/release-notes-v1.3.0.md +45 -0
- package/docs/testing.md +14 -0
- package/package.json +3 -2
- package/src/core/context/BinaryDataSnapshot.mjs +183 -43
- package/src/core/context/CircuitJsonDocumentContext.mjs +31 -4
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +21 -8
- 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/README.md
CHANGED
|
@@ -61,6 +61,14 @@ 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
|
+
|
|
64
72
|
Before 1.1.0:
|
|
65
73
|
|
|
66
74
|
```js
|
|
@@ -329,6 +337,7 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
|
|
|
329
337
|
- [1.1.2 release notes](docs/release-notes-v1.1.2.md)
|
|
330
338
|
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
331
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)
|
|
332
341
|
- [Library scope](spec/library-scope.md)
|
|
333
342
|
|
|
334
343
|
## Package scope
|
package/docs/api.md
CHANGED
|
@@ -109,6 +109,28 @@ 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
|
+
|
|
112
134
|
## Root entrypoint
|
|
113
135
|
|
|
114
136
|
`circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
|
|
@@ -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",
|
|
@@ -55,6 +55,7 @@
|
|
|
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",
|
|
58
59
|
"docs/testing.md",
|
|
59
60
|
"spec",
|
|
60
61
|
"LICENSE",
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"REUSE.toml"
|
|
68
69
|
],
|
|
69
70
|
"scripts": {
|
|
70
|
-
"test": "node
|
|
71
|
+
"test": "node scripts/run-tests.mjs",
|
|
71
72
|
"format": "prettier --write .",
|
|
72
73
|
"check:format": "prettier --check .",
|
|
73
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'
|
|
@@ -41,9 +41,10 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
41
41
|
* Seals an envelope whose exact model was already deeply frozen by validation.
|
|
42
42
|
* @param {Record<string, any>} document Canonical document envelope.
|
|
43
43
|
* @param {object[]} model Exact deeply frozen model owned by the proof.
|
|
44
|
+
* @param {{ standardBuiltins?: boolean }} [options] Proven metadata provenance.
|
|
44
45
|
* @returns {Record<string, any>} The same read-only envelope.
|
|
45
46
|
*/
|
|
46
|
-
static freezeValidated(document, model) {
|
|
47
|
+
static freezeValidated(document, model, options = {}) {
|
|
47
48
|
if (!CircuitJsonValidationAuthority.permitsSeal(model)) {
|
|
48
49
|
throw new TypeError(
|
|
49
50
|
'Validated document sealing requires an unforgeable validation proof.'
|
|
@@ -76,7 +77,8 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
76
77
|
}
|
|
77
78
|
return CircuitJsonReadOnlyDocument.#freezeDocument(
|
|
78
79
|
document,
|
|
79
|
-
frozenRoots
|
|
80
|
+
frozenRoots,
|
|
81
|
+
options?.standardBuiltins === true
|
|
80
82
|
)
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -84,14 +86,18 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
84
86
|
* Captures owned boundaries and freezes an envelope with known frozen roots.
|
|
85
87
|
* @param {Record<string, any>} document Canonical document envelope.
|
|
86
88
|
* @param {Set<object>} frozenRoots Deeply frozen roots that need no revisit.
|
|
89
|
+
* @param {boolean} [standardBuiltins] Whether extension built-ins have proven standard prototypes.
|
|
87
90
|
* @returns {Record<string, any>} The same read-only envelope.
|
|
88
91
|
*/
|
|
89
|
-
static #freezeDocument(document, frozenRoots) {
|
|
92
|
+
static #freezeDocument(document, frozenRoots, standardBuiltins = false) {
|
|
90
93
|
const assets = CircuitJsonReadOnlyDocument.#documentAssets(document)
|
|
91
94
|
const metadataState = StructuredDataSnapshot.createState()
|
|
92
95
|
CircuitJsonReadOnlyDocument.#captureAssetData(assets, metadataState)
|
|
93
96
|
const extensions =
|
|
94
|
-
CircuitJsonReadOnlyDocument.#captureDocumentExtensions(
|
|
97
|
+
CircuitJsonReadOnlyDocument.#captureDocumentExtensions(
|
|
98
|
+
document,
|
|
99
|
+
standardBuiltins
|
|
100
|
+
)
|
|
95
101
|
if (extensions && typeof extensions === 'object') {
|
|
96
102
|
frozenRoots.add(extensions)
|
|
97
103
|
}
|
|
@@ -199,9 +205,10 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
199
205
|
* transfer-sized item and byte ceilings.
|
|
200
206
|
* @param {unknown} value Extension candidate.
|
|
201
207
|
* @param {((snapshot: unknown) => unknown) | null} [normalize] Optional normalization over the owned mutable snapshot.
|
|
208
|
+
* @param {{ standardBuiltins?: boolean }} [options] Proven source-graph provenance.
|
|
202
209
|
* @returns {unknown} Deeply immutable owned extension metadata.
|
|
203
210
|
*/
|
|
204
|
-
static copyReadonlyExtensionValue(value, normalize = null) {
|
|
211
|
+
static copyReadonlyExtensionValue(value, normalize = null, options = {}) {
|
|
205
212
|
if (
|
|
206
213
|
value &&
|
|
207
214
|
typeof value === 'object' &&
|
|
@@ -216,7 +223,10 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
216
223
|
}
|
|
217
224
|
const snapshot = StructuredDataSnapshot.capture(
|
|
218
225
|
value,
|
|
219
|
-
StructuredDataSnapshot.createState(
|
|
226
|
+
StructuredDataSnapshot.createState({
|
|
227
|
+
...EXTENSION_METADATA_LIMITS,
|
|
228
|
+
standardBuiltins: options?.standardBuiltins === true
|
|
229
|
+
})
|
|
220
230
|
)
|
|
221
231
|
ProtectedExtensionBinaryBoundary.protect(snapshot)
|
|
222
232
|
const normalized = normalize ? normalize(snapshot) : snapshot
|
|
@@ -327,9 +337,10 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
327
337
|
* Captures an unowned worker or caller extension root with extension-sized
|
|
328
338
|
* bounds before the generic source metadata pass begins.
|
|
329
339
|
* @param {Record<string, any>} document Canonical document envelope.
|
|
340
|
+
* @param {boolean} standardBuiltins Whether extension built-ins have proven standard prototypes.
|
|
330
341
|
* @returns {unknown} Owned extension root or undefined.
|
|
331
342
|
*/
|
|
332
|
-
static #captureDocumentExtensions(document) {
|
|
343
|
+
static #captureDocumentExtensions(document, standardBuiltins) {
|
|
333
344
|
let descriptor
|
|
334
345
|
try {
|
|
335
346
|
descriptor = Object.getOwnPropertyDescriptor(document, 'extensions')
|
|
@@ -345,7 +356,9 @@ export class CircuitJsonReadOnlyDocument {
|
|
|
345
356
|
)
|
|
346
357
|
}
|
|
347
358
|
const captured = CircuitJsonReadOnlyDocument.copyReadonlyExtensionValue(
|
|
348
|
-
descriptor.value
|
|
359
|
+
descriptor.value,
|
|
360
|
+
null,
|
|
361
|
+
{ standardBuiltins }
|
|
349
362
|
)
|
|
350
363
|
if (captured === descriptor.value) return captured
|
|
351
364
|
Object.defineProperty(document, 'extensions', {
|
|
@@ -53,9 +53,10 @@ export class CircuitJsonValidationProof {
|
|
|
53
53
|
/**
|
|
54
54
|
* Validates, freezes, and proves one canonical document envelope.
|
|
55
55
|
* @param {Record<string, any>} document Canonical document envelope.
|
|
56
|
+
* @param {{ standardBuiltins?: boolean }} [options] Proven metadata provenance.
|
|
56
57
|
* @returns {Record<string, any>} The same read-only document envelope.
|
|
57
58
|
*/
|
|
58
|
-
static validateAndAttach(document) {
|
|
59
|
+
static validateAndAttach(document, options = {}) {
|
|
59
60
|
const model = CircuitJsonValidationProof.#requireModelData(document)
|
|
60
61
|
if (!CircuitJsonValidationProof.#matches(document, model)) {
|
|
61
62
|
const errors =
|
|
@@ -80,7 +81,8 @@ export class CircuitJsonValidationProof {
|
|
|
80
81
|
}
|
|
81
82
|
const readonlyDocument = CircuitJsonReadOnlyDocument.freezeValidated(
|
|
82
83
|
document,
|
|
83
|
-
model
|
|
84
|
+
model,
|
|
85
|
+
options
|
|
84
86
|
)
|
|
85
87
|
if (
|
|
86
88
|
CircuitJsonValidationProof.#requireModelData(readonlyDocument) !==
|
|
@@ -12,7 +12,7 @@ export class ProtectedExtensionBinaryBoundary {
|
|
|
12
12
|
* @returns {void}
|
|
13
13
|
*/
|
|
14
14
|
static protect(root) {
|
|
15
|
-
if (BinaryDataSnapshot.
|
|
15
|
+
if (BinaryDataSnapshot.describeStandard(root)) {
|
|
16
16
|
throw new TypeError(
|
|
17
17
|
'Canonical extension root must be a plain data container.'
|
|
18
18
|
)
|
|
@@ -51,7 +51,7 @@ export class ProtectedExtensionBinaryBoundary {
|
|
|
51
51
|
stack.push(child)
|
|
52
52
|
continue
|
|
53
53
|
}
|
|
54
|
-
const binary = BinaryDataSnapshot.
|
|
54
|
+
const binary = BinaryDataSnapshot.describeStandard(child)
|
|
55
55
|
if (!binary) continue
|
|
56
56
|
const ownedBinary = child
|
|
57
57
|
/** @returns {ArrayBuffer | Uint8Array | DataView} A defensive binary copy. */
|
|
@@ -26,8 +26,8 @@ const SET_VALUES = Set.prototype.values
|
|
|
26
26
|
export class StructuredDataSnapshot {
|
|
27
27
|
/**
|
|
28
28
|
* Creates state shared by multiple metadata roots in one request.
|
|
29
|
-
* @param {{ label?: string, maxBytes?: number, maxItems?: number, preserveBinary?: boolean }} [limits] Capture limits.
|
|
30
|
-
* @returns {{ seen: Map<object, unknown>, accounted: Set<object>, bytes: number, items: number, label: string, maxBytes: number, maxItems: number, preserveBinary: boolean }} Capture state.
|
|
29
|
+
* @param {{ label?: string, maxBytes?: number, maxItems?: number, preserveBinary?: boolean, standardBuiltins?: boolean }} [limits] Capture limits and proven graph provenance.
|
|
30
|
+
* @returns {{ seen: Map<object, unknown>, accounted: Set<object>, bytes: number, items: number, label: string, maxBytes: number, maxItems: number, preserveBinary: boolean, standardBuiltins: boolean }} Capture state.
|
|
31
31
|
*/
|
|
32
32
|
static createState(limits = {}) {
|
|
33
33
|
const maxBytes =
|
|
@@ -52,7 +52,8 @@ export class StructuredDataSnapshot {
|
|
|
52
52
|
label: String(limits.label || 'Canonical asset source'),
|
|
53
53
|
maxBytes,
|
|
54
54
|
maxItems,
|
|
55
|
-
preserveBinary: limits.preserveBinary === true
|
|
55
|
+
preserveBinary: limits.preserveBinary === true,
|
|
56
|
+
standardBuiltins: limits.standardBuiltins === true
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -71,7 +72,8 @@ export class StructuredDataSnapshot {
|
|
|
71
72
|
!Number.isSafeInteger(state.maxBytes) ||
|
|
72
73
|
!Number.isSafeInteger(state.maxItems) ||
|
|
73
74
|
typeof state.label !== 'string' ||
|
|
74
|
-
typeof state.preserveBinary !== 'boolean'
|
|
75
|
+
typeof state.preserveBinary !== 'boolean' ||
|
|
76
|
+
typeof state.standardBuiltins !== 'boolean'
|
|
75
77
|
) {
|
|
76
78
|
throw new TypeError('Invalid source-metadata capture state.')
|
|
77
79
|
}
|
|
@@ -93,7 +95,8 @@ export class StructuredDataSnapshot {
|
|
|
93
95
|
!Number.isSafeInteger(state.maxBytes) ||
|
|
94
96
|
!Number.isSafeInteger(state.maxItems) ||
|
|
95
97
|
typeof state.label !== 'string' ||
|
|
96
|
-
typeof state.preserveBinary !== 'boolean'
|
|
98
|
+
typeof state.preserveBinary !== 'boolean' ||
|
|
99
|
+
typeof state.standardBuiltins !== 'boolean'
|
|
97
100
|
) {
|
|
98
101
|
throw new TypeError('Invalid source-metadata capture state.')
|
|
99
102
|
}
|
|
@@ -224,7 +227,9 @@ export class StructuredDataSnapshot {
|
|
|
224
227
|
if (state.seen.has(value)) return state.seen.get(value)
|
|
225
228
|
StructuredDataSnapshot.#reserve(state, 1)
|
|
226
229
|
|
|
227
|
-
const binary =
|
|
230
|
+
const binary = state.standardBuiltins
|
|
231
|
+
? BinaryDataSnapshot.describeStandard(value)
|
|
232
|
+
: BinaryDataSnapshot.describe(value)
|
|
228
233
|
if (binary) {
|
|
229
234
|
return StructuredDataSnapshot.#binary(value, binary, state)
|
|
230
235
|
}
|