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.
@@ -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
- const typed = BinaryDataSnapshot.#view(
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
  /**
@@ -120,6 +241,44 @@ export class BinaryDataSnapshot {
120
241
  }
121
242
  }
122
243
 
244
+ /**
245
+ * Copies one bounded byte range into an existing isolated byte array.
246
+ * @param {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number }} range Captured intrinsic range.
247
+ * @param {Uint8Array} target Isolated destination bytes.
248
+ * @param {number} offset Visible-range byte offset.
249
+ * @param {number} count Number of bytes to copy.
250
+ * @returns {void}
251
+ * @internal
252
+ */
253
+ static copyBytesInto(range, target, offset, count) {
254
+ if (
255
+ !(target instanceof Uint8Array) ||
256
+ !Number.isSafeInteger(offset) ||
257
+ !Number.isSafeInteger(count) ||
258
+ offset < 0 ||
259
+ count < 0 ||
260
+ offset + count > range?.byteLength ||
261
+ target.byteLength !== range?.byteLength
262
+ ) {
263
+ throw new TypeError('Invalid binary copy range.')
264
+ }
265
+ try {
266
+ const source = new Uint8Array(
267
+ range.buffer,
268
+ range.byteOffset + offset,
269
+ count
270
+ )
271
+ const destination = new Uint8Array(
272
+ target.buffer,
273
+ target.byteOffset + offset,
274
+ count
275
+ )
276
+ UINT8_ARRAY_SET.call(destination, source)
277
+ } catch {
278
+ throw new TypeError('Binary data changed during capture.')
279
+ }
280
+ }
281
+
123
282
  /**
124
283
  * Copies one binary metadata value while retaining its common view type.
125
284
  * @param {unknown} value Binary metadata.
@@ -133,8 +292,28 @@ export class BinaryDataSnapshot {
133
292
  if (range.kind === 'buffer') return bytes.buffer
134
293
  if (range.kind === 'data-view') return new DataView(bytes.buffer)
135
294
 
136
- const prototype = Object.getPrototypeOf(value)
137
- const Constructor = BinaryDataSnapshot.#typedArrayConstructor(prototype)
295
+ const Constructor = BinaryDataSnapshot.#typedArrayConstructor(value)
296
+ if (!Constructor || Constructor === Uint8Array) return bytes
297
+ const bytesPerElement = Constructor.BYTES_PER_ELEMENT
298
+ if (bytes.byteLength % bytesPerElement !== 0) return bytes
299
+ return new Constructor(bytes.buffer)
300
+ }
301
+
302
+ /**
303
+ * Restores one common binary view type from completely isolated bytes.
304
+ * @param {unknown} value Original binary value used only for view type.
305
+ * @param {{ kind: 'buffer' | 'typed-array' | 'data-view' }} range Captured intrinsic kind.
306
+ * @param {Uint8Array} bytes Completely copied isolated bytes.
307
+ * @returns {ArrayBuffer | Uint8Array | DataView} Isolated binary clone.
308
+ * @internal
309
+ */
310
+ static cloneFromBytes(value, range, bytes) {
311
+ if (!(bytes instanceof Uint8Array)) {
312
+ throw new TypeError('Expected isolated binary bytes.')
313
+ }
314
+ if (range.kind === 'buffer') return bytes.buffer
315
+ if (range.kind === 'data-view') return new DataView(bytes.buffer)
316
+ const Constructor = BinaryDataSnapshot.#typedArrayConstructor(value)
138
317
  if (!Constructor || Constructor === Uint8Array) return bytes
139
318
  const bytesPerElement = Constructor.BYTES_PER_ELEMENT
140
319
  if (bytes.byteLength % bytesPerElement !== 0) return bytes
@@ -150,12 +329,44 @@ export class BinaryDataSnapshot {
150
329
  static #callLength(getter, value) {
151
330
  if (typeof getter !== 'function') return null
152
331
  try {
153
- return getter.call(value)
332
+ return REFLECT_APPLY(getter, value, [])
154
333
  } catch {
155
334
  return null
156
335
  }
157
336
  }
158
337
 
338
+ /**
339
+ * Uses the platform's side-effect-free view brand classifier.
340
+ * @param {unknown} value Candidate value.
341
+ * @returns {boolean} Whether the value has ArrayBuffer view slots.
342
+ */
343
+ static #isView(value) {
344
+ try {
345
+ return REFLECT_APPLY(ARRAY_BUFFER_IS_VIEW, ArrayBuffer, [value])
346
+ } catch {
347
+ return false
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Returns true for common local containers that cannot be binary objects.
353
+ * @param {object} value Candidate object.
354
+ * @param {object | null} prototype Captured prototype.
355
+ * @returns {boolean} Whether intrinsic buffer probing can be skipped.
356
+ */
357
+ static #isKnownNonBinary(value, prototype) {
358
+ return (
359
+ ARRAY_IS_ARRAY(value) ||
360
+ prototype === ARRAY_PROTOTYPE ||
361
+ prototype === OBJECT_PROTOTYPE ||
362
+ prototype === null ||
363
+ prototype === DATE_PROTOTYPE ||
364
+ prototype === REGEXP_PROTOTYPE ||
365
+ prototype === MAP_PROTOTYPE ||
366
+ prototype === SET_PROTOTYPE
367
+ )
368
+ }
369
+
159
370
  /**
160
371
  * Reads captured intrinsic view slots without ordinary property access.
161
372
  * @param {unknown} value View candidate.
@@ -175,9 +386,9 @@ export class BinaryDataSnapshot {
175
386
  }
176
387
  try {
177
388
  return {
178
- buffer: bufferGetter.call(value),
179
- byteOffset: offsetGetter.call(value),
180
- byteLength: lengthGetter.call(value),
389
+ buffer: REFLECT_APPLY(bufferGetter, value, []),
390
+ byteOffset: REFLECT_APPLY(offsetGetter, value, []),
391
+ byteLength: REFLECT_APPLY(lengthGetter, value, []),
181
392
  kind
182
393
  }
183
394
  } catch {
@@ -186,30 +397,18 @@ export class BinaryDataSnapshot {
186
397
  }
187
398
 
188
399
  /**
189
- * Maps a genuine typed-array prototype to its platform constructor.
190
- * @param {object | null} prototype Candidate prototype.
400
+ * Maps a genuine typed array's intrinsic tag to a local constructor.
401
+ * @param {unknown} value Genuine typed-array value.
191
402
  * @returns {Function | null} Matching typed-array constructor.
192
403
  */
193
- static #typedArrayConstructor(prototype) {
194
- const constructors = [
195
- Int8Array,
196
- Uint8Array,
197
- Uint8ClampedArray,
198
- Int16Array,
199
- Uint16Array,
200
- Int32Array,
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
- )
404
+ static #typedArrayConstructor(value) {
405
+ if (typeof TYPED_ARRAY_TAG !== 'function') return null
406
+ try {
407
+ const tag = REFLECT_APPLY(TYPED_ARRAY_TAG, value, [])
408
+ return TYPED_ARRAY_CONSTRUCTORS.get(tag) || null
409
+ } catch {
410
+ return null
411
+ }
213
412
  }
