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,441 @@
|
|
|
1
|
+
import { ToolkitAsset } from '../contracts/ToolkitAsset.mjs'
|
|
2
|
+
import { ToolkitError } from '../contracts/ToolkitError.mjs'
|
|
3
|
+
import { ToolkitProgress } from '../contracts/ToolkitProgress.mjs'
|
|
4
|
+
import { BinaryDataSnapshot } from '../context/BinaryDataSnapshot.mjs'
|
|
5
|
+
import { CircuitJsonReadOnlyDocument } from '../context/CircuitJsonReadOnlyDocument.mjs'
|
|
6
|
+
import { Scene3dOptions } from './Scene3dOptions.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves scene assets only through an explicitly injected callback.
|
|
10
|
+
*/
|
|
11
|
+
export class SceneAssetResolver {
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a bounded asset list while preserving request order.
|
|
14
|
+
* @param {unknown} requests Asset request records.
|
|
15
|
+
* @param {unknown} [options] Resolution options.
|
|
16
|
+
* @returns {Promise<object[]>} Resolved canonical asset records.
|
|
17
|
+
*/
|
|
18
|
+
static async resolveAll(requests, options = {}) {
|
|
19
|
+
const requestValues = Scene3dOptions.dataArray(
|
|
20
|
+
requests,
|
|
21
|
+
'Scene assets',
|
|
22
|
+
Scene3dOptions.maxAssetCount
|
|
23
|
+
)
|
|
24
|
+
const normalized = Scene3dOptions.normalize(options)
|
|
25
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
26
|
+
const capturedRequests = SceneAssetResolver.#preflightResident(
|
|
27
|
+
requestValues,
|
|
28
|
+
normalized
|
|
29
|
+
)
|
|
30
|
+
const assets = capturedRequests.map((request) =>
|
|
31
|
+
SceneAssetResolver.#request(request)
|
|
32
|
+
)
|
|
33
|
+
if (!assets.length) return []
|
|
34
|
+
|
|
35
|
+
let previous = SceneAssetResolver.#progress(
|
|
36
|
+
normalized,
|
|
37
|
+
{
|
|
38
|
+
stage: 'decode',
|
|
39
|
+
detail: 'scene-assets',
|
|
40
|
+
completed: 0,
|
|
41
|
+
total: assets.length
|
|
42
|
+
},
|
|
43
|
+
null
|
|
44
|
+
)
|
|
45
|
+
const results = new Array(assets.length)
|
|
46
|
+
let totalBytes = 0
|
|
47
|
+
|
|
48
|
+
for (
|
|
49
|
+
let start = 0;
|
|
50
|
+
start < assets.length;
|
|
51
|
+
start += normalized.resolveConcurrency
|
|
52
|
+
) {
|
|
53
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
54
|
+
const end = Math.min(
|
|
55
|
+
start + normalized.resolveConcurrency,
|
|
56
|
+
assets.length
|
|
57
|
+
)
|
|
58
|
+
const batch = []
|
|
59
|
+
for (let index = start; index < end; index += 1) {
|
|
60
|
+
batch.push(
|
|
61
|
+
SceneAssetResolver.#resolveOne(assets[index], normalized)
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
const resolvedBatch = await Promise.all(batch)
|
|
65
|
+
for (let offset = 0; offset < resolvedBatch.length; offset += 1) {
|
|
66
|
+
const index = start + offset
|
|
67
|
+
const resolved = resolvedBatch[offset]
|
|
68
|
+
const range =
|
|
69
|
+
typeof resolved.data === 'string'
|
|
70
|
+
? null
|
|
71
|
+
: BinaryDataSnapshot.describe(resolved.data)
|
|
72
|
+
const byteLength = SceneAssetResolver.#byteLength(
|
|
73
|
+
resolved.data,
|
|
74
|
+
range
|
|
75
|
+
)
|
|
76
|
+
if (byteLength > normalized.maxAssetBytes) {
|
|
77
|
+
throw SceneAssetResolver.#limitError(
|
|
78
|
+
resolved.request,
|
|
79
|
+
byteLength,
|
|
80
|
+
normalized.maxAssetBytes,
|
|
81
|
+
'asset'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
if (totalBytes + byteLength > normalized.maxTotalAssetBytes) {
|
|
85
|
+
throw SceneAssetResolver.#limitError(
|
|
86
|
+
resolved.request,
|
|
87
|
+
totalBytes + byteLength,
|
|
88
|
+
normalized.maxTotalAssetBytes,
|
|
89
|
+
'total'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
const asset = SceneAssetResolver.#assetWithData(
|
|
93
|
+
resolved.request,
|
|
94
|
+
resolved.data,
|
|
95
|
+
range
|
|
96
|
+
)
|
|
97
|
+
totalBytes += byteLength
|
|
98
|
+
results[index] = asset
|
|
99
|
+
previous = SceneAssetResolver.#progress(
|
|
100
|
+
normalized,
|
|
101
|
+
{
|
|
102
|
+
stage: 'decode',
|
|
103
|
+
detail: 'scene-assets',
|
|
104
|
+
completed: index + 1,
|
|
105
|
+
total: assets.length
|
|
106
|
+
},
|
|
107
|
+
previous
|
|
108
|
+
)
|
|
109
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
114
|
+
SceneAssetResolver.#progress(
|
|
115
|
+
normalized,
|
|
116
|
+
{
|
|
117
|
+
stage: 'complete',
|
|
118
|
+
detail: 'scene-assets',
|
|
119
|
+
completed: assets.length,
|
|
120
|
+
total: assets.length
|
|
121
|
+
},
|
|
122
|
+
previous
|
|
123
|
+
)
|
|
124
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
125
|
+
return results
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Normalizes one public asset request without invoking accessors.
|
|
130
|
+
* @param {unknown} request Asset request candidate.
|
|
131
|
+
* @returns {object} Canonical asset record.
|
|
132
|
+
*/
|
|
133
|
+
static #request(request) {
|
|
134
|
+
const fields = CircuitJsonReadOnlyDocument.copyAssetFields(request)
|
|
135
|
+
const asset = ToolkitAsset.create(fields)
|
|
136
|
+
if (!asset.id) {
|
|
137
|
+
throw new TypeError('Scene asset request id must not be empty.')
|
|
138
|
+
}
|
|
139
|
+
return asset
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Bounds all resident request data before normalization can copy or encode it.
|
|
144
|
+
* @param {object[]} requests Descriptor-safe request records.
|
|
145
|
+
* @param {ReturnType<Scene3dOptions['normalize']>} options Normalized options.
|
|
146
|
+
* @returns {object[]} Atomically captured request records.
|
|
147
|
+
*/
|
|
148
|
+
static #preflightResident(requests, options) {
|
|
149
|
+
const metadataBudget =
|
|
150
|
+
CircuitJsonReadOnlyDocument.createMetadataBudget()
|
|
151
|
+
let totalBytes = 0
|
|
152
|
+
const captured = new Array(requests.length)
|
|
153
|
+
for (let index = 0; index < requests.length; index += 1) {
|
|
154
|
+
const request = CircuitJsonReadOnlyDocument.captureAsset(
|
|
155
|
+
requests[index],
|
|
156
|
+
metadataBudget,
|
|
157
|
+
false,
|
|
158
|
+
(byteLength, identity) => {
|
|
159
|
+
if (byteLength > options.maxAssetBytes) {
|
|
160
|
+
throw SceneAssetResolver.#preflightLimitError(
|
|
161
|
+
identity,
|
|
162
|
+
byteLength,
|
|
163
|
+
options.maxAssetBytes,
|
|
164
|
+
'asset'
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
if (totalBytes + byteLength > options.maxTotalAssetBytes) {
|
|
168
|
+
throw SceneAssetResolver.#preflightLimitError(
|
|
169
|
+
identity,
|
|
170
|
+
totalBytes + byteLength,
|
|
171
|
+
options.maxTotalAssetBytes,
|
|
172
|
+
'total'
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
captured[index] = request
|
|
178
|
+
const byteLength =
|
|
179
|
+
CircuitJsonReadOnlyDocument.assetPayloadByteLength(
|
|
180
|
+
request,
|
|
181
|
+
metadataBudget
|
|
182
|
+
)
|
|
183
|
+
if (byteLength > options.maxAssetBytes) {
|
|
184
|
+
throw SceneAssetResolver.#candidateLimitError(
|
|
185
|
+
request,
|
|
186
|
+
byteLength,
|
|
187
|
+
options.maxAssetBytes,
|
|
188
|
+
'asset'
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
totalBytes += byteLength
|
|
192
|
+
if (totalBytes > options.maxTotalAssetBytes) {
|
|
193
|
+
throw SceneAssetResolver.#candidateLimitError(
|
|
194
|
+
request,
|
|
195
|
+
totalBytes,
|
|
196
|
+
options.maxTotalAssetBytes,
|
|
197
|
+
'total'
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return captured
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Creates a byte-limit error before payload or source metadata is copied.
|
|
206
|
+
* @param {{ id: unknown, name: unknown }} identity Captured primitive identity.
|
|
207
|
+
* @param {number} actual Actual resident bytes.
|
|
208
|
+
* @param {number} maximum Maximum bytes.
|
|
209
|
+
* @param {'asset' | 'total'} scope Limit scope.
|
|
210
|
+
* @returns {ToolkitError} Typed limit error.
|
|
211
|
+
*/
|
|
212
|
+
static #preflightLimitError(identity, actual, maximum, scope) {
|
|
213
|
+
return SceneAssetResolver.#limitError(
|
|
214
|
+
{
|
|
215
|
+
id: SceneAssetResolver.#primitiveText(identity.id),
|
|
216
|
+
name: SceneAssetResolver.#primitiveText(identity.name)
|
|
217
|
+
},
|
|
218
|
+
actual,
|
|
219
|
+
maximum,
|
|
220
|
+
scope
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Converts an already-validated primitive identity without object coercion.
|
|
226
|
+
* @param {unknown} value Primitive identity field.
|
|
227
|
+
* @returns {string} Text identity.
|
|
228
|
+
*/
|
|
229
|
+
static #primitiveText(value) {
|
|
230
|
+
return value === null || value === undefined ? '' : String(value)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Creates a limit error from metadata-only request normalization.
|
|
235
|
+
* @param {object} request Asset request candidate.
|
|
236
|
+
* @param {number} actual Actual bytes.
|
|
237
|
+
* @param {number} maximum Maximum bytes.
|
|
238
|
+
* @param {'asset' | 'total'} scope Limit scope.
|
|
239
|
+
* @returns {ToolkitError} Typed limit error.
|
|
240
|
+
*/
|
|
241
|
+
static #candidateLimitError(request, actual, maximum, scope) {
|
|
242
|
+
const metadata = ToolkitAsset.create(
|
|
243
|
+
CircuitJsonReadOnlyDocument.copyAssetMetadataFields(request)
|
|
244
|
+
)
|
|
245
|
+
return SceneAssetResolver.#limitError(metadata, actual, maximum, scope)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resolves or copies one asset payload.
|
|
250
|
+
* @param {object} request Canonical asset request.
|
|
251
|
+
* @param {ReturnType<Scene3dOptions['normalize']>} options Normalized options.
|
|
252
|
+
* @returns {Promise<{ request: object, data: string | ArrayBuffer | ArrayBufferView }>} Resolved request and payload.
|
|
253
|
+
*/
|
|
254
|
+
static async #resolveOne(request, options) {
|
|
255
|
+
Scene3dOptions.assertNotAborted(options.signal)
|
|
256
|
+
if (request.data !== null) {
|
|
257
|
+
return { request, data: request.data }
|
|
258
|
+
}
|
|
259
|
+
if (!options.resolveAsset) {
|
|
260
|
+
throw SceneAssetResolver.#missingData(request)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let pending
|
|
264
|
+
try {
|
|
265
|
+
pending = options.resolveAsset(
|
|
266
|
+
SceneAssetResolver.#resolverRequest(request),
|
|
267
|
+
{ signal: options.signal }
|
|
268
|
+
)
|
|
269
|
+
} catch (error) {
|
|
270
|
+
throw new ToolkitError(
|
|
271
|
+
`Unable to resolve scene asset: ${request.id}.`,
|
|
272
|
+
{
|
|
273
|
+
code: 'ERR_ASSET_RESOLUTION',
|
|
274
|
+
category: 'runtime',
|
|
275
|
+
source: request.name,
|
|
276
|
+
details: { assetId: request.id },
|
|
277
|
+
cause: error
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
let result
|
|
282
|
+
try {
|
|
283
|
+
result = await Scene3dOptions.awaitWithSignal(
|
|
284
|
+
pending,
|
|
285
|
+
options.signal
|
|
286
|
+
)
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (
|
|
289
|
+
error instanceof ToolkitError &&
|
|
290
|
+
error.code === 'ERR_CANCELLED'
|
|
291
|
+
) {
|
|
292
|
+
throw error
|
|
293
|
+
}
|
|
294
|
+
throw new ToolkitError(
|
|
295
|
+
`Unable to resolve scene asset: ${request.id}.`,
|
|
296
|
+
{
|
|
297
|
+
code: 'ERR_ASSET_RESOLUTION',
|
|
298
|
+
category: 'runtime',
|
|
299
|
+
source: request.name,
|
|
300
|
+
details: { assetId: request.id },
|
|
301
|
+
cause: error
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
Scene3dOptions.assertNotAborted(options.signal)
|
|
306
|
+
const data = SceneAssetResolver.#resolvedData(result)
|
|
307
|
+
if (data === null) throw SceneAssetResolver.#missingData(request)
|
|
308
|
+
return { request, data }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Creates a clone-safe resolver request without copying absent payload data.
|
|
313
|
+
* @param {object} request Canonical request.
|
|
314
|
+
* @returns {object} Resolver request.
|
|
315
|
+
*/
|
|
316
|
+
static #resolverRequest(request) {
|
|
317
|
+
return {
|
|
318
|
+
id: request.id,
|
|
319
|
+
kind: request.kind,
|
|
320
|
+
name: request.name,
|
|
321
|
+
mediaType: request.mediaType,
|
|
322
|
+
byteLength: request.byteLength,
|
|
323
|
+
data: null,
|
|
324
|
+
source: CircuitJsonReadOnlyDocument.copyMetadataValue(
|
|
325
|
+
request.source
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Extracts one supported payload from a resolver return value.
|
|
332
|
+
* @param {unknown} result Resolver return value.
|
|
333
|
+
* @returns {string | ArrayBuffer | ArrayBufferView | null} Payload.
|
|
334
|
+
*/
|
|
335
|
+
static #resolvedData(result) {
|
|
336
|
+
if (SceneAssetResolver.#isPayload(result)) return result
|
|
337
|
+
if (result === null || result === undefined) return null
|
|
338
|
+
const fields = Scene3dOptions.dataRecord(result, 'Resolved scene asset')
|
|
339
|
+
return SceneAssetResolver.#isPayload(fields.data) ? fields.data : null
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Copies and bounds one resolved asset payload.
|
|
344
|
+
* @param {object} request Asset request.
|
|
345
|
+
* @param {string | ArrayBuffer | ArrayBufferView} data Resolved payload.
|
|
346
|
+
* @param {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number } | null} range Captured binary range.
|
|
347
|
+
* @returns {object} Canonical asset with copied data.
|
|
348
|
+
*/
|
|
349
|
+
static #assetWithData(request, data, range) {
|
|
350
|
+
const copiedData =
|
|
351
|
+
typeof data === 'string'
|
|
352
|
+
? data
|
|
353
|
+
: BinaryDataSnapshot.copyBytes(data, range)
|
|
354
|
+
return ToolkitAsset.create({
|
|
355
|
+
id: request.id,
|
|
356
|
+
kind: request.kind,
|
|
357
|
+
name: request.name,
|
|
358
|
+
mediaType: request.mediaType,
|
|
359
|
+
data: copiedData,
|
|
360
|
+
source: request.source
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Measures a supported payload without copying oversized binary inputs.
|
|
366
|
+
* @param {string | ArrayBuffer | ArrayBufferView} data Payload.
|
|
367
|
+
* @param {{ byteLength: number } | null} [range] Captured binary range.
|
|
368
|
+
* @returns {number} Byte length.
|
|
369
|
+
*/
|
|
370
|
+
static #byteLength(data, range = null) {
|
|
371
|
+
if (typeof data === 'string') {
|
|
372
|
+
return CircuitJsonReadOnlyDocument.utf8ByteLength(data)
|
|
373
|
+
}
|
|
374
|
+
return range?.byteLength ?? BinaryDataSnapshot.byteLength(data) ?? 0
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Returns true for supported textual or binary payloads.
|
|
379
|
+
* @param {unknown} value Payload candidate.
|
|
380
|
+
* @returns {boolean} Whether the value is supported.
|
|
381
|
+
*/
|
|
382
|
+
static #isPayload(value) {
|
|
383
|
+
return (
|
|
384
|
+
typeof value === 'string' ||
|
|
385
|
+
BinaryDataSnapshot.byteLength(value) !== null
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Creates the shared missing-data failure.
|
|
391
|
+
* @param {object} request Asset request.
|
|
392
|
+
* @returns {ToolkitError} Typed failure.
|
|
393
|
+
*/
|
|
394
|
+
static #missingData(request) {
|
|
395
|
+
return new ToolkitError(
|
|
396
|
+
`Scene asset data is required: ${request.id}.`,
|
|
397
|
+
{
|
|
398
|
+
code: 'ERR_ASSET_DATA_REQUIRED',
|
|
399
|
+
category: 'unsupported',
|
|
400
|
+
source: request.name,
|
|
401
|
+
details: { assetId: request.id }
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Creates one asset size-limit failure.
|
|
408
|
+
* @param {object} request Asset request.
|
|
409
|
+
* @param {number} actual Actual bytes.
|
|
410
|
+
* @param {number} maximum Maximum bytes.
|
|
411
|
+
* @param {'asset' | 'total'} scope Limit scope.
|
|
412
|
+
* @returns {ToolkitError} Typed failure.
|
|
413
|
+
*/
|
|
414
|
+
static #limitError(request, actual, maximum, scope) {
|
|
415
|
+
return new ToolkitError('Scene asset bytes exceed the safe limit.', {
|
|
416
|
+
code: 'ERR_ASSET_LIMIT',
|
|
417
|
+
category: 'unsupported',
|
|
418
|
+
source: request.name,
|
|
419
|
+
details: {
|
|
420
|
+
assetId: request.id,
|
|
421
|
+
scope,
|
|
422
|
+
actual,
|
|
423
|
+
maximum
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Emits one ordered progress row when a callback is installed.
|
|
430
|
+
* @param {ReturnType<Scene3dOptions['normalize']>} options Normalized options.
|
|
431
|
+
* @param {object} fields Progress fields.
|
|
432
|
+
* @param {object | null} previous Previous row.
|
|
433
|
+
* @returns {object | null} Emitted row or previous value.
|
|
434
|
+
*/
|
|
435
|
+
static #progress(options, fields, previous) {
|
|
436
|
+
if (!options.onProgress) return previous
|
|
437
|
+
const row = ToolkitProgress.create(fields, previous)
|
|
438
|
+
options.onProgress(row)
|
|
439
|
+
return row
|
|
440
|
+
}
|
|
441
|
+
}
|