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,363 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from '../CircuitJsonUnits.mjs'
|
|
2
|
+
import { ToolkitError } from '../contracts/ToolkitError.mjs'
|
|
3
|
+
|
|
4
|
+
const FIDELITIES = new Set(['auto', 'canonical', 'native'])
|
|
5
|
+
const DEFAULT_BOARD_THICKNESS_MM = 1.6
|
|
6
|
+
const DEFAULT_MAX_ASSET_BYTES = 256 * 1024 * 1024
|
|
7
|
+
const DEFAULT_MAX_TOTAL_ASSET_BYTES = 512 * 1024 * 1024
|
|
8
|
+
const DEFAULT_RESOLVE_CONCURRENCY = 8
|
|
9
|
+
const MAX_ASSET_COUNT = 10_000
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes bounded options shared by the canonical scene services.
|
|
13
|
+
*/
|
|
14
|
+
export class Scene3dOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes one public scene request without invoking accessors.
|
|
17
|
+
* @param {unknown} [options] Scene options candidate.
|
|
18
|
+
* @returns {{ fidelity: 'auto' | 'canonical' | 'native', boardThickness: number, boardThicknessProvided: boolean, resolveAsset: Function | null, signal: AbortSignal | null, onProgress: Function | null, maxAssetBytes: number, maxTotalAssetBytes: number, resolveConcurrency: number }} Normalized options.
|
|
19
|
+
*/
|
|
20
|
+
static normalize(options = {}) {
|
|
21
|
+
const fields = Scene3dOptions.dataRecord(options, 'Scene options')
|
|
22
|
+
const fidelityValue = fields.fidelity ?? 'auto'
|
|
23
|
+
if (typeof fidelityValue !== 'string') {
|
|
24
|
+
throw new TypeError('Scene fidelity must be a string.')
|
|
25
|
+
}
|
|
26
|
+
const fidelity = fidelityValue
|
|
27
|
+
if (!FIDELITIES.has(fidelity)) {
|
|
28
|
+
throw new ToolkitError(
|
|
29
|
+
`Unsupported scene fidelity: ${fidelity || '(empty)'}.`,
|
|
30
|
+
{
|
|
31
|
+
code: 'ERR_OPTION_INVALID',
|
|
32
|
+
category: 'unsupported'
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const boardThicknessValue = fields.boardThickness
|
|
38
|
+
if (
|
|
39
|
+
boardThicknessValue !== undefined &&
|
|
40
|
+
typeof boardThicknessValue !== 'number' &&
|
|
41
|
+
typeof boardThicknessValue !== 'string'
|
|
42
|
+
) {
|
|
43
|
+
throw new TypeError(
|
|
44
|
+
'Scene boardThickness must be a number or unit string.'
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
const boardThickness =
|
|
48
|
+
boardThicknessValue === undefined
|
|
49
|
+
? DEFAULT_BOARD_THICKNESS_MM
|
|
50
|
+
: CircuitJsonUnits.optionalLength(boardThicknessValue)
|
|
51
|
+
if (
|
|
52
|
+
boardThickness === null ||
|
|
53
|
+
boardThickness <= 0 ||
|
|
54
|
+
boardThickness > 1000
|
|
55
|
+
) {
|
|
56
|
+
throw new ToolkitError(
|
|
57
|
+
'Scene boardThickness must be a positive millimeter value no greater than 1000.',
|
|
58
|
+
{
|
|
59
|
+
code: 'ERR_OPTION_INVALID',
|
|
60
|
+
category: 'unsupported'
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolveAsset = Scene3dOptions.#optionalFunction(
|
|
66
|
+
fields.resolveAsset,
|
|
67
|
+
'resolveAsset'
|
|
68
|
+
)
|
|
69
|
+
const onProgress = Scene3dOptions.#optionalFunction(
|
|
70
|
+
fields.onProgress,
|
|
71
|
+
'onProgress'
|
|
72
|
+
)
|
|
73
|
+
const signal = Scene3dOptions.#signal(fields.signal)
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
fidelity,
|
|
77
|
+
boardThickness,
|
|
78
|
+
boardThicknessProvided: fields.boardThickness !== undefined,
|
|
79
|
+
resolveAsset,
|
|
80
|
+
signal,
|
|
81
|
+
onProgress,
|
|
82
|
+
maxAssetBytes: Scene3dOptions.#positiveInteger(
|
|
83
|
+
fields.maxAssetBytes,
|
|
84
|
+
DEFAULT_MAX_ASSET_BYTES,
|
|
85
|
+
Number.MAX_SAFE_INTEGER,
|
|
86
|
+
'maxAssetBytes'
|
|
87
|
+
),
|
|
88
|
+
maxTotalAssetBytes: Scene3dOptions.#positiveInteger(
|
|
89
|
+
fields.maxTotalAssetBytes,
|
|
90
|
+
DEFAULT_MAX_TOTAL_ASSET_BYTES,
|
|
91
|
+
Number.MAX_SAFE_INTEGER,
|
|
92
|
+
'maxTotalAssetBytes'
|
|
93
|
+
),
|
|
94
|
+
resolveConcurrency: Scene3dOptions.#positiveInteger(
|
|
95
|
+
fields.resolveConcurrency,
|
|
96
|
+
DEFAULT_RESOLVE_CONCURRENCY,
|
|
97
|
+
DEFAULT_RESOLVE_CONCURRENCY,
|
|
98
|
+
'resolveConcurrency'
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns a null-prototype map of own data properties.
|
|
105
|
+
* @param {unknown} value Record candidate.
|
|
106
|
+
* @param {string} label Human-readable record label.
|
|
107
|
+
* @returns {Record<string, any>} Data-property values.
|
|
108
|
+
*/
|
|
109
|
+
static dataRecord(value, label) {
|
|
110
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
111
|
+
throw new TypeError(`${label} must be a plain object.`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let prototype
|
|
115
|
+
try {
|
|
116
|
+
prototype = Object.getPrototypeOf(value)
|
|
117
|
+
} catch {
|
|
118
|
+
throw new TypeError(
|
|
119
|
+
`${label} must expose a plain object prototype.`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
123
|
+
throw new TypeError(`${label} must be a plain object.`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = Object.create(null)
|
|
127
|
+
let keys
|
|
128
|
+
try {
|
|
129
|
+
keys = Reflect.ownKeys(value)
|
|
130
|
+
} catch {
|
|
131
|
+
throw new TypeError(`${label} keys could not be inspected safely.`)
|
|
132
|
+
}
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
if (typeof key !== 'string') {
|
|
135
|
+
throw new TypeError(`${label} may contain only string keys.`)
|
|
136
|
+
}
|
|
137
|
+
let descriptor
|
|
138
|
+
try {
|
|
139
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key)
|
|
140
|
+
} catch {
|
|
141
|
+
throw new TypeError(
|
|
142
|
+
`${label} properties could not be inspected safely.`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value')) {
|
|
146
|
+
throw new TypeError(
|
|
147
|
+
`${label} may contain only data properties.`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
result[key] = descriptor.value
|
|
151
|
+
}
|
|
152
|
+
return result
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Copies one dense plain array by descriptors without invoking accessors.
|
|
157
|
+
* @param {unknown} value Array candidate.
|
|
158
|
+
* @param {string} label Human-readable array label.
|
|
159
|
+
* @param {number} maximum Maximum item count.
|
|
160
|
+
* @returns {any[]} Dense data-property values.
|
|
161
|
+
*/
|
|
162
|
+
static dataArray(value, label, maximum) {
|
|
163
|
+
if (!Array.isArray(value)) {
|
|
164
|
+
throw new TypeError(`${label} must be an array.`)
|
|
165
|
+
}
|
|
166
|
+
let prototype
|
|
167
|
+
let keys
|
|
168
|
+
try {
|
|
169
|
+
prototype = Object.getPrototypeOf(value)
|
|
170
|
+
keys = Reflect.ownKeys(value)
|
|
171
|
+
} catch {
|
|
172
|
+
throw new TypeError(`${label} could not be inspected safely.`)
|
|
173
|
+
}
|
|
174
|
+
if (prototype !== Array.prototype) {
|
|
175
|
+
throw new TypeError(`${label} must be a plain array.`)
|
|
176
|
+
}
|
|
177
|
+
let lengthDescriptor
|
|
178
|
+
try {
|
|
179
|
+
lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length')
|
|
180
|
+
} catch {
|
|
181
|
+
throw new TypeError(
|
|
182
|
+
`${label} length could not be inspected safely.`
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
const length = lengthDescriptor?.value
|
|
186
|
+
if (!Number.isSafeInteger(length) || length < 0) {
|
|
187
|
+
throw new TypeError(`${label} length must be a safe integer.`)
|
|
188
|
+
}
|
|
189
|
+
if (length > maximum) {
|
|
190
|
+
throw new ToolkitError(`${label} count exceeds the safe limit.`, {
|
|
191
|
+
code: 'ERR_ASSET_LIMIT',
|
|
192
|
+
category: 'unsupported',
|
|
193
|
+
details: { count: length, maximum }
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
if (keys.length !== length + 1) {
|
|
197
|
+
throw new TypeError(
|
|
198
|
+
`${label} must be dense and contain no custom properties.`
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = new Array(length)
|
|
203
|
+
for (let index = 0; index < length; index += 1) {
|
|
204
|
+
let descriptor
|
|
205
|
+
try {
|
|
206
|
+
descriptor = Object.getOwnPropertyDescriptor(
|
|
207
|
+
value,
|
|
208
|
+
String(index)
|
|
209
|
+
)
|
|
210
|
+
} catch {
|
|
211
|
+
throw new TypeError(
|
|
212
|
+
`${label} items could not be inspected safely.`
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value')) {
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
`${label} must contain only dense data properties.`
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
result[index] = descriptor.value
|
|
221
|
+
}
|
|
222
|
+
return result
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Rejects an already-aborted request with the shared typed cancellation.
|
|
227
|
+
* @param {AbortSignal | null} signal Cancellation signal.
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
static assertNotAborted(signal) {
|
|
231
|
+
if (!signal || !Scene3dOptions.#isAborted(signal)) return
|
|
232
|
+
throw Scene3dOptions.#cancelled()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Awaits a promise while allowing a genuine AbortSignal to win promptly.
|
|
237
|
+
* @param {unknown} value Promise or value to await.
|
|
238
|
+
* @param {AbortSignal | null} signal Cancellation signal.
|
|
239
|
+
* @returns {Promise<any>} Settled value.
|
|
240
|
+
*/
|
|
241
|
+
static async awaitWithSignal(value, signal) {
|
|
242
|
+
if (!signal) return await value
|
|
243
|
+
Scene3dOptions.assertNotAborted(signal)
|
|
244
|
+
return await new Promise((resolve, reject) => {
|
|
245
|
+
let settled = false
|
|
246
|
+
const remove = () => {
|
|
247
|
+
EventTarget.prototype.removeEventListener.call(
|
|
248
|
+
signal,
|
|
249
|
+
'abort',
|
|
250
|
+
onAbort
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
const finish = (callback, result) => {
|
|
254
|
+
if (settled) return
|
|
255
|
+
settled = true
|
|
256
|
+
remove()
|
|
257
|
+
callback(result)
|
|
258
|
+
}
|
|
259
|
+
const onAbort = () => finish(reject, Scene3dOptions.#cancelled())
|
|
260
|
+
EventTarget.prototype.addEventListener.call(
|
|
261
|
+
signal,
|
|
262
|
+
'abort',
|
|
263
|
+
onAbort,
|
|
264
|
+
{ once: true }
|
|
265
|
+
)
|
|
266
|
+
Promise.resolve(value).then(
|
|
267
|
+
(result) => finish(resolve, result),
|
|
268
|
+
(error) => finish(reject, error)
|
|
269
|
+
)
|
|
270
|
+
if (Scene3dOptions.#isAborted(signal)) onAbort()
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Normalizes one optional callback.
|
|
276
|
+
* @param {unknown} value Callback candidate.
|
|
277
|
+
* @param {string} name Option name.
|
|
278
|
+
* @returns {Function | null} Callback or null.
|
|
279
|
+
*/
|
|
280
|
+
static #optionalFunction(value, name) {
|
|
281
|
+
if (value === undefined) return null
|
|
282
|
+
if (typeof value !== 'function') {
|
|
283
|
+
throw new TypeError(`Scene ${name} must be a function.`)
|
|
284
|
+
}
|
|
285
|
+
return value
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Brand-checks one optional AbortSignal without trusting lookalikes.
|
|
290
|
+
* @param {unknown} value Signal candidate.
|
|
291
|
+
* @returns {AbortSignal | null} Signal or null.
|
|
292
|
+
*/
|
|
293
|
+
static #signal(value) {
|
|
294
|
+
if (value === undefined || value === null) return null
|
|
295
|
+
try {
|
|
296
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
297
|
+
AbortSignal.prototype,
|
|
298
|
+
'aborted'
|
|
299
|
+
)
|
|
300
|
+
if (typeof descriptor?.get !== 'function') throw new TypeError()
|
|
301
|
+
descriptor.get.call(value)
|
|
302
|
+
return /** @type {AbortSignal} */ (value)
|
|
303
|
+
} catch {
|
|
304
|
+
throw new TypeError('Scene signal must be an AbortSignal.')
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Normalizes a positive bounded integer option.
|
|
310
|
+
* @param {unknown} value Candidate value.
|
|
311
|
+
* @param {number} fallback Default value.
|
|
312
|
+
* @param {number} maximum Maximum accepted value.
|
|
313
|
+
* @param {string} name Option name.
|
|
314
|
+
* @returns {number} Normalized integer.
|
|
315
|
+
*/
|
|
316
|
+
static #positiveInteger(value, fallback, maximum, name) {
|
|
317
|
+
if (value === undefined) return fallback
|
|
318
|
+
if (typeof value !== 'number' && typeof value !== 'string') {
|
|
319
|
+
throw new TypeError(
|
|
320
|
+
`Scene ${name} must be a positive integer no greater than ${maximum}.`
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
const number = Number(value)
|
|
324
|
+
if (!Number.isSafeInteger(number) || number <= 0 || number > maximum) {
|
|
325
|
+
throw new TypeError(
|
|
326
|
+
`Scene ${name} must be a positive integer no greater than ${maximum}.`
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
return number
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Returns the maximum number of scene assets accepted per operation.
|
|
334
|
+
* @returns {number} Asset count ceiling.
|
|
335
|
+
*/
|
|
336
|
+
static get maxAssetCount() {
|
|
337
|
+
return MAX_ASSET_COUNT
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Reads aborted state through the platform brand-checked getter.
|
|
342
|
+
* @param {AbortSignal} signal Cancellation signal.
|
|
343
|
+
* @returns {boolean} Whether the signal is aborted.
|
|
344
|
+
*/
|
|
345
|
+
static #isAborted(signal) {
|
|
346
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
347
|
+
AbortSignal.prototype,
|
|
348
|
+
'aborted'
|
|
349
|
+
)
|
|
350
|
+
return Boolean(descriptor?.get?.call(signal))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Creates the shared scene cancellation error.
|
|
355
|
+
* @returns {ToolkitError} Typed cancellation.
|
|
356
|
+
*/
|
|
357
|
+
static #cancelled() {
|
|
358
|
+
return new ToolkitError('Scene preparation was cancelled.', {
|
|
359
|
+
code: 'ERR_CANCELLED',
|
|
360
|
+
category: 'cancelled'
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
}
|