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,47 @@
|
|
|
1
|
+
import { freezeScene } from './Scene3dFreeze.mjs'
|
|
2
|
+
import { Scene3dOptions } from './Scene3dOptions.mjs'
|
|
3
|
+
import { PcbScene3dBuilder } from './PcbScene3dBuilder.mjs'
|
|
4
|
+
import { SceneAssetResolver } from './SceneAssetResolver.mjs'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Asynchronously prepares canonical scenes and resolves requested model assets.
|
|
8
|
+
*/
|
|
9
|
+
export class PcbScene3dPreparator {
|
|
10
|
+
/**
|
|
11
|
+
* Builds one scene and resolves its assets only through injected options.
|
|
12
|
+
* @param {unknown} input Document result, CircuitJSON model, or context.
|
|
13
|
+
* @param {unknown} [options] Scene preparation options.
|
|
14
|
+
* @returns {Promise<object>} Prepared canonical scene.
|
|
15
|
+
*/
|
|
16
|
+
static async prepare(input, options = {}) {
|
|
17
|
+
const normalized = Scene3dOptions.normalize(options)
|
|
18
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
19
|
+
const scene = PcbScene3dBuilder.build(input, options)
|
|
20
|
+
const hasUnresolvedAssets = scene.assets.some(
|
|
21
|
+
(asset) => asset.data === null
|
|
22
|
+
)
|
|
23
|
+
if (
|
|
24
|
+
hasUnresolvedAssets &&
|
|
25
|
+
!normalized.resolveAsset &&
|
|
26
|
+
normalized.fidelity !== 'native'
|
|
27
|
+
) {
|
|
28
|
+
return scene
|
|
29
|
+
}
|
|
30
|
+
const assets = await SceneAssetResolver.resolveAll(
|
|
31
|
+
scene.assets,
|
|
32
|
+
options
|
|
33
|
+
)
|
|
34
|
+
Scene3dOptions.assertNotAborted(normalized.signal)
|
|
35
|
+
if (!assets.length) return scene
|
|
36
|
+
|
|
37
|
+
return freezeScene({
|
|
38
|
+
...scene,
|
|
39
|
+
assets,
|
|
40
|
+
statistics: {
|
|
41
|
+
...scene.statistics,
|
|
42
|
+
assetCount: assets.length,
|
|
43
|
+
resolvedAssetCount: assets.length
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { ToolkitAsset } from '../contracts/ToolkitAsset.mjs'
|
|
2
|
+
import { ToolkitError } from '../contracts/ToolkitError.mjs'
|
|
3
|
+
import { CircuitJsonReadOnlyDocument } from '../context/CircuitJsonReadOnlyDocument.mjs'
|
|
4
|
+
import { Scene3dModelReference } from './Scene3dModelReference.mjs'
|
|
5
|
+
import { Scene3dOptions } from './Scene3dOptions.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Selects bounded scene assets through exact canonical path aliases.
|
|
9
|
+
*/
|
|
10
|
+
export class Scene3dAssetIndex {
|
|
11
|
+
#byAlias
|
|
12
|
+
#byAsset
|
|
13
|
+
#byReference
|
|
14
|
+
#claims
|
|
15
|
+
#options
|
|
16
|
+
#selected
|
|
17
|
+
#totalBytes
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates one request-local asset index without copying payload bytes.
|
|
21
|
+
* @param {unknown} values Canonical document assets.
|
|
22
|
+
* @param {ReturnType<Scene3dOptions['normalize']>} options Scene options.
|
|
23
|
+
*/
|
|
24
|
+
constructor(values, options) {
|
|
25
|
+
const assets = Scene3dOptions.dataArray(
|
|
26
|
+
values === undefined ? [] : values,
|
|
27
|
+
'Scene assets',
|
|
28
|
+
Scene3dOptions.maxAssetCount
|
|
29
|
+
)
|
|
30
|
+
this.#options = options
|
|
31
|
+
this.#byAlias = new Map()
|
|
32
|
+
this.#byAsset = new WeakMap()
|
|
33
|
+
this.#byReference = new Map()
|
|
34
|
+
this.#claims = new Map()
|
|
35
|
+
this.#selected = []
|
|
36
|
+
this.#totalBytes = 0
|
|
37
|
+
const ids = new Map()
|
|
38
|
+
const metadataBudget =
|
|
39
|
+
CircuitJsonReadOnlyDocument.createMetadataBudget()
|
|
40
|
+
for (const asset of assets) {
|
|
41
|
+
const metadata = Scene3dAssetIndex.#lookupMetadata(
|
|
42
|
+
CircuitJsonReadOnlyDocument.copyAssetIndexFields(
|
|
43
|
+
asset,
|
|
44
|
+
metadataBudget
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
if (
|
|
48
|
+
metadata.id &&
|
|
49
|
+
ids.has(metadata.id) &&
|
|
50
|
+
ids.get(metadata.id) !== asset
|
|
51
|
+
) {
|
|
52
|
+
throw Scene3dAssetIndex.#ambiguousId(metadata.id)
|
|
53
|
+
}
|
|
54
|
+
if (metadata.id) ids.set(metadata.id, asset)
|
|
55
|
+
const entry = { asset, metadata }
|
|
56
|
+
for (const alias of Scene3dAssetIndex.#aliases(metadata)) {
|
|
57
|
+
if (!this.#byAlias.has(alias)) {
|
|
58
|
+
this.#byAlias.set(alias, entry)
|
|
59
|
+
} else if (this.#byAlias.get(alias)?.asset !== asset) {
|
|
60
|
+
this.#byAlias.set(alias, null)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns selected assets in deterministic first-reference order.
|
|
68
|
+
* @returns {object[]} Canonical scene assets.
|
|
69
|
+
*/
|
|
70
|
+
get assets() {
|
|
71
|
+
return [...this.#selected]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolves one exact model reference or creates an unresolved request.
|
|
76
|
+
* @param {{ name: string, mediaType: string }} reference Model reference.
|
|
77
|
+
* @returns {object} Canonical scene asset.
|
|
78
|
+
*/
|
|
79
|
+
resolve(reference) {
|
|
80
|
+
const wanted = Scene3dModelReference.normalizedPath(reference.name)
|
|
81
|
+
if (this.#byReference.has(wanted)) {
|
|
82
|
+
return this.#byReference.get(wanted)
|
|
83
|
+
}
|
|
84
|
+
const entry = this.#byAlias.get(wanted)
|
|
85
|
+
if (entry === null) {
|
|
86
|
+
throw new ToolkitError(
|
|
87
|
+
`Scene asset reference is ambiguous: ${reference.name}.`,
|
|
88
|
+
{
|
|
89
|
+
code: 'ERR_ASSET_AMBIGUOUS',
|
|
90
|
+
category: 'unsupported',
|
|
91
|
+
source: reference.name,
|
|
92
|
+
details: { name: reference.name }
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
const asset = entry
|
|
97
|
+
? this.#materialize(entry)
|
|
98
|
+
: ToolkitAsset.create({
|
|
99
|
+
kind: 'model3d',
|
|
100
|
+
name: reference.name,
|
|
101
|
+
mediaType: reference.mediaType,
|
|
102
|
+
data: null,
|
|
103
|
+
source: { uri: reference.name }
|
|
104
|
+
})
|
|
105
|
+
const identity = entry?.asset || `unresolved:${wanted}`
|
|
106
|
+
this.#claim(asset, identity)
|
|
107
|
+
this.#byReference.set(wanted, asset)
|
|
108
|
+
return asset
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Copies one matched payload only after both byte limits pass.
|
|
113
|
+
* @param {{ asset: object, metadata: object }} entry Indexed asset entry.
|
|
114
|
+
* @returns {object} Canonical copied asset.
|
|
115
|
+
*/
|
|
116
|
+
#materialize(entry) {
|
|
117
|
+
const cached = this.#byAsset.get(entry.asset)
|
|
118
|
+
if (cached) return cached
|
|
119
|
+
const bytes = CircuitJsonReadOnlyDocument.assetPayloadByteLength(
|
|
120
|
+
entry.asset
|
|
121
|
+
)
|
|
122
|
+
if (bytes > this.#options.maxAssetBytes) {
|
|
123
|
+
throw Scene3dAssetIndex.#limitError(
|
|
124
|
+
entry.metadata,
|
|
125
|
+
bytes,
|
|
126
|
+
this.#options.maxAssetBytes,
|
|
127
|
+
'asset'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
if (this.#totalBytes + bytes > this.#options.maxTotalAssetBytes) {
|
|
131
|
+
throw Scene3dAssetIndex.#limitError(
|
|
132
|
+
entry.metadata,
|
|
133
|
+
this.#totalBytes + bytes,
|
|
134
|
+
this.#options.maxTotalAssetBytes,
|
|
135
|
+
'total'
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
const result = ToolkitAsset.create(
|
|
139
|
+
CircuitJsonReadOnlyDocument.copyAssetFields(entry.asset)
|
|
140
|
+
)
|
|
141
|
+
this.#totalBytes += bytes
|
|
142
|
+
this.#byAsset.set(entry.asset, result)
|
|
143
|
+
return result
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Adds a unique selected asset and rejects deterministic-id collisions.
|
|
148
|
+
* @param {object} asset Canonical scene asset.
|
|
149
|
+
* @param {object | string} identity Source identity.
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
#claim(asset, identity) {
|
|
153
|
+
if (this.#claims.has(asset.id)) {
|
|
154
|
+
if (this.#claims.get(asset.id) !== identity) {
|
|
155
|
+
throw Scene3dAssetIndex.#ambiguousId(asset.id)
|
|
156
|
+
}
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (this.#selected.length >= Scene3dOptions.maxAssetCount) {
|
|
160
|
+
throw new ToolkitError(
|
|
161
|
+
'Scene asset count exceeds the safe limit.',
|
|
162
|
+
{
|
|
163
|
+
code: 'ERR_ASSET_LIMIT',
|
|
164
|
+
category: 'unsupported',
|
|
165
|
+
details: {
|
|
166
|
+
count: this.#selected.length + 1,
|
|
167
|
+
maximum: Scene3dOptions.maxAssetCount
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
this.#claims.set(asset.id, identity)
|
|
173
|
+
this.#selected.push(asset)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Builds exact normalized aliases without basename guessing.
|
|
178
|
+
* @param {object} asset Canonical asset metadata.
|
|
179
|
+
* @returns {Set<string>} Alias set.
|
|
180
|
+
*/
|
|
181
|
+
static #aliases(asset) {
|
|
182
|
+
return new Set(
|
|
183
|
+
[
|
|
184
|
+
asset.name,
|
|
185
|
+
asset.source.entryName,
|
|
186
|
+
asset.source.projectRelativePath,
|
|
187
|
+
asset.source.project_relative_path,
|
|
188
|
+
asset.source.relativePath,
|
|
189
|
+
asset.source.url,
|
|
190
|
+
asset.source.uri
|
|
191
|
+
]
|
|
192
|
+
.map((value) => Scene3dModelReference.normalizedPath(value))
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Retains only scalar identity and exact path aliases for eager indexing.
|
|
199
|
+
* @param {Record<string, any>} fields Descriptor-safe asset fields.
|
|
200
|
+
* @returns {{ id: string, name: string, source: Record<string, string> }} Lightweight lookup metadata.
|
|
201
|
+
*/
|
|
202
|
+
static #lookupMetadata(fields) {
|
|
203
|
+
const source = Object.create(null)
|
|
204
|
+
const candidate = fields.source
|
|
205
|
+
if (candidate && typeof candidate === 'object') {
|
|
206
|
+
for (const key of [
|
|
207
|
+
'entryName',
|
|
208
|
+
'projectRelativePath',
|
|
209
|
+
'project_relative_path',
|
|
210
|
+
'relativePath',
|
|
211
|
+
'url',
|
|
212
|
+
'uri'
|
|
213
|
+
]) {
|
|
214
|
+
let descriptor
|
|
215
|
+
try {
|
|
216
|
+
descriptor = Object.getOwnPropertyDescriptor(candidate, key)
|
|
217
|
+
} catch {
|
|
218
|
+
throw new TypeError(
|
|
219
|
+
'Canonical asset source aliases could not be inspected safely.'
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
if (!descriptor) continue
|
|
223
|
+
if (!Object.hasOwn(descriptor, 'value')) {
|
|
224
|
+
throw new TypeError(
|
|
225
|
+
'Canonical asset source aliases must be data properties.'
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
source[key] = Scene3dAssetIndex.#scalarText(
|
|
229
|
+
descriptor.value,
|
|
230
|
+
''
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
id: Scene3dAssetIndex.#scalarText(fields.id, ''),
|
|
236
|
+
name: Scene3dAssetIndex.#scalarText(fields.name, ''),
|
|
237
|
+
source
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Converts an already validated primitive without object coercion.
|
|
243
|
+
* @param {unknown} value Primitive candidate.
|
|
244
|
+
* @param {string} fallback Fallback string.
|
|
245
|
+
* @returns {string} Primitive text.
|
|
246
|
+
*/
|
|
247
|
+
static #scalarText(value, fallback) {
|
|
248
|
+
if (!value) return fallback
|
|
249
|
+
if (!['string', 'number', 'boolean', 'bigint'].includes(typeof value)) {
|
|
250
|
+
return fallback
|
|
251
|
+
}
|
|
252
|
+
return String(value)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Creates an asset-id ambiguity failure.
|
|
257
|
+
* @param {string} assetId Colliding canonical id.
|
|
258
|
+
* @returns {ToolkitError} Typed failure.
|
|
259
|
+
*/
|
|
260
|
+
static #ambiguousId(assetId) {
|
|
261
|
+
return new ToolkitError(`Scene asset id is ambiguous: ${assetId}.`, {
|
|
262
|
+
code: 'ERR_ASSET_AMBIGUOUS',
|
|
263
|
+
category: 'unsupported',
|
|
264
|
+
details: { assetId }
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Creates a synchronous asset-size failure.
|
|
270
|
+
* @param {object} asset Canonical asset metadata.
|
|
271
|
+
* @param {number} actual Selected bytes.
|
|
272
|
+
* @param {number} maximum Configured byte maximum.
|
|
273
|
+
* @param {'asset' | 'total'} scope Limit scope.
|
|
274
|
+
* @returns {ToolkitError} Typed failure.
|
|
275
|
+
*/
|
|
276
|
+
static #limitError(asset, actual, maximum, scope) {
|
|
277
|
+
return new ToolkitError('Scene asset bytes exceed the safe limit.', {
|
|
278
|
+
code: 'ERR_ASSET_LIMIT',
|
|
279
|
+
category: 'unsupported',
|
|
280
|
+
source: asset.name,
|
|
281
|
+
details: { assetId: asset.id, scope, actual, maximum }
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
}
|