circuitjson-toolkit 1.0.17 → 1.1.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.
Files changed (154) hide show
  1. package/NOTICE.md +31 -0
  2. package/README.md +267 -107
  3. package/docs/api.md +501 -96
  4. package/docs/capabilities.md +70 -0
  5. package/docs/migration/behaviors.md +45 -0
  6. package/docs/migration/parser.md +60 -0
  7. package/docs/migration/renderers.md +515 -0
  8. package/docs/migration/root.md +740 -0
  9. package/docs/migration.md +120 -0
  10. package/docs/model-format.md +175 -57
  11. package/docs/provenance.md +206 -0
  12. package/docs/release-notes-v1.1.0.md +154 -0
  13. package/docs/testing.md +117 -7
  14. package/package.json +31 -5
  15. package/spec/api-baseline-v1.0.17.json +1 -0
  16. package/spec/baseline-provenance-v1.0.17.json +7 -0
  17. package/spec/circuitjson-schema-snapshot.json +321 -0
  18. package/spec/circuitjson-schema-source.json +28 -0
  19. package/spec/feature-preservation.json +1 -0
  20. package/spec/library-scope.md +27 -20
  21. package/src/capabilities.mjs +1 -0
  22. package/src/core/ArchiveEntryPath.mjs +93 -0
  23. package/src/core/ArchiveLimits.mjs +31 -0
  24. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  25. package/src/core/AsyncInputOwnership.mjs +56 -0
  26. package/src/core/AttachedValueLimits.mjs +67 -0
  27. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  28. package/src/core/CircuitJsonDocument.mjs +19 -61
  29. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  30. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  31. package/src/core/CircuitJsonIndexer.mjs +274 -194
  32. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  33. package/src/core/CircuitJsonParser.mjs +75 -13
  34. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  35. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  36. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  37. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  38. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  39. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  40. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  41. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  42. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  43. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  44. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  45. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  46. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  47. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  48. package/src/core/CircuitJsonUnits.mjs +13 -87
  49. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  50. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  51. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  52. package/src/core/ManufacturingService.mjs +323 -0
  53. package/src/core/Parser.mjs +343 -0
  54. package/src/core/ParserOptions.mjs +333 -0
  55. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  56. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  57. package/src/core/PcbInteractionIndex.mjs +368 -0
  58. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  59. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  60. package/src/core/ProjectLoader.mjs +975 -0
  61. package/src/core/SimulationService.mjs +790 -0
  62. package/src/core/ToolkitCapabilities.mjs +130 -0
  63. package/src/core/ZipArchiveInspector.mjs +649 -0
  64. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  65. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  66. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  67. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  68. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  69. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  70. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  71. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  72. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  73. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  74. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  75. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  76. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  77. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  78. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  79. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  80. package/src/core/contracts/DocumentResult.mjs +198 -0
  81. package/src/core/contracts/ProjectResult.mjs +96 -0
  82. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  83. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  84. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  85. package/src/core/contracts/ToolkitError.mjs +176 -0
  86. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  87. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  88. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  89. package/src/core/query/CircuitTraversal.mjs +343 -0
  90. package/src/core/query/ComponentGrouping.mjs +275 -0
  91. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  92. package/src/core/query/QueryService.mjs +435 -0
  93. package/src/core/query/RegexPattern.mjs +75 -0
  94. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  95. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  96. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  97. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  98. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  99. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  100. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  101. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  102. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  103. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  104. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  105. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  106. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  107. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  108. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  109. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  110. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  111. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  112. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  113. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  114. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  115. package/src/core/worker/WorkerRequestData.mjs +623 -0
  116. package/src/core/worker/WorkerResponseData.mjs +905 -0
  117. package/src/extensions.mjs +37 -0
  118. package/src/index.mjs +14 -9
  119. package/src/interaction.mjs +2 -0
  120. package/src/manufacturing.mjs +1 -0
  121. package/src/parser.mjs +12 -2
  122. package/src/project.mjs +5 -0
  123. package/src/query.mjs +1 -0
  124. package/src/renderers.mjs +3 -29
  125. package/src/scene3d.mjs +3 -0
  126. package/src/simulation.mjs +1 -0
  127. package/src/styles/renderers.css +24 -0
  128. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  129. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  130. package/src/testing/runToolkitContract.mjs +705 -0
  131. package/src/testing.mjs +3 -0
  132. package/src/ui/BomTableRenderer.mjs +304 -0
  133. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  134. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  135. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  136. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  137. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  138. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  139. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  140. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  141. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  142. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  143. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  144. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  145. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  146. package/src/ui/PcbSvgRenderer.mjs +41 -0
  147. package/src/ui/SafeSvgPaint.mjs +26 -0
  148. package/src/ui/SafeXmlText.mjs +60 -0
  149. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  150. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  151. package/src/ui/SchematicTableGeometry.mjs +319 -0
  152. package/src/ui/SchematicTextAnchor.mjs +55 -0
  153. package/src/ui/SchematicTextBounds.mjs +98 -0
  154. package/src/workers/parser.worker.mjs +59 -0
