circuitjson-toolkit 1.0.16 → 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/AGENTS.md +15 -0
- 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,683 @@
|
|
|
1
|
+
import { BinaryDataSnapshot } from './BinaryDataSnapshot.mjs'
|
|
2
|
+
|
|
3
|
+
const METADATA_MAX_DEPTH = 64
|
|
4
|
+
const METADATA_MAX_ITEMS = 100_000
|
|
5
|
+
const UNBOUNDED_METADATA_BYTES = Number.MAX_SAFE_INTEGER
|
|
6
|
+
const DATE_GET_TIME = Date.prototype.getTime
|
|
7
|
+
const DATE_TO_ISO = Date.prototype.toISOString
|
|
8
|
+
const MAP_ENTRIES = Map.prototype.entries
|
|
9
|
+
const MAP_ITERATOR_NEXT = Object.getPrototypeOf(new Map().entries()).next
|
|
10
|
+
const MAP_SIZE = Object.getOwnPropertyDescriptor(Map.prototype, 'size')?.get
|
|
11
|
+
const REGEXP_FLAGS = Object.getOwnPropertyDescriptor(
|
|
12
|
+
RegExp.prototype,
|
|
13
|
+
'flags'
|
|
14
|
+
)?.get
|
|
15
|
+
const REGEXP_SOURCE = Object.getOwnPropertyDescriptor(
|
|
16
|
+
RegExp.prototype,
|
|
17
|
+
'source'
|
|
18
|
+
)?.get
|
|
19
|
+
const SET_ITERATOR_NEXT = Object.getPrototypeOf(new Set().values()).next
|
|
20
|
+
const SET_SIZE = Object.getOwnPropertyDescriptor(Set.prototype, 'size')?.get
|
|
21
|
+
const SET_VALUES = Set.prototype.values
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Atomically validates and owns bounded clone-safe metadata graphs.
|
|
25
|
+
*/
|
|
26
|
+
export class StructuredDataSnapshot {
|
|
27
|
+
/**
|
|
28
|
+
* Creates state shared by multiple metadata roots in one request.
|
|
29
|
+
* @param {{ label?: string, maxBytes?: number, maxItems?: number, preserveBinary?: boolean }} [limits] Capture limits.
|
|
30
|
+
* @returns {{ seen: Map<object, unknown>, accounted: Set<object>, bytes: number, items: number, label: string, maxBytes: number, maxItems: number, preserveBinary: boolean }} Capture state.
|
|
31
|
+
*/
|
|
32
|
+
static createState(limits = {}) {
|
|
33
|
+
const maxBytes =
|
|
34
|
+
limits.maxBytes === undefined
|
|
35
|
+
? UNBOUNDED_METADATA_BYTES
|
|
36
|
+
: limits.maxBytes
|
|
37
|
+
const maxItems =
|
|
38
|
+
limits.maxItems === undefined ? METADATA_MAX_ITEMS : limits.maxItems
|
|
39
|
+
if (
|
|
40
|
+
!Number.isSafeInteger(maxBytes) ||
|
|
41
|
+
maxBytes < 0 ||
|
|
42
|
+
!Number.isSafeInteger(maxItems) ||
|
|
43
|
+
maxItems < 0
|
|
44
|
+
) {
|
|
45
|
+
throw new TypeError('Invalid source-metadata capture limits.')
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
seen: new Map(),
|
|
49
|
+
accounted: new Set(),
|
|
50
|
+
bytes: 0,
|
|
51
|
+
items: 0,
|
|
52
|
+
label: String(limits.label || 'Canonical asset source'),
|
|
53
|
+
maxBytes,
|
|
54
|
+
maxItems,
|
|
55
|
+
preserveBinary: limits.preserveBinary === true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Captures a bounded graph in the same descriptor traversal that validates it.
|
|
61
|
+
* @param {unknown} value Metadata candidate.
|
|
62
|
+
* @param {{ seen: Map<object, unknown>, items: number }} [state] Shared capture state.
|
|
63
|
+
* @returns {unknown} Isolated normalized snapshot.
|
|
64
|
+
*/
|
|
65
|
+
static capture(value, state = StructuredDataSnapshot.createState()) {
|
|
66
|
+
if (
|
|
67
|
+
!(state?.seen instanceof Map) ||
|
|
68
|
+
!(state?.accounted instanceof Set) ||
|
|
69
|
+
!Number.isSafeInteger(state.bytes) ||
|
|
70
|
+
!Number.isSafeInteger(state.items) ||
|
|
71
|
+
!Number.isSafeInteger(state.maxBytes) ||
|
|
72
|
+
!Number.isSafeInteger(state.maxItems) ||
|
|
73
|
+
typeof state.label !== 'string' ||
|
|
74
|
+
typeof state.preserveBinary !== 'boolean'
|
|
75
|
+
) {
|
|
76
|
+
throw new TypeError('Invalid source-metadata capture state.')
|
|
77
|
+
}
|
|
78
|
+
return StructuredDataSnapshot.#capture(value, state, 0)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Accounts an already normalized metadata snapshot without cloning it.
|
|
83
|
+
* @param {unknown} value Owned metadata snapshot.
|
|
84
|
+
* @param {{ seen: Map<object, unknown>, accounted: Set<object>, bytes: number, items: number, label: string, maxBytes: number, maxItems: number }} state Shared capture state.
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
static account(value, state) {
|
|
88
|
+
if (
|
|
89
|
+
!(state?.seen instanceof Map) ||
|
|
90
|
+
!(state?.accounted instanceof Set) ||
|
|
91
|
+
!Number.isSafeInteger(state.bytes) ||
|
|
92
|
+
!Number.isSafeInteger(state.items) ||
|
|
93
|
+
!Number.isSafeInteger(state.maxBytes) ||
|
|
94
|
+
!Number.isSafeInteger(state.maxItems) ||
|
|
95
|
+
typeof state.label !== 'string' ||
|
|
96
|
+
typeof state.preserveBinary !== 'boolean'
|
|
97
|
+
) {
|
|
98
|
+
throw new TypeError('Invalid source-metadata capture state.')
|
|
99
|
+
}
|
|
100
|
+
const stack = [{ depth: 0, value }]
|
|
101
|
+
while (stack.length) {
|
|
102
|
+
const frame = stack.pop()
|
|
103
|
+
const current = frame.value
|
|
104
|
+
const type = typeof current
|
|
105
|
+
if (type === 'string') {
|
|
106
|
+
StructuredDataSnapshot.#reserveText(state, current)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
current === null ||
|
|
111
|
+
['undefined', 'boolean', 'number', 'bigint'].includes(type)
|
|
112
|
+
) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
if (type !== 'object') {
|
|
116
|
+
throw new TypeError(
|
|
117
|
+
'Canonical asset source must contain clone-safe data.'
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
if (frame.depth > METADATA_MAX_DEPTH) {
|
|
121
|
+
throw new TypeError(
|
|
122
|
+
'Canonical asset source is nested too deeply.'
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
if (state.accounted.has(current)) continue
|
|
126
|
+
let prototype
|
|
127
|
+
let keys
|
|
128
|
+
try {
|
|
129
|
+
prototype = Object.getPrototypeOf(current)
|
|
130
|
+
keys = Reflect.ownKeys(current)
|
|
131
|
+
} catch {
|
|
132
|
+
throw new TypeError(
|
|
133
|
+
'Canonical asset source could not be inspected safely.'
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
prototype !== Object.prototype &&
|
|
138
|
+
prototype !== null &&
|
|
139
|
+
!(Array.isArray(current) && prototype === Array.prototype)
|
|
140
|
+
) {
|
|
141
|
+
throw new TypeError(
|
|
142
|
+
'Canonical asset source must contain plain data objects.'
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
state.accounted.add(current)
|
|
146
|
+
StructuredDataSnapshot.#reserve(state, 1)
|
|
147
|
+
const children = []
|
|
148
|
+
if (Array.isArray(current)) {
|
|
149
|
+
const lengthDescriptor = StructuredDataSnapshot.#descriptor(
|
|
150
|
+
current,
|
|
151
|
+
'length',
|
|
152
|
+
'Canonical asset source array could not be inspected.'
|
|
153
|
+
)
|
|
154
|
+
const length = lengthDescriptor.value
|
|
155
|
+
if (
|
|
156
|
+
!Object.hasOwn(lengthDescriptor, 'value') ||
|
|
157
|
+
!Number.isSafeInteger(length) ||
|
|
158
|
+
length < 0 ||
|
|
159
|
+
keys.length !== length + 1
|
|
160
|
+
) {
|
|
161
|
+
throw new TypeError(
|
|
162
|
+
'Canonical asset source arrays must be dense and plain.'
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
StructuredDataSnapshot.#reserve(state, length)
|
|
166
|
+
for (let index = 0; index < length; index += 1) {
|
|
167
|
+
children.push(
|
|
168
|
+
StructuredDataSnapshot.#dataDescriptor(
|
|
169
|
+
current,
|
|
170
|
+
String(index)
|
|
171
|
+
).value
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
StructuredDataSnapshot.#reserve(state, keys.length)
|
|
176
|
+
for (const key of keys) {
|
|
177
|
+
if (typeof key !== 'string') {
|
|
178
|
+
throw new TypeError(
|
|
179
|
+
'Canonical asset source keys must be strings.'
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
children.push(
|
|
183
|
+
StructuredDataSnapshot.#dataDescriptor(current, key)
|
|
184
|
+
.value
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
189
|
+
stack.push({
|
|
190
|
+
depth: frame.depth + 1,
|
|
191
|
+
value: children[index]
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Captures one graph node without revisiting caller-owned containers.
|
|
199
|
+
* @param {unknown} value Metadata candidate.
|
|
200
|
+
* @param {{ seen: Map<object, unknown>, bytes: number, items: number, label: string, maxBytes: number, maxItems: number }} state Capture state.
|
|
201
|
+
* @param {number} depth Current container depth.
|
|
202
|
+
* @returns {unknown} Isolated normalized value.
|
|
203
|
+
*/
|
|
204
|
+
static #capture(value, state, depth) {
|
|
205
|
+
const type = typeof value
|
|
206
|
+
if (type === 'string') {
|
|
207
|
+
StructuredDataSnapshot.#reserveText(state, value)
|
|
208
|
+
return value
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
value === null ||
|
|
212
|
+
['undefined', 'boolean', 'number', 'bigint'].includes(type)
|
|
213
|
+
) {
|
|
214
|
+
return value
|
|
215
|
+
}
|
|
216
|
+
if (type !== 'object') {
|
|
217
|
+
throw new TypeError(
|
|
218
|
+
'Canonical asset source must contain clone-safe data.'
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
if (depth > METADATA_MAX_DEPTH) {
|
|
222
|
+
throw new TypeError('Canonical asset source is nested too deeply.')
|
|
223
|
+
}
|
|
224
|
+
if (state.seen.has(value)) return state.seen.get(value)
|
|
225
|
+
StructuredDataSnapshot.#reserve(state, 1)
|
|
226
|
+
|
|
227
|
+
const binary = BinaryDataSnapshot.describe(value)
|
|
228
|
+
if (binary) {
|
|
229
|
+
return StructuredDataSnapshot.#binary(value, binary, state)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let prototype
|
|
233
|
+
let keys
|
|
234
|
+
try {
|
|
235
|
+
prototype = Object.getPrototypeOf(value)
|
|
236
|
+
keys = Reflect.ownKeys(value)
|
|
237
|
+
} catch {
|
|
238
|
+
throw new TypeError(
|
|
239
|
+
'Canonical asset source could not be inspected safely.'
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
if (prototype === Date.prototype) {
|
|
243
|
+
return StructuredDataSnapshot.#date(value, keys, state)
|
|
244
|
+
}
|
|
245
|
+
if (prototype === RegExp.prototype) {
|
|
246
|
+
return StructuredDataSnapshot.#regexp(value, keys, state)
|
|
247
|
+
}
|
|
248
|
+
if (prototype === Map.prototype) {
|
|
249
|
+
return StructuredDataSnapshot.#map(value, keys, state, depth)
|
|
250
|
+
}
|
|
251
|
+
if (prototype === Set.prototype) {
|
|
252
|
+
return StructuredDataSnapshot.#set(value, keys, state, depth)
|
|
253
|
+
}
|
|
254
|
+
if (Array.isArray(value)) {
|
|
255
|
+
return StructuredDataSnapshot.#array(
|
|
256
|
+
value,
|
|
257
|
+
prototype,
|
|
258
|
+
keys,
|
|
259
|
+
state,
|
|
260
|
+
depth
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
264
|
+
throw new TypeError(
|
|
265
|
+
'Canonical asset source must contain plain data objects.'
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
return StructuredDataSnapshot.#record(
|
|
269
|
+
value,
|
|
270
|
+
prototype,
|
|
271
|
+
keys,
|
|
272
|
+
state,
|
|
273
|
+
depth
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Copies genuine binary data after validating its single captured key set.
|
|
279
|
+
* @param {ArrayBuffer | SharedArrayBuffer | ArrayBufferView} value Binary value.
|
|
280
|
+
* @param {{ byteLength: number }} range Captured intrinsic binary range.
|
|
281
|
+
* @param {{ seen: Map<object, unknown>, items: number, preserveBinary: boolean }} state Capture state.
|
|
282
|
+
* @returns {number[] | ArrayBuffer | ArrayBufferView} Plain isolated bytes or preserved byte-backed data.
|
|
283
|
+
*/
|
|
284
|
+
static #binary(value, range, state) {
|
|
285
|
+
StructuredDataSnapshot.#reserveBytes(state, range.byteLength)
|
|
286
|
+
if (state.preserveBinary) {
|
|
287
|
+
const result = BinaryDataSnapshot.clone(value, range)
|
|
288
|
+
state.seen.set(value, result)
|
|
289
|
+
return result
|
|
290
|
+
}
|
|
291
|
+
// The normalized result contains one numeric item per byte. Reserve that
|
|
292
|
+
// output before enumerating keys or allocating/copying the payload.
|
|
293
|
+
StructuredDataSnapshot.#reserve(state, range.byteLength)
|
|
294
|
+
let keys
|
|
295
|
+
try {
|
|
296
|
+
keys = Reflect.ownKeys(value)
|
|
297
|
+
} catch {
|
|
298
|
+
throw new TypeError(
|
|
299
|
+
'Canonical asset source binary data could not be inspected.'
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
StructuredDataSnapshot.#reserve(
|
|
303
|
+
state,
|
|
304
|
+
Math.max(0, keys.length - range.byteLength)
|
|
305
|
+
)
|
|
306
|
+
for (const key of keys) {
|
|
307
|
+
if (typeof key !== 'string' || !/^\d+$/u.test(key)) {
|
|
308
|
+
throw new TypeError(
|
|
309
|
+
'Canonical asset source binary data has custom properties.'
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
const descriptor = StructuredDataSnapshot.#descriptor(
|
|
313
|
+
value,
|
|
314
|
+
key,
|
|
315
|
+
'Canonical asset source binary data could not be inspected.'
|
|
316
|
+
)
|
|
317
|
+
if (!Object.hasOwn(descriptor, 'value')) {
|
|
318
|
+
throw new TypeError(
|
|
319
|
+
'Canonical asset source binary data has accessors.'
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const bytes = BinaryDataSnapshot.copyBytes(value, range)
|
|
324
|
+
const result = new Array(bytes.byteLength)
|
|
325
|
+
state.seen.set(value, result)
|
|
326
|
+
for (let index = 0; index < bytes.byteLength; index += 1) {
|
|
327
|
+
result[index] = bytes[index]
|
|
328
|
+
}
|
|
329
|
+
return result
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Converts one genuine property-free Date to ISO text.
|
|
334
|
+
* @param {Date} value Date candidate.
|
|
335
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
336
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
337
|
+
* @returns {string} ISO timestamp.
|
|
338
|
+
*/
|
|
339
|
+
static #date(value, keys, state) {
|
|
340
|
+
if (keys.length) {
|
|
341
|
+
throw new TypeError(
|
|
342
|
+
'Canonical asset source built-ins may not have custom properties.'
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
let result
|
|
346
|
+
try {
|
|
347
|
+
result = DATE_TO_ISO.call(new Date(DATE_GET_TIME.call(value)))
|
|
348
|
+
} catch {
|
|
349
|
+
throw new TypeError(
|
|
350
|
+
'Canonical asset source contains an invalid built-in value.'
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
StructuredDataSnapshot.#reserveText(state, result)
|
|
354
|
+
state.seen.set(value, result)
|
|
355
|
+
return result
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Converts one genuine RegExp after reading its standard slot once.
|
|
360
|
+
* @param {RegExp} value Regular expression candidate.
|
|
361
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
362
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
363
|
+
* @returns {{ source: string, flags: string, lastIndex: number }} Plain regexp data.
|
|
364
|
+
*/
|
|
365
|
+
static #regexp(value, keys, state) {
|
|
366
|
+
const descriptor = StructuredDataSnapshot.#descriptor(
|
|
367
|
+
value,
|
|
368
|
+
'lastIndex',
|
|
369
|
+
'Canonical asset source regular expression could not be inspected.'
|
|
370
|
+
)
|
|
371
|
+
if (
|
|
372
|
+
keys.length !== 1 ||
|
|
373
|
+
keys[0] !== 'lastIndex' ||
|
|
374
|
+
!Object.hasOwn(descriptor, 'value') ||
|
|
375
|
+
typeof descriptor.value !== 'number' ||
|
|
376
|
+
typeof REGEXP_SOURCE !== 'function' ||
|
|
377
|
+
typeof REGEXP_FLAGS !== 'function'
|
|
378
|
+
) {
|
|
379
|
+
throw new TypeError(
|
|
380
|
+
'Canonical asset source regular expressions may not have custom properties.'
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
let result
|
|
384
|
+
try {
|
|
385
|
+
result = {
|
|
386
|
+
source: REGEXP_SOURCE.call(value),
|
|
387
|
+
flags: REGEXP_FLAGS.call(value),
|
|
388
|
+
lastIndex: descriptor.value
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
throw new TypeError(
|
|
392
|
+
'Canonical asset source contains an invalid regular expression.'
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
StructuredDataSnapshot.#reserveText(state, result.source)
|
|
396
|
+
StructuredDataSnapshot.#reserveText(state, result.flags)
|
|
397
|
+
state.seen.set(value, result)
|
|
398
|
+
return result
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Converts one genuine Map through captured platform iteration.
|
|
403
|
+
* @param {Map<any, any>} value Map candidate.
|
|
404
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
405
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
406
|
+
* @param {number} depth Current depth.
|
|
407
|
+
* @returns {any[][]} Plain entry pairs.
|
|
408
|
+
*/
|
|
409
|
+
static #map(value, keys, state, depth) {
|
|
410
|
+
if (keys.length || typeof MAP_SIZE !== 'function') {
|
|
411
|
+
throw new TypeError(
|
|
412
|
+
'Canonical asset source maps may not have custom properties.'
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
let size
|
|
416
|
+
let iterator
|
|
417
|
+
try {
|
|
418
|
+
size = MAP_SIZE.call(value)
|
|
419
|
+
iterator = MAP_ENTRIES.call(value)
|
|
420
|
+
} catch {
|
|
421
|
+
throw new TypeError(
|
|
422
|
+
'Canonical asset source contains an invalid map.'
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
StructuredDataSnapshot.#reserve(state, size * 2)
|
|
426
|
+
const result = []
|
|
427
|
+
state.seen.set(value, result)
|
|
428
|
+
for (let index = 0; index < size; index += 1) {
|
|
429
|
+
const row = MAP_ITERATOR_NEXT.call(iterator)
|
|
430
|
+
if (row.done || !Array.isArray(row.value)) {
|
|
431
|
+
throw new TypeError(
|
|
432
|
+
'Canonical asset source map changed during inspection.'
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
result[index] = [
|
|
436
|
+
StructuredDataSnapshot.#capture(row.value[0], state, depth + 1),
|
|
437
|
+
StructuredDataSnapshot.#capture(row.value[1], state, depth + 1)
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
if (!MAP_ITERATOR_NEXT.call(iterator).done) {
|
|
441
|
+
throw new TypeError(
|
|
442
|
+
'Canonical asset source map changed during inspection.'
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
return result
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Converts one genuine Set through captured platform iteration.
|
|
450
|
+
* @param {Set<any>} value Set candidate.
|
|
451
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
452
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
453
|
+
* @param {number} depth Current depth.
|
|
454
|
+
* @returns {any[]} Plain values.
|
|
455
|
+
*/
|
|
456
|
+
static #set(value, keys, state, depth) {
|
|
457
|
+
if (keys.length || typeof SET_SIZE !== 'function') {
|
|
458
|
+
throw new TypeError(
|
|
459
|
+
'Canonical asset source sets may not have custom properties.'
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
let size
|
|
463
|
+
let iterator
|
|
464
|
+
try {
|
|
465
|
+
size = SET_SIZE.call(value)
|
|
466
|
+
iterator = SET_VALUES.call(value)
|
|
467
|
+
} catch {
|
|
468
|
+
throw new TypeError(
|
|
469
|
+
'Canonical asset source contains an invalid set.'
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
StructuredDataSnapshot.#reserve(state, size)
|
|
473
|
+
const result = []
|
|
474
|
+
state.seen.set(value, result)
|
|
475
|
+
for (let index = 0; index < size; index += 1) {
|
|
476
|
+
const row = SET_ITERATOR_NEXT.call(iterator)
|
|
477
|
+
if (row.done) {
|
|
478
|
+
throw new TypeError(
|
|
479
|
+
'Canonical asset source set changed during inspection.'
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
result[index] = StructuredDataSnapshot.#capture(
|
|
483
|
+
row.value,
|
|
484
|
+
state,
|
|
485
|
+
depth + 1
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
if (!SET_ITERATOR_NEXT.call(iterator).done) {
|
|
489
|
+
throw new TypeError(
|
|
490
|
+
'Canonical asset source set changed during inspection.'
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
return result
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Captures one dense plain array from its first key and descriptor view.
|
|
498
|
+
* @param {any[]} value Array candidate.
|
|
499
|
+
* @param {object | null} prototype Captured prototype.
|
|
500
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
501
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
502
|
+
* @param {number} depth Current depth.
|
|
503
|
+
* @returns {any[]} Owned array.
|
|
504
|
+
*/
|
|
505
|
+
static #array(value, prototype, keys, state, depth) {
|
|
506
|
+
const lengthDescriptor = StructuredDataSnapshot.#descriptor(
|
|
507
|
+
value,
|
|
508
|
+
'length',
|
|
509
|
+
'Canonical asset source array could not be inspected.'
|
|
510
|
+
)
|
|
511
|
+
const length = lengthDescriptor.value
|
|
512
|
+
if (
|
|
513
|
+
prototype !== Array.prototype ||
|
|
514
|
+
!Object.hasOwn(lengthDescriptor, 'value') ||
|
|
515
|
+
!Number.isSafeInteger(length) ||
|
|
516
|
+
length < 0 ||
|
|
517
|
+
keys.length !== length + 1
|
|
518
|
+
) {
|
|
519
|
+
throw new TypeError(
|
|
520
|
+
'Canonical asset source arrays must be dense and plain.'
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
StructuredDataSnapshot.#reserve(state, length)
|
|
524
|
+
const result = new Array(length)
|
|
525
|
+
state.seen.set(value, result)
|
|
526
|
+
for (let index = 0; index < length; index += 1) {
|
|
527
|
+
const key = String(index)
|
|
528
|
+
const descriptor = StructuredDataSnapshot.#dataDescriptor(
|
|
529
|
+
value,
|
|
530
|
+
key
|
|
531
|
+
)
|
|
532
|
+
Object.defineProperty(result, key, {
|
|
533
|
+
...descriptor,
|
|
534
|
+
value: StructuredDataSnapshot.#capture(
|
|
535
|
+
descriptor.value,
|
|
536
|
+
state,
|
|
537
|
+
depth + 1
|
|
538
|
+
)
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
return result
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Captures one plain record from its first key and descriptor view.
|
|
546
|
+
* @param {object} value Record candidate.
|
|
547
|
+
* @param {object | null} prototype Captured prototype.
|
|
548
|
+
* @param {PropertyKey[]} keys Captured own keys.
|
|
549
|
+
* @param {{ seen: Map<object, unknown>, items: number }} state Capture state.
|
|
550
|
+
* @param {number} depth Current depth.
|
|
551
|
+
* @returns {object} Owned record.
|
|
552
|
+
*/
|
|
553
|
+
static #record(value, prototype, keys, state, depth) {
|
|
554
|
+
StructuredDataSnapshot.#reserve(state, keys.length)
|
|
555
|
+
const result = Object.create(prototype)
|
|
556
|
+
state.seen.set(value, result)
|
|
557
|
+
for (const key of keys) {
|
|
558
|
+
if (typeof key !== 'string') {
|
|
559
|
+
throw new TypeError(
|
|
560
|
+
'Canonical asset source keys must be strings.'
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
const descriptor = StructuredDataSnapshot.#dataDescriptor(
|
|
564
|
+
value,
|
|
565
|
+
key
|
|
566
|
+
)
|
|
567
|
+
Object.defineProperty(result, key, {
|
|
568
|
+
...descriptor,
|
|
569
|
+
value: StructuredDataSnapshot.#capture(
|
|
570
|
+
descriptor.value,
|
|
571
|
+
state,
|
|
572
|
+
depth + 1
|
|
573
|
+
)
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
return result
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Reads one enumerable own data descriptor with normalized failures.
|
|
581
|
+
* @param {object} owner Property owner.
|
|
582
|
+
* @param {string} key Property name.
|
|
583
|
+
* @returns {PropertyDescriptor} Validated descriptor.
|
|
584
|
+
*/
|
|
585
|
+
static #dataDescriptor(owner, key) {
|
|
586
|
+
const descriptor = StructuredDataSnapshot.#descriptor(
|
|
587
|
+
owner,
|
|
588
|
+
key,
|
|
589
|
+
'Canonical asset source properties could not be inspected.'
|
|
590
|
+
)
|
|
591
|
+
if (
|
|
592
|
+
!Object.hasOwn(descriptor, 'value') ||
|
|
593
|
+
descriptor.enumerable !== true
|
|
594
|
+
) {
|
|
595
|
+
throw new TypeError(
|
|
596
|
+
'Canonical asset source may contain only enumerable data properties.'
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
return descriptor
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Reads one own property descriptor and normalizes proxy failures.
|
|
604
|
+
* @param {object} owner Property owner.
|
|
605
|
+
* @param {PropertyKey} key Property key.
|
|
606
|
+
* @param {string} message Failure message.
|
|
607
|
+
* @returns {PropertyDescriptor} Existing own descriptor.
|
|
608
|
+
*/
|
|
609
|
+
static #descriptor(owner, key, message) {
|
|
610
|
+
let descriptor
|
|
611
|
+
try {
|
|
612
|
+
descriptor = Object.getOwnPropertyDescriptor(owner, key)
|
|
613
|
+
} catch {
|
|
614
|
+
throw new TypeError(message)
|
|
615
|
+
}
|
|
616
|
+
if (!descriptor) throw new TypeError(message)
|
|
617
|
+
return descriptor
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Reserves bounded graph work before child traversal begins.
|
|
622
|
+
* @param {{ items: number, label: string, maxItems: number }} state Capture state.
|
|
623
|
+
* @param {number} count Additional items.
|
|
624
|
+
* @returns {void}
|
|
625
|
+
*/
|
|
626
|
+
static #reserve(state, count) {
|
|
627
|
+
if (
|
|
628
|
+
!Number.isSafeInteger(count) ||
|
|
629
|
+
count < 0 ||
|
|
630
|
+
state.items + count > state.maxItems
|
|
631
|
+
) {
|
|
632
|
+
throw new TypeError(`${state.label} is too large.`)
|
|
633
|
+
}
|
|
634
|
+
state.items += count
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Reserves bounded payload bytes before a metadata copy is allocated.
|
|
639
|
+
* @param {{ bytes: number, label: string, maxBytes: number }} state Capture state.
|
|
640
|
+
* @param {number} count Additional bytes.
|
|
641
|
+
* @returns {void}
|
|
642
|
+
*/
|
|
643
|
+
static #reserveBytes(state, count) {
|
|
644
|
+
if (state.maxBytes === UNBOUNDED_METADATA_BYTES) return
|
|
645
|
+
if (
|
|
646
|
+
!Number.isSafeInteger(count) ||
|
|
647
|
+
count < 0 ||
|
|
648
|
+
state.bytes + count > state.maxBytes
|
|
649
|
+
) {
|
|
650
|
+
throw new TypeError(`${state.label} is too large.`)
|
|
651
|
+
}
|
|
652
|
+
state.bytes += count
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Measures and reserves UTF-8 text without allocating encoded bytes.
|
|
657
|
+
* @param {{ bytes: number, label: string, maxBytes: number }} state Capture state.
|
|
658
|
+
* @param {string} value Text value.
|
|
659
|
+
* @returns {void}
|
|
660
|
+
*/
|
|
661
|
+
static #reserveText(state, value) {
|
|
662
|
+
if (state.maxBytes === UNBOUNDED_METADATA_BYTES) return
|
|
663
|
+
let length = 0
|
|
664
|
+
for (const character of value) {
|
|
665
|
+
const codePoint = character.codePointAt(0)
|
|
666
|
+
length +=
|
|
667
|
+
codePoint <= 0x7f
|
|
668
|
+
? 1
|
|
669
|
+
: codePoint <= 0x7ff
|
|
670
|
+
? 2
|
|
671
|
+
: codePoint <= 0xffff
|
|
672
|
+
? 3
|
|
673
|
+
: 4
|
|
674
|
+
if (state.bytes + length > state.maxBytes) {
|
|
675
|
+
throw new TypeError(`${state.label} is too large.`)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
StructuredDataSnapshot.#reserveBytes(state, length)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
Object.freeze(StructuredDataSnapshot.prototype)
|
|
683
|
+
Object.freeze(StructuredDataSnapshot)
|