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.
- package/NOTICE.md +31 -0
- package/README.md +267 -107
- package/docs/api.md +501 -96
- package/docs/capabilities.md +70 -0
- package/docs/migration/behaviors.md +45 -0
- package/docs/migration/parser.md +60 -0
- package/docs/migration/renderers.md +515 -0
- package/docs/migration/root.md +740 -0
- package/docs/migration.md +120 -0
- package/docs/model-format.md +175 -57
- package/docs/provenance.md +206 -0
- package/docs/release-notes-v1.1.0.md +154 -0
- package/docs/testing.md +117 -7
- package/package.json +31 -5
- package/spec/api-baseline-v1.0.17.json +1 -0
- package/spec/baseline-provenance-v1.0.17.json +7 -0
- package/spec/circuitjson-schema-snapshot.json +321 -0
- package/spec/circuitjson-schema-source.json +28 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +27 -20
- package/src/capabilities.mjs +1 -0
- package/src/core/ArchiveEntryPath.mjs +93 -0
- package/src/core/ArchiveLimits.mjs +31 -0
- package/src/core/ArchiveLimitsValidator.mjs +107 -0
- package/src/core/AsyncInputOwnership.mjs +56 -0
- package/src/core/AttachedValueLimits.mjs +67 -0
- package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
- package/src/core/CircuitJsonDocument.mjs +19 -61
- package/src/core/CircuitJsonElementTypes.mjs +10 -0
- package/src/core/CircuitJsonElementValidator.mjs +98 -847
- package/src/core/CircuitJsonIndexer.mjs +274 -194
- package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
- package/src/core/CircuitJsonParser.mjs +75 -13
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
- package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
- package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
- package/src/core/CircuitJsonUnitParsers.mjs +101 -0
- package/src/core/CircuitJsonUnits.mjs +13 -87
- package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
- package/src/core/CircuitJsonValidationUnits.mjs +6 -0
- package/src/core/ManufacturingService.mjs +323 -0
- package/src/core/Parser.mjs +343 -0
- package/src/core/ParserOptions.mjs +333 -0
- package/src/core/PcbBoundsSelectionModel.mjs +55 -19
- package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
- package/src/core/PcbInteractionIndex.mjs +368 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
- package/src/core/ProjectAsyncInputOwner.mjs +70 -0
- package/src/core/ProjectLoader.mjs +975 -0
- package/src/core/SimulationService.mjs +790 -0
- package/src/core/ToolkitCapabilities.mjs +130 -0
- package/src/core/ZipArchiveInspector.mjs +649 -0
- package/src/core/context/BinaryDataSnapshot.mjs +217 -0
- package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
- package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
- package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
- package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
- package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
- package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
- package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
- package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
- package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
- package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
- package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
- package/src/core/context/PcbSpatialIndex.mjs +701 -0
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
- package/src/core/context/StructuredDataSnapshot.mjs +683 -0
- package/src/core/contracts/DocumentResult.mjs +198 -0
- package/src/core/contracts/ProjectResult.mjs +96 -0
- package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
- package/src/core/contracts/ToolkitAsset.mjs +493 -0
- package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
- package/src/core/contracts/ToolkitError.mjs +176 -0
- package/src/core/contracts/ToolkitProgress.mjs +89 -0
- package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
- package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
- package/src/core/query/CircuitTraversal.mjs +343 -0
- package/src/core/query/ComponentGrouping.mjs +275 -0
- package/src/core/query/QueryNetlistBuilder.mjs +306 -0
- package/src/core/query/QueryService.mjs +435 -0
- package/src/core/query/RegexPattern.mjs +75 -0
- package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
- package/src/core/rendering/CanonicalBomRows.mjs +92 -0
- package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
- package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
- package/src/core/rendering/PcbRenderPlan.mjs +429 -0
- package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
- package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
- package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
- package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
- package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
- package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
- package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
- package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
- package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
- package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
- package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
- package/src/core/scene3d/Scene3dOptions.mjs +363 -0
- package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
- package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
- package/src/core/worker/ParserWorkerClient.mjs +997 -0
- package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
- package/src/core/worker/WorkerRequestData.mjs +623 -0
- package/src/core/worker/WorkerResponseData.mjs +905 -0
- package/src/extensions.mjs +37 -0
- package/src/index.mjs +14 -9
- package/src/interaction.mjs +2 -0
- package/src/manufacturing.mjs +1 -0
- package/src/parser.mjs +12 -2
- package/src/project.mjs +5 -0
- package/src/query.mjs +1 -0
- package/src/renderers.mjs +3 -29
- package/src/scene3d.mjs +3 -0
- package/src/simulation.mjs +1 -0
- package/src/styles/renderers.css +24 -0
- package/src/testing/ToolkitContractFixtures.mjs +124 -0
- package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
- package/src/testing/runToolkitContract.mjs +705 -0
- package/src/testing.mjs +3 -0
- package/src/ui/BomTableRenderer.mjs +304 -0
- package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
- package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
- package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
- package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
- package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
- package/src/ui/PcbSvgRenderer.mjs +41 -0
- package/src/ui/SafeSvgPaint.mjs +26 -0
- package/src/ui/SafeXmlText.mjs +60 -0
- package/src/ui/SchematicGeometryBounds.mjs +540 -0
- package/src/ui/SchematicSvgRenderer.mjs +110 -0
- package/src/ui/SchematicTableGeometry.mjs +319 -0
- package/src/ui/SchematicTextAnchor.mjs +55 -0
- package/src/ui/SchematicTextBounds.mjs +98 -0
- 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
|
+
}
|