@@ -0,0 +1,623 @@
1
+ import { CircuitJsonValidationProof } from '../context/CircuitJsonValidationProof.mjs'
2
+ import { CircuitJsonReadOnlyDocument } from '../context/CircuitJsonReadOnlyDocument.mjs'
3
+ import { ProtectedExtensionBinaryBoundary } from '../context/ProtectedExtensionBinaryBoundary.mjs'
4
+ import { RuntimeProxyBoundary } from '../contracts/RuntimeProxyBoundary.mjs'
5
+
6
+ const ARRAY_BUFFER_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
7
+ ArrayBuffer.prototype,
8
+ 'byteLength'
9
+ )?.get
10
+ const ARRAY_BUFFER_RESIZABLE_GETTER = Object.getOwnPropertyDescriptor(
11
+ ArrayBuffer.prototype,
12
+ 'resizable'
13
+ )?.get
14
+ const SHARED_ARRAY_BUFFER_BYTE_LENGTH_GETTER =
15
+ typeof SharedArrayBuffer === 'function'
16
+ ? Object.getOwnPropertyDescriptor(
17
+ SharedArrayBuffer.prototype,
18
+ 'byteLength'
19
+ )?.get
20
+ : null
21
+ const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype)
22
+ const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
23
+ TYPED_ARRAY_PROTOTYPE,
24
+ 'buffer'
25
+ )?.get
26
+ const TYPED_ARRAY_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
27
+ TYPED_ARRAY_PROTOTYPE,
28
+ 'byteLength'
29
+ )?.get
30
+ const TYPED_ARRAY_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
31
+ TYPED_ARRAY_PROTOTYPE,
32
+ 'byteOffset'
33
+ )?.get
34
+ const TYPED_ARRAY_TAG_GETTER = Object.getOwnPropertyDescriptor(
35
+ TYPED_ARRAY_PROTOTYPE,
36
+ Symbol.toStringTag
37
+ )?.get
38
+ const DATA_VIEW_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
39
+ DataView.prototype,
40
+ 'buffer'
41
+ )?.get
42
+ const DATA_VIEW_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
43
+ DataView.prototype,
44
+ 'byteLength'
45
+ )?.get
46
+ const DATA_VIEW_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
47
+ DataView.prototype,
48
+ 'byteOffset'
49
+ )?.get
50
+ const DATA_VIEW_CONSTRUCTOR = DataView
51
+ const UINT8_ARRAY_CONSTRUCTOR = Uint8Array
52
+ const UINT8_ARRAY_SET = Uint8Array.prototype.set
53
+ const MAX_REQUEST_BYTES = 100_000_000
54
+ const MAX_REQUEST_DEPTH = 64
55
+ const MAX_REQUEST_VALUES = 100_000
56
+ const MAX_RESULT_BYTES = 250_000_000
57
+ const MAX_RESULT_VALUES = 2_000_000
58
+ const TYPED_ARRAYS = new Map(
59
+ [
60
+ Int8Array,
61
+ Uint8Array,
62
+ Uint8ClampedArray,
63
+ Int16Array,
64
+ Uint16Array,
65
+ Int32Array,
66
+ Uint32Array,
67
+ Float32Array,
68
+ Float64Array,
69
+ typeof BigInt64Array === 'function' ? BigInt64Array : null,
70
+ typeof BigUint64Array === 'function' ? BigUint64Array : null
71
+ ]
72
+ .filter(Boolean)
73
+ .map((Constructor) => [Constructor.name, Constructor])
74
+ )
75
+
76
+ /**
77
+ * Validates and prepares bounded structured-clone worker request data.
78
+ */
79
+ export class WorkerRequestData {
80
+ /**
81
+ * Creates an accessor-free payload and exact transfer list in one pass.
82
+ * @param {unknown} value Request payload.
83
+ * @param {{ transferInput?: boolean }} [options] Transfer options.
84
+ * @returns {{ value: unknown, transfer: Transferable[] }} Prepared payload.
85
+ */
86
+ static prepare(value, options = {}) {
87
+ return WorkerRequestData.#prepare(value, {
88
+ bytes: MAX_REQUEST_BYTES,
89
+ copyBinary: false,
90
+ output: false,
91
+ strictDescriptors: false,
92
+ transferInput: options.transferInput === true,
93
+ trustProof: false,
94
+ values: MAX_REQUEST_VALUES
95
+ })
96
+ }
97
+
98
+ /**
99
+ * Prepares a bounded worker-owned result and transfers all safe buffers.
100
+ * @param {unknown} value Worker result.
101
+ * @returns {{ value: unknown, transfer: Transferable[] }} Prepared result.
102
+ */
103
+ static prepareResult(value) {
104
+ return WorkerRequestData.#prepare(value, {
105
+ bytes: MAX_RESULT_BYTES,
106
+ copyBinary: false,
107
+ output: true,
108
+ strictDescriptors: false,
109
+ transferInput: true,
110
+ trustProof: true,
111
+ values: MAX_RESULT_VALUES
112
+ })
113
+ }
114
+
115
+ /**
116
+ * Creates an owned accessor-free snapshot of one received worker result.
117
+ * @param {unknown} value Received result candidate.
118
+ * @returns {unknown} Owned clone-safe result data.
119
+ */
120
+ static prepareResponse(value) {
121
+ return WorkerRequestData.#prepare(value, {
122
+ bytes: MAX_RESULT_BYTES,
123
+ copyBinary: true,
124
+ output: true,
125
+ strictDescriptors: true,
126
+ transferInput: false,
127
+ trustProof: false,
128
+ values: MAX_RESULT_VALUES
129
+ }).value
130
+ }
131
+
132
+ /**
133
+ * Runs the shared bounded structured-data preparation pass.
134
+ * @param {unknown} value Data candidate.
135
+ * @param {{ bytes: number, copyBinary: boolean, output: boolean, strictDescriptors: boolean, transferInput: boolean, trustProof: boolean, values: number }} limits Preparation limits.
136
+ * @returns {{ value: unknown, transfer: Transferable[] }} Prepared data.
137
+ */
138
+ static #prepare(value, limits) {
139
+ const state = {
140
+ bytes: 0,
141
+ copyBinary: limits.copyBinary,
142
+ maxBytes: limits.bytes,
143
+ maxValues: limits.values,
144
+ output: limits.output,
145
+ prepared: new WeakMap(),
146
+ strictDescriptors: limits.strictDescriptors,
147
+ transfer: { items: [], seen: new Set() },
148
+ transferInput: limits.transferInput,
149
+ trustProof: limits.trustProof,
150
+ values: 0,
151
+ visiting: new WeakSet()
152
+ }
153
+ return {
154
+ value: WorkerRequestData.#prepareValue(value, state, 0),
155
+ transfer: state.transfer.items
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Rejects accessor-backed, executable, or unbounded posted values.
161
+ * @param {unknown} value Request payload.
162
+ * @returns {void}
163
+ */
164
+ static assertCloneSafe(value) {
165
+ WorkerRequestData.prepare(value)
166
+ }
167
+
168
+ /**
169
+ * Prepares one bounded clone-safe value without invoking caller code.
170
+ * @param {unknown} value Value candidate.
171
+ * @param {Record<string, any>} state Traversal state.
172
+ * @param {number} depth Container depth.
173
+ * @returns {unknown} Prepared value.
174
+ */
175
+ static #prepareValue(value, state, depth) {
176
+ const type = typeof value
177
+ if (
178
+ value === null ||
179
+ ['undefined', 'boolean', 'number', 'bigint'].includes(type)
180
+ ) {
181
+ return value
182
+ }
183
+ if (type === 'string') {
184
+ WorkerRequestData.#reserveBytes(state, value.length * 2)
185
+ return value
186
+ }
187
+ if (type !== 'object') {
188
+ throw new TypeError(
189
+ 'Worker requests may contain only clone-safe data.'
190
+ )
191
+ }
192
+ RuntimeProxyBoundary.assert(value, 'Worker request data')
193
+ if (depth > MAX_REQUEST_DEPTH) {
194
+ throw new TypeError('Worker request data is nested too deeply.')
195
+ }
196
+ if (state.visiting.has(value)) {
197
+ throw new TypeError('Worker request data must not be cyclic.')
198
+ }
199
+ if (state.prepared.has(value)) return state.prepared.get(value)
200
+ WorkerRequestData.#reserveValues(state, 1)
201
+ state.visiting.add(value)
202
+ try {
203
+ const bufferLength = WorkerRequestData.#bufferLength(value)
204
+ if (bufferLength !== null) {
205
+ WorkerRequestData.#reserveBytes(state, bufferLength)
206
+ const isolate =
207
+ state.copyBinary ||
208
+ WorkerRequestData.#isResizableBuffer(value)
209
+ const prepared = isolate
210
+ ? WorkerRequestData.#copyBuffer(value, 0, bufferLength)
211
+ : value
212
+ state.prepared.set(value, prepared)
213
+ if (state.transferInput) {
214
+ WorkerRequestData.#addTransfer(state.transfer, prepared)
215
+ }
216
+ return prepared
217
+ }
218
+ const sharedLength = WorkerRequestData.#sharedBufferLength(value)
219
+ if (sharedLength !== null) {
220
+ WorkerRequestData.#reserveBytes(state, sharedLength)
221
+ const copied = WorkerRequestData.#copyBuffer(
222
+ value,
223
+ 0,
224
+ sharedLength
225
+ )
226
+ state.prepared.set(value, copied)
227
+ if (state.transferInput) {
228
+ WorkerRequestData.#addTransfer(state.transfer, copied)
229
+ }
230
+ return copied
231
+ }
232
+ const view = WorkerRequestData.#view(value)
233
+ if (view) {
234
+ WorkerRequestData.#reserveBytes(state, view.byteLength)
235
+ const prepared = WorkerRequestData.#prepareView(
236
+ value,
237
+ view,
238
+ state
239
+ )
240
+ state.prepared.set(value, prepared)
241
+ return prepared
242
+ }
243
+ return WorkerRequestData.#prepareContainer(value, state, depth)
244
+ } finally {
245
+ state.visiting.delete(value)
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Prepares a dense array or plain object through data descriptors only.
251
+ * @param {object} value Container candidate.
252
+ * @param {Record<string, any>} state Traversal state.
253
+ * @param {number} depth Container depth.
254
+ * @returns {object | any[]} Prepared container.
255
+ */
256
+ static #prepareContainer(value, state, depth) {
257
+ let prototype
258
+ let descriptors
259
+ try {
260
+ prototype = Object.getPrototypeOf(value)
261
+ descriptors = Object.getOwnPropertyDescriptors(value)
262
+ } catch {
263
+ throw new TypeError(
264
+ 'Worker request data could not be inspected safely.'
265
+ )
266
+ }
267
+ if (Array.isArray(value)) {
268
+ return WorkerRequestData.#prepareArray(
269
+ value,
270
+ prototype,
271
+ descriptors,
272
+ state,
273
+ depth
274
+ )
275
+ }
276
+ if (prototype !== Object.prototype && prototype !== null) {
277
+ throw new TypeError(
278
+ 'Worker requests may contain only plain data objects.'
279
+ )
280
+ }
281
+ const keys =
282
+ state.output && !state.strictDescriptors
283
+ ? Reflect.ownKeys(descriptors).filter(
284
+ (key) =>
285
+ typeof key === 'string' && descriptors[key].enumerable
286
+ )
287
+ : Reflect.ownKeys(descriptors)
288
+ const documentResult =
289
+ state.output &&
290
+ state.trustProof &&
291
+ WorkerRequestData.#dataValue(descriptors.schema) ===
292
+ 'ecad-toolkit.document.v1' &&
293
+ CircuitJsonValidationProof.has(value)
294
+ WorkerRequestData.#reserveValues(state, keys.length)
295
+ const prepared = prototype === null ? Object.create(null) : {}
296
+ state.prepared.set(value, prepared)
297
+ for (const key of keys) {
298
+ if (typeof key !== 'string') {
299
+ throw new TypeError('Worker request keys must be strings.')
300
+ }
301
+ WorkerRequestData.#reserveBytes(state, key.length * 2)
302
+ Object.defineProperty(prepared, key, {
303
+ configurable: true,
304
+ enumerable: true,
305
+ value:
306
+ documentResult && key === 'model'
307
+ ? WorkerRequestData.#documentModel(descriptors[key])
308
+ : WorkerRequestData.#descriptorValue(
309
+ descriptors[key],
310
+ state,
311
+ depth
312
+ ),
313
+ writable: true
314
+ })
315
+ }
316
+ return prepared
317
+ }
318
+
319
+ /**
320
+ * Prepares one bounded dense plain array before indexed allocation.
321
+ * @param {any[]} value Array candidate.
322
+ * @param {object | null} prototype Array prototype.
323
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
324
+ * @param {Record<string, any>} state Traversal state.
325
+ * @param {number} depth Container depth.
326
+ * @returns {any[]} Prepared array.
327
+ */
328
+ static #prepareArray(value, prototype, descriptors, state, depth) {
329
+ const keys = Reflect.ownKeys(descriptors)
330
+ const length = WorkerRequestData.#dataValue(descriptors.length)
331
+ const visibleKeys =
332
+ state.output && !state.strictDescriptors
333
+ ? keys.filter(
334
+ (key) =>
335
+ key !== 'length' &&
336
+ typeof key === 'string' &&
337
+ descriptors[key].enumerable
338
+ )
339
+ : keys
340
+ if (
341
+ prototype !== Array.prototype ||
342
+ !Number.isSafeInteger(length) ||
343
+ length < 0 ||
344
+ (state.output && !state.strictDescriptors
345
+ ? visibleKeys.length !== length
346
+ : keys.length !== length + 1)
347
+ ) {
348
+ throw new TypeError(
349
+ 'Worker request arrays must be bounded, dense, and plain.'
350
+ )
351
+ }
352
+ WorkerRequestData.#reserveValues(state, length)
353
+ const prepared = new Array(length)
354
+ state.prepared.set(value, prepared)
355
+ for (let index = 0; index < length; index += 1) {
356
+ prepared[index] = WorkerRequestData.#descriptorValue(
357
+ descriptors[String(index)],
358
+ state,
359
+ depth
360
+ )
361
+ }
362
+ return prepared
363
+ }
364
+
365
+ /**
366
+ * Reads one enumerable data descriptor and prepares its value.
367
+ * @param {PropertyDescriptor | undefined} descriptor Descriptor candidate.
368
+ * @param {Record<string, any>} state Traversal state.
369
+ * @param {number} depth Parent depth.
370
+ * @returns {unknown} Prepared value.
371
+ */
372
+ static #descriptorValue(descriptor, state, depth) {
373
+ if (state.trustProof) {
374
+ const protectedData =
375
+ CircuitJsonReadOnlyDocument.readProtectedAssetData(descriptor)
376
+ if (protectedData.trusted) {
377
+ return WorkerRequestData.#prepareValue(
378
+ protectedData.value,
379
+ state,
380
+ depth + 1
381
+ )
382
+ }
383
+ const protectedExtensionBinary =
384
+ ProtectedExtensionBinaryBoundary.read(descriptor)
385
+ if (protectedExtensionBinary.trusted) {
386
+ return WorkerRequestData.#prepareValue(
387
+ protectedExtensionBinary.value,
388
+ state,
389
+ depth + 1
390
+ )
391
+ }
392
+ }
393
+ if (
394
+ !descriptor ||
395
+ !Object.hasOwn(descriptor, 'value') ||
396
+ descriptor.enumerable !== true
397
+ ) {
398
+ throw new TypeError(
399
+ 'Worker requests may contain only enumerable data properties.'
400
+ )
401
+ }
402
+ return WorkerRequestData.#prepareValue(
403
+ descriptor.value,
404
+ state,
405
+ depth + 1
406
+ )
407
+ }
408
+
409
+ /**
410
+ * Preserves a genuine view type while isolating partial or shared bytes.
411
+ * @param {object} original Original view.
412
+ * @param {{ buffer: ArrayBufferLike, byteOffset: number, byteLength: number, tag: string }} view Intrinsic view fields.
413
+ * @param {Record<string, any>} state Traversal state.
414
+ * @returns {ArrayBufferView} Prepared exact view.
415
+ */
416
+ static #prepareView(original, view, state) {
417
+ const backingLength = WorkerRequestData.#bufferLength(view.buffer)
418
+ const sharedLength = WorkerRequestData.#sharedBufferLength(view.buffer)
419
+ const exactArrayBuffer =
420
+ backingLength !== null &&
421
+ view.byteOffset === 0 &&
422
+ view.byteLength === backingLength
423
+ if (
424
+ exactArrayBuffer &&
425
+ !state.copyBinary &&
426
+ !WorkerRequestData.#isResizableBuffer(view.buffer)
427
+ ) {
428
+ if (state.transferInput) {
429
+ WorkerRequestData.#addTransfer(state.transfer, view.buffer)
430
+ }
431
+ return original
432
+ }
433
+ if (backingLength === null && sharedLength === null) {
434
+ throw new TypeError('Worker request binary view is invalid.')
435
+ }
436
+ const copy = WorkerRequestData.#copyBuffer(
437
+ view.buffer,
438
+ view.byteOffset,
439
+ view.byteLength
440
+ )
441
+ const prepared = WorkerRequestData.#recreateView(view, copy)
442
+ if (state.transferInput) {
443
+ WorkerRequestData.#addTransfer(state.transfer, copy)
444
+ }
445
+ return prepared
446
+ }
447
+
448
+ /**
449
+ * Recreates one intrinsic view on an exact isolated backing buffer.
450
+ * @param {{ byteLength: number, tag: string }} view View fields.
451
+ * @param {ArrayBuffer} buffer Exact copied buffer.
452
+ * @returns {ArrayBufferView} Recreated view.
453
+ */
454
+ static #recreateView(view, buffer) {
455
+ if (view.tag === 'DataView') return new DATA_VIEW_CONSTRUCTOR(buffer)
456
+ const Constructor = TYPED_ARRAYS.get(view.tag)
457
+ if (!Constructor || view.byteLength % Constructor.BYTES_PER_ELEMENT) {
458
+ throw new TypeError('Worker request binary view is unsupported.')
459
+ }
460
+ return new Constructor(
461
+ buffer,
462
+ 0,
463
+ view.byteLength / Constructor.BYTES_PER_ELEMENT
464
+ )
465
+ }
466
+
467
+ /**
468
+ * Reads genuine typed-array or DataView internal slots.
469
+ * @param {unknown} value View candidate.
470
+ * @returns {{ buffer: ArrayBufferLike, byteOffset: number, byteLength: number, tag: string } | null} Intrinsic view fields.
471
+ */
472
+ static #view(value) {
473
+ try {
474
+ if (
475
+ DATA_VIEW_BUFFER_GETTER &&
476
+ DATA_VIEW_BYTE_OFFSET_GETTER &&
477
+ DATA_VIEW_BYTE_LENGTH_GETTER
478
+ ) {
479
+ const buffer = DATA_VIEW_BUFFER_GETTER.call(value)
480
+ return {
481
+ buffer,
482
+ byteOffset: DATA_VIEW_BYTE_OFFSET_GETTER.call(value),
483
+ byteLength: DATA_VIEW_BYTE_LENGTH_GETTER.call(value),
484
+ tag: 'DataView'
485
+ }
486
+ }
487
+ } catch {
488
+ // The typed-array intrinsic check below is independent.
489
+ }
490
+ try {
491
+ const tag = TYPED_ARRAY_TAG_GETTER?.call(value)
492
+ if (!TYPED_ARRAYS.has(tag)) return null
493
+ return {
494
+ buffer: TYPED_ARRAY_BUFFER_GETTER.call(value),
495
+ byteOffset: TYPED_ARRAY_BYTE_OFFSET_GETTER.call(value),
496
+ byteLength: TYPED_ARRAY_BYTE_LENGTH_GETTER.call(value),
497
+ tag
498
+ }
499
+ } catch {
500
+ return null
501
+ }
502
+ }
503
+
504
+ /** @param {unknown} value Candidate. @returns {number | null} Byte length. */
505
+ static #bufferLength(value) {
506
+ if (!ARRAY_BUFFER_BYTE_LENGTH_GETTER) return null
507
+ try {
508
+ return ARRAY_BUFFER_BYTE_LENGTH_GETTER.call(value)
509
+ } catch {
510
+ return null
511
+ }
512
+ }
513
+
514
+ /** @param {unknown} value Candidate. @returns {number | null} Byte length. */
515
+ static #sharedBufferLength(value) {
516
+ if (!SHARED_ARRAY_BUFFER_BYTE_LENGTH_GETTER) return null
517
+ try {
518
+ return SHARED_ARRAY_BUFFER_BYTE_LENGTH_GETTER.call(value)
519
+ } catch {
520
+ return null
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Returns whether a genuine ArrayBuffer can change size after accounting.
526
+ * @param {unknown} value Buffer candidate.
527
+ * @returns {boolean} Whether the buffer is resizable.
528
+ */
529
+ static #isResizableBuffer(value) {
530
+ if (!ARRAY_BUFFER_RESIZABLE_GETTER) return false
531
+ try {
532
+ return ARRAY_BUFFER_RESIZABLE_GETTER.call(value) === true
533
+ } catch {
534
+ return false
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Copies one already-accounted intrinsic byte range into a fixed buffer.
540
+ * The explicit range prevents a growable backing store from widening the
541
+ * copy between its limit check and snapshot.
542
+ * @param {ArrayBufferLike} buffer Source buffer.
543
+ * @param {number} byteOffset Captured byte offset.
544
+ * @param {number} byteLength Captured byte length.
545
+ * @returns {ArrayBuffer} Fixed owned snapshot.
546
+ */
547
+ static #copyBuffer(buffer, byteOffset, byteLength) {
548
+ try {
549
+ const source = new UINT8_ARRAY_CONSTRUCTOR(
550
+ buffer,
551
+ byteOffset,
552
+ byteLength
553
+ )
554
+ const copy = new UINT8_ARRAY_CONSTRUCTOR(byteLength)
555
+ UINT8_ARRAY_SET.call(copy, source)
556
+ return TYPED_ARRAY_BUFFER_GETTER.call(copy)
557
+ } catch {
558
+ throw new TypeError(
559
+ 'Worker request binary data changed while it was copied.'
560
+ )
561
+ }
562
+ }
563
+
564
+ /** @param {PropertyDescriptor | undefined} descriptor Descriptor. @returns {unknown} Data value. */
565
+ static #dataValue(descriptor) {
566
+ return descriptor && Object.hasOwn(descriptor, 'value')
567
+ ? descriptor.value
568
+ : undefined
569
+ }
570
+
571
+ /**
572
+ * Preserves the already validated JSON-only CircuitJSON model by reference.
573
+ * @param {PropertyDescriptor | undefined} descriptor Model descriptor.
574
+ * @returns {object[]} CircuitJSON model.
575
+ */
576
+ static #documentModel(descriptor) {
577
+ if (
578
+ !descriptor ||
579
+ !Object.hasOwn(descriptor, 'value') ||
580
+ descriptor.enumerable !== true ||
581
+ !Array.isArray(descriptor.value)
582
+ ) {
583
+ throw new TypeError('Worker result document model is invalid.')
584
+ }
585
+ return descriptor.value
586
+ }
587
+
588
+ /**
589
+ * Adds one transferable exactly once while preserving encounter order.
590
+ * @param {{ items: Transferable[], seen: Set<object> }} transfer Transfer accumulator.
591
+ * @param {Transferable} value Transferable value.
592
+ * @returns {void}
593
+ */
594
+ static #addTransfer(transfer, value) {
595
+ if (transfer.seen.has(value)) return
596
+ transfer.seen.add(value)
597
+ transfer.items.push(value)
598
+ }
599
+
600
+ /** @param {{ bytes: number }} state State. @param {number} count Bytes. */
601
+ static #reserveBytes(state, count) {
602
+ if (
603
+ !Number.isSafeInteger(count) ||
604
+ count < 0 ||
605
+ state.bytes + count > state.maxBytes
606
+ ) {
607
+ throw new TypeError('Worker request data exceeds its byte limit.')
608
+ }
609
+ state.bytes += count
610
+ }
611
+
612
+ /** @param {{ values: number }} state State. @param {number} count Values. */
613
+ static #reserveValues(state, count) {
614
+ if (
615
+ !Number.isSafeInteger(count) ||
616
+ count < 0 ||
617
+ state.values + count > state.maxValues
618
+ ) {
619
+ throw new TypeError('Worker request data is too large.')
620
+ }
621
+ state.values += count
622
+ }
623
+ }