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,543 @@
1
+ import { ToolkitError } from '../contracts/ToolkitError.mjs'
2
+
3
+ const MAX_PARAMETER_ITEMS = 100000
4
+ const MAX_PARAMETER_BYTES = 100000000
5
+ const MAX_PARAMETER_DEPTH = 64
6
+ const MAX_PARAMETER_STRING_LENGTH = 10000000
7
+
8
+ const ARRAY_BUFFER_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
9
+ ArrayBuffer.prototype,
10
+ 'byteLength'
11
+ )?.get
12
+ const SHARED_ARRAY_BUFFER_BYTE_LENGTH_GETTER =
13
+ typeof SharedArrayBuffer === 'function'
14
+ ? Object.getOwnPropertyDescriptor(
15
+ SharedArrayBuffer.prototype,
16
+ 'byteLength'
17
+ )?.get
18
+ : null
19
+ const DATA_VIEW_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
20
+ DataView.prototype,
21
+ 'buffer'
22
+ )?.get
23
+ const DATA_VIEW_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
24
+ DataView.prototype,
25
+ 'byteLength'
26
+ )?.get
27
+ const DATA_VIEW_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
28
+ DataView.prototype,
29
+ 'byteOffset'
30
+ )?.get
31
+ const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype)
32
+ const TYPED_ARRAY_BUFFER_GETTER = Object.getOwnPropertyDescriptor(
33
+ TYPED_ARRAY_PROTOTYPE,
34
+ 'buffer'
35
+ )?.get
36
+ const TYPED_ARRAY_BYTE_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
37
+ TYPED_ARRAY_PROTOTYPE,
38
+ 'byteLength'
39
+ )?.get
40
+ const TYPED_ARRAY_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
41
+ TYPED_ARRAY_PROTOTYPE,
42
+ 'byteOffset'
43
+ )?.get
44
+ const TYPED_ARRAY_LENGTH_GETTER = Object.getOwnPropertyDescriptor(
45
+ TYPED_ARRAY_PROTOTYPE,
46
+ 'length'
47
+ )?.get
48
+ const TYPED_ARRAY_TAG_GETTER = Object.getOwnPropertyDescriptor(
49
+ TYPED_ARRAY_PROTOTYPE,
50
+ Symbol.toStringTag
51
+ )?.get
52
+ const MAP_SIZE_GETTER = Object.getOwnPropertyDescriptor(
53
+ Map.prototype,
54
+ 'size'
55
+ )?.get
56
+ const SET_SIZE_GETTER = Object.getOwnPropertyDescriptor(
57
+ Set.prototype,
58
+ 'size'
59
+ )?.get
60
+ const REGEXP_SOURCE_GETTER = Object.getOwnPropertyDescriptor(
61
+ RegExp.prototype,
62
+ 'source'
63
+ )?.get
64
+
65
+ const TYPED_ARRAY_CONSTRUCTORS = new Map([
66
+ ['Int8Array', Int8Array],
67
+ ['Uint8Array', Uint8Array],
68
+ ['Uint8ClampedArray', Uint8ClampedArray],
69
+ ['Int16Array', Int16Array],
70
+ ['Uint16Array', Uint16Array],
71
+ ['Int32Array', Int32Array],
72
+ ['Uint32Array', Uint32Array],
73
+ ['Float32Array', Float32Array],
74
+ ['Float64Array', Float64Array],
75
+ ['BigInt64Array', BigInt64Array],
76
+ ['BigUint64Array', BigUint64Array]
77
+ ])
78
+
79
+ /**
80
+ * Copies bounded simulation parameters without retaining caller-owned memory.
81
+ */
82
+ export class SimulationParameterCloner {
83
+ /**
84
+ * Copies one plain parameter record into isolated clone-safe data.
85
+ * @param {unknown} value Parameter candidate.
86
+ * @returns {object} Isolated parameters.
87
+ */
88
+ static cloneRecord(value) {
89
+ try {
90
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
91
+ throw SimulationParameterCloner.#error(
92
+ 'Simulation parameters must be a plain object.'
93
+ )
94
+ }
95
+ const prototype = Object.getPrototypeOf(value)
96
+ if (prototype !== Object.prototype && prototype !== null) {
97
+ throw SimulationParameterCloner.#error(
98
+ 'Simulation parameters must be a plain object.'
99
+ )
100
+ }
101
+ return SimulationParameterCloner.#clone(
102
+ value,
103
+ { clones: new WeakMap(), items: 0, bytes: 0 },
104
+ 0
105
+ )
106
+ } catch (error) {
107
+ if (SimulationParameterCloner.#isRequestError(error)) throw error
108
+ throw SimulationParameterCloner.#error(
109
+ 'Simulation parameters could not be inspected safely.'
110
+ )
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Copies one supported structured-clone value.
116
+ * @param {unknown} value Value to copy.
117
+ * @param {{ clones: WeakMap<object, any>, items: number, bytes: number }} state Traversal state.
118
+ * @param {number} depth Current container depth.
119
+ * @returns {any} Copied value.
120
+ */
121
+ static #clone(value, state, depth) {
122
+ if (typeof value === 'string') {
123
+ if (value.length > MAX_PARAMETER_STRING_LENGTH) {
124
+ throw SimulationParameterCloner.#error(
125
+ 'Simulation parameter strings are too large.'
126
+ )
127
+ }
128
+ return value
129
+ }
130
+ if (
131
+ value === null ||
132
+ ['undefined', 'boolean', 'number', 'bigint'].includes(typeof value)
133
+ ) {
134
+ return value
135
+ }
136
+ if (typeof value !== 'object') {
137
+ throw SimulationParameterCloner.#error(
138
+ 'Simulation parameters must contain clone-safe data.'
139
+ )
140
+ }
141
+ if (depth > MAX_PARAMETER_DEPTH) {
142
+ throw SimulationParameterCloner.#error(
143
+ 'Simulation parameters are nested too deeply.'
144
+ )
145
+ }
146
+ if (state.clones.has(value)) return state.clones.get(value)
147
+ SimulationParameterCloner.#consumeItems(state, 1)
148
+
149
+ if (ArrayBuffer.isView(value)) {
150
+ return SimulationParameterCloner.#cloneView(value, state)
151
+ }
152
+ const bufferLength = SimulationParameterCloner.#bufferLength(value)
153
+ if (bufferLength !== null) {
154
+ const copied = SimulationParameterCloner.#copyBytes(
155
+ value,
156
+ 0,
157
+ bufferLength,
158
+ state
159
+ ).buffer
160
+ state.clones.set(value, copied)
161
+ return copied
162
+ }
163
+ const date = SimulationParameterCloner.#date(value)
164
+ if (date !== null) {
165
+ state.clones.set(value, date)
166
+ return date
167
+ }
168
+ const regexp = SimulationParameterCloner.#regexp(value)
169
+ if (regexp !== null) {
170
+ state.clones.set(value, regexp)
171
+ return regexp
172
+ }
173
+ const mapSize = SimulationParameterCloner.#collectionSize(
174
+ value,
175
+ MAP_SIZE_GETTER
176
+ )
177
+ if (mapSize !== null) {
178
+ return SimulationParameterCloner.#cloneMap(
179
+ value,
180
+ mapSize,
181
+ state,
182
+ depth
183
+ )
184
+ }
185
+ const setSize = SimulationParameterCloner.#collectionSize(
186
+ value,
187
+ SET_SIZE_GETTER
188
+ )
189
+ if (setSize !== null) {
190
+ return SimulationParameterCloner.#cloneSet(
191
+ value,
192
+ setSize,
193
+ state,
194
+ depth
195
+ )
196
+ }
197
+ return SimulationParameterCloner.#cloneRecordValue(value, state, depth)
198
+ }
199
+
200
+ /**
201
+ * Copies exactly the bytes exposed by one typed-array or DataView view.
202
+ * @param {ArrayBufferView} value View candidate.
203
+ * @param {{ clones: WeakMap<object, any>, items: number, bytes: number }} state Traversal state.
204
+ * @returns {ArrayBufferView} Isolated view.
205
+ */
206
+ static #cloneView(value, state) {
207
+ const dataView = SimulationParameterCloner.#viewInfo(
208
+ value,
209
+ DATA_VIEW_BUFFER_GETTER,
210
+ DATA_VIEW_BYTE_OFFSET_GETTER,
211
+ DATA_VIEW_BYTE_LENGTH_GETTER
212
+ )
213
+ if (dataView) {
214
+ const bytes = SimulationParameterCloner.#copyBytes(
215
+ dataView.buffer,
216
+ dataView.byteOffset,
217
+ dataView.byteLength,
218
+ state
219
+ )
220
+ const clone = new DataView(bytes.buffer)
221
+ state.clones.set(value, clone)
222
+ return clone
223
+ }
224
+
225
+ const typedArray = SimulationParameterCloner.#typedArrayInfo(value)
226
+ if (!typedArray) {
227
+ throw SimulationParameterCloner.#error(
228
+ 'Simulation parameters contain unsupported binary views.'
229
+ )
230
+ }
231
+ const bytes = SimulationParameterCloner.#copyBytes(
232
+ typedArray.buffer,
233
+ typedArray.byteOffset,
234
+ typedArray.byteLength,
235
+ state
236
+ )
237
+ const Constructor = TYPED_ARRAY_CONSTRUCTORS.get(typedArray.tag)
238
+ if (!Constructor) {
239
+ throw SimulationParameterCloner.#error(
240
+ 'Simulation parameters contain unsupported binary views.'
241
+ )
242
+ }
243
+ const clone = new Constructor(bytes.buffer, 0, typedArray.length)
244
+ state.clones.set(value, clone)
245
+ return clone
246
+ }
247
+
248
+ /**
249
+ * Reads DataView internal fields through built-in getters.
250
+ * @param {unknown} value View candidate.
251
+ * @param {Function | undefined} bufferGetter Buffer getter.
252
+ * @param {Function | undefined} offsetGetter Offset getter.
253
+ * @param {Function | undefined} lengthGetter Length getter.
254
+ * @returns {{ buffer: ArrayBufferLike, byteOffset: number, byteLength: number } | null} View fields.
255
+ */
256
+ static #viewInfo(value, bufferGetter, offsetGetter, lengthGetter) {
257
+ if (!bufferGetter || !offsetGetter || !lengthGetter) return null
258
+ try {
259
+ return {
260
+ buffer: bufferGetter.call(value),
261
+ byteOffset: offsetGetter.call(value),
262
+ byteLength: lengthGetter.call(value)
263
+ }
264
+ } catch {
265
+ return null
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Reads typed-array internal fields through built-in getters.
271
+ * @param {unknown} value Typed-array candidate.
272
+ * @returns {{ buffer: ArrayBufferLike, byteOffset: number, byteLength: number, length: number, tag: string } | null} Typed-array fields.
273
+ */
274
+ static #typedArrayInfo(value) {
275
+ const info = SimulationParameterCloner.#viewInfo(
276
+ value,
277
+ TYPED_ARRAY_BUFFER_GETTER,
278
+ TYPED_ARRAY_BYTE_OFFSET_GETTER,
279
+ TYPED_ARRAY_BYTE_LENGTH_GETTER
280
+ )
281
+ if (!info || !TYPED_ARRAY_LENGTH_GETTER || !TYPED_ARRAY_TAG_GETTER) {
282
+ return null
283
+ }
284
+ try {
285
+ return {
286
+ ...info,
287
+ length: TYPED_ARRAY_LENGTH_GETTER.call(value),
288
+ tag: TYPED_ARRAY_TAG_GETTER.call(value)
289
+ }
290
+ } catch {
291
+ return null
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Copies one exact byte range into a new ArrayBuffer-backed array.
297
+ * @param {ArrayBufferLike} buffer Source buffer.
298
+ * @param {number} byteOffset First source byte.
299
+ * @param {number} byteLength Number of source bytes.
300
+ * @param {{ bytes: number }} state Traversal state.
301
+ * @returns {Uint8Array} Isolated bytes.
302
+ */
303
+ static #copyBytes(buffer, byteOffset, byteLength, state) {
304
+ SimulationParameterCloner.#assertByteBudget(state, byteLength)
305
+ const copied = new Uint8Array(buffer, byteOffset, byteLength).slice()
306
+ if (copied.byteLength !== byteLength) {
307
+ throw SimulationParameterCloner.#error(
308
+ 'Simulation parameter buffers could not be copied safely.'
309
+ )
310
+ }
311
+ state.bytes += copied.byteLength
312
+ return copied
313
+ }
314
+
315
+ /**
316
+ * Reads a genuine ArrayBuffer or SharedArrayBuffer byte length.
317
+ * @param {unknown} value Buffer candidate.
318
+ * @returns {number | null} Byte length or null for another object type.
319
+ */
320
+ static #bufferLength(value) {
321
+ for (const getter of [
322
+ ARRAY_BUFFER_BYTE_LENGTH_GETTER,
323
+ SHARED_ARRAY_BUFFER_BYTE_LENGTH_GETTER
324
+ ]) {
325
+ if (!getter) continue
326
+ try {
327
+ return getter.call(value)
328
+ } catch {
329
+ // Try the other supported buffer brand.
330
+ }
331
+ }
332
+ return null
333
+ }
334
+
335
+ /**
336
+ * Copies a genuine Date without reading shadowable properties.
337
+ * @param {unknown} value Date candidate.
338
+ * @returns {Date | null} Copied date or null.
339
+ */
340
+ static #date(value) {
341
+ try {
342
+ return new Date(Date.prototype.getTime.call(value))
343
+ } catch {
344
+ return null
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Copies a genuine RegExp through its internal structured-clone slots.
350
+ * @param {unknown} value RegExp candidate.
351
+ * @returns {RegExp | null} Copied regular expression or null.
352
+ */
353
+ static #regexp(value) {
354
+ if (!REGEXP_SOURCE_GETTER) return null
355
+ try {
356
+ REGEXP_SOURCE_GETTER.call(value)
357
+ return structuredClone(value)
358
+ } catch {
359
+ return null
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Reads a Map or Set size through its brand-checking built-in getter.
365
+ * @param {unknown} value Collection candidate.
366
+ * @param {Function | undefined} getter Native size getter.
367
+ * @returns {number | null} Collection size or null.
368
+ */
369
+ static #collectionSize(value, getter) {
370
+ if (!getter) return null
371
+ try {
372
+ return getter.call(value)
373
+ } catch {
374
+ return null
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Copies Map entries while charging them to the global item budget.
380
+ * @param {Map<unknown, unknown>} value Source map.
381
+ * @param {number} size Map size.
382
+ * @param {{ clones: WeakMap<object, any>, items: number, bytes: number }} state Traversal state.
383
+ * @param {number} depth Current depth.
384
+ * @returns {Map<unknown, unknown>} Copied map.
385
+ */
386
+ static #cloneMap(value, size, state, depth) {
387
+ SimulationParameterCloner.#consumeItems(state, size)
388
+ const clone = new Map()
389
+ state.clones.set(value, clone)
390
+ const entries = Map.prototype.entries.call(value)
391
+ for (const [key, entry] of entries) {
392
+ clone.set(
393
+ SimulationParameterCloner.#clone(key, state, depth + 1),
394
+ SimulationParameterCloner.#clone(entry, state, depth + 1)
395
+ )
396
+ }
397
+ return clone
398
+ }
399
+
400
+ /**
401
+ * Copies Set entries while charging them to the global item budget.
402
+ * @param {Set<unknown>} value Source set.
403
+ * @param {number} size Set size.
404
+ * @param {{ clones: WeakMap<object, any>, items: number, bytes: number }} state Traversal state.
405
+ * @param {number} depth Current depth.
406
+ * @returns {Set<unknown>} Copied set.
407
+ */
408
+ static #cloneSet(value, size, state, depth) {
409
+ SimulationParameterCloner.#consumeItems(state, size)
410
+ const clone = new Set()
411
+ state.clones.set(value, clone)
412
+ const entries = Set.prototype.values.call(value)
413
+ for (const entry of entries) {
414
+ clone.add(SimulationParameterCloner.#clone(entry, state, depth + 1))
415
+ }
416
+ return clone
417
+ }
418
+
419
+ /**
420
+ * Copies an Array or plain accessor-free object.
421
+ * @param {object} value Source record.
422
+ * @param {{ clones: WeakMap<object, any>, items: number, bytes: number }} state Traversal state.
423
+ * @param {number} depth Current depth.
424
+ * @returns {object | unknown[]} Copied record.
425
+ */
426
+ static #cloneRecordValue(value, state, depth) {
427
+ const isArray = Array.isArray(value)
428
+ const prototype = Object.getPrototypeOf(value)
429
+ if (!isArray && prototype !== Object.prototype && prototype !== null) {
430
+ throw SimulationParameterCloner.#error(
431
+ 'Simulation parameters contain unsupported objects.'
432
+ )
433
+ }
434
+ const descriptors = Object.getOwnPropertyDescriptors(value)
435
+ const keys = Reflect.ownKeys(descriptors)
436
+ const length = isArray ? descriptors.length?.value : 0
437
+ if (
438
+ isArray &&
439
+ (!Number.isSafeInteger(length) ||
440
+ length < 0 ||
441
+ length > MAX_PARAMETER_ITEMS)
442
+ ) {
443
+ throw SimulationParameterCloner.#error(
444
+ 'Simulation parameters contain too many values.'
445
+ )
446
+ }
447
+ const dataKeys = keys.filter((key) => !(isArray && key === 'length'))
448
+ SimulationParameterCloner.#consumeItems(state, dataKeys.length)
449
+ const clone = isArray ? new Array(length) : {}
450
+ state.clones.set(value, clone)
451
+
452
+ for (const key of dataKeys) {
453
+ const descriptor = descriptors[key]
454
+ if (
455
+ typeof key !== 'string' ||
456
+ descriptor.enumerable !== true ||
457
+ descriptor.get ||
458
+ descriptor.set
459
+ ) {
460
+ throw SimulationParameterCloner.#error(
461
+ 'Simulation parameters contain accessor-backed data.'
462
+ )
463
+ }
464
+ Object.defineProperty(clone, key, {
465
+ configurable: true,
466
+ enumerable: true,
467
+ value: SimulationParameterCloner.#clone(
468
+ descriptor.value,
469
+ state,
470
+ depth + 1
471
+ ),
472
+ writable: true
473
+ })
474
+ }
475
+ return clone
476
+ }
477
+
478
+ /**
479
+ * Charges values to the one request-global item budget.
480
+ * @param {{ items: number }} state Traversal state.
481
+ * @param {number} count Additional values.
482
+ * @returns {void}
483
+ */
484
+ static #consumeItems(state, count) {
485
+ if (
486
+ !Number.isSafeInteger(count) ||
487
+ count < 0 ||
488
+ state.items + count > MAX_PARAMETER_ITEMS
489
+ ) {
490
+ throw SimulationParameterCloner.#error(
491
+ 'Simulation parameters contain too many values.'
492
+ )
493
+ }
494
+ state.items += count
495
+ }
496
+
497
+ /**
498
+ * Verifies the actual copied byte slice fits the request-global budget.
499
+ * @param {{ bytes: number }} state Traversal state.
500
+ * @param {number} byteLength Proposed copied byte length.
501
+ * @returns {void}
502
+ */
503
+ static #assertByteBudget(state, byteLength) {
504
+ if (
505
+ !Number.isSafeInteger(byteLength) ||
506
+ byteLength < 0 ||
507
+ state.bytes + byteLength > MAX_PARAMETER_BYTES
508
+ ) {
509
+ throw SimulationParameterCloner.#error(
510
+ 'Simulation parameter buffers are too large.'
511
+ )
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Identifies request errors created by this helper without trusting proxies.
517
+ * @param {unknown} error Error candidate.
518
+ * @returns {boolean} Whether this is an internal request error.
519
+ */
520
+ static #isRequestError(error) {
521
+ try {
522
+ return (
523
+ error instanceof ToolkitError &&
524
+ error.code === 'ERR_SIMULATION_REQUEST'
525
+ )
526
+ } catch {
527
+ return false
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Creates one canonical simulation request error.
533
+ * @param {string} message Failure message.
534
+ * @returns {ToolkitError} Typed error.
535
+ */
536
+ static #error(message) {
537
+ return new ToolkitError(message, {
538
+ code: 'ERR_SIMULATION_REQUEST',
539
+ category: 'validation',
540
+ format: 'circuitjson'
541
+ })
542
+ }
543
+ }