214
413
  }
215
414
 
@@ -74,9 +74,56 @@ 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
+ * Destructively adopts an exclusively transferred structured-clone graph
93
+ * while allowing hosts to service input and paint between bounded slices.
94
+ * @param {unknown} input Structured-cloned document result or existing context.
95
+ * @param {{ indexes?: unknown, ownership: 'exclusive', yield?: () => Promise<void> | void }} [options] Requested indexes, required exclusive ownership authority, and host scheduler.
96
+ * @returns {Promise<CircuitJsonDocumentContext>} Prepared request-scoped context.
97
+ */
98
+ static async prepareStructuredCloneAsync(input, options = {}) {
99
+ if (CircuitJsonDocumentContext.#isContext(input)) {
100
+ return CircuitJsonDocumentContext.#prepare(input, options, true)
101
+ }
102
+ if (options?.ownership !== 'exclusive') {
103
+ throw new TypeError(
104
+ 'Cooperative structured-clone preparation requires exclusive ownership.'
105
+ )
106
+ }
107
+ const context = await CircuitJsonDocumentContext.#fromInputAsync(
108
+ input,
109
+ true,
110
+ options?.yield
111
+ )
112
+ context.#indexes.ensure(options?.indexes || [])
113
+ return context
114
+ }
115
+
116
+ /**
117
+ * Prepares one context with explicit metadata provenance.
118
+ * @param {unknown} input Document result, CircuitJSON model, or context.
119
+ * @param {{ indexes?: unknown }} options Requested context options.
120
+ * @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
121
+ * @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
122
+ */
123
+ static #prepare(input, options, standardBuiltins) {
77
124
  const context = CircuitJsonDocumentContext.#isContext(input)
78
125
  ? input
79
- : CircuitJsonDocumentContext.#fromInput(input)
126
+ : CircuitJsonDocumentContext.#fromInput(input, standardBuiltins)
80
127
  context.#indexes.ensure(options?.indexes || [])
81
128
  return context
82
129
  }
@@ -181,15 +228,50 @@ export class CircuitJsonDocumentContext {
181
228
  /**
182
229
  * Creates one context and establishes a matching immutable model proof.
183
230
  * @param {unknown} input Document result or CircuitJSON model.
231
+ * @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
184
232
  * @returns {CircuitJsonDocumentContext} New context.
185
233
  */
186
- static #fromInput(input) {
234
+ static #fromInput(input, standardBuiltins) {
235
+ const document = CircuitJsonDocumentContext.#normalizeDocument(input)
236
+ const validationPasses = CircuitJsonValidationProof.has(document)
237
+ ? 0
238
+ : 1
239
+ const readonlyDocument = CircuitJsonValidationProof.validateAndAttach(
240
+ document,
241
+ {
242
+ standardBuiltins
243
+ }
244
+ )
245
+ const model = CircuitJsonDocumentContext.#ownData(
246
+ readonlyDocument,
247
+ 'model'
248
+ )
249
+ return new CircuitJsonDocumentContext(
250
+ readonlyDocument,
251
+ model,
252
+ validationPasses,
253
+ CONTEXT_CONSTRUCTION_AUTHORITY
254
+ )
255
+ }
256
+
257
+ /**
258
+ * Creates one context while yielding between model validation and envelope
259
+ * sealing for a proven structured-clone graph.
260
+ * @param {unknown} input Document result or CircuitJSON model.
261
+ * @param {boolean} standardBuiltins Whether metadata built-ins have standard local prototypes.
262
+ * @param {(() => Promise<void> | void) | undefined} yieldControl Host scheduler.
263
+ * @returns {Promise<CircuitJsonDocumentContext>} New context.
264
+ */
265
+ static async #fromInputAsync(input, standardBuiltins, yieldControl) {
187
266
  const document = CircuitJsonDocumentContext.#normalizeDocument(input)
188
267
  const validationPasses = CircuitJsonValidationProof.has(document)
189
268
  ? 0
190
269
  : 1
191
270
  const readonlyDocument =
192
- CircuitJsonValidationProof.validateAndAttach(document)
271
+ await CircuitJsonValidationProof.validateAndAttachAsync(document, {
272
+ standardBuiltins,
273
+ yield: yieldControl
274
+ })
193
275
  const model = CircuitJsonDocumentContext.#ownData(
194
276
  readonlyDocument,
195
277
  'model'