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.
Files changed (155) hide show
  1. package/AGENTS.md +15 -0
  2. package/NOTICE.md +31 -0
  3. package/README.md +267 -107
  4. package/docs/api.md +501 -96
  5. package/docs/capabilities.md +70 -0
  6. package/docs/migration/behaviors.md +45 -0
  7. package/docs/migration/parser.md +60 -0
  8. package/docs/migration/renderers.md +515 -0
  9. package/docs/migration/root.md +740 -0
  10. package/docs/migration.md +120 -0
  11. package/docs/model-format.md +175 -57
  12. package/docs/provenance.md +206 -0
  13. package/docs/release-notes-v1.1.0.md +154 -0
  14. package/docs/testing.md +117 -7
  15. package/package.json +31 -5
  16. package/spec/api-baseline-v1.0.17.json +1 -0
  17. package/spec/baseline-provenance-v1.0.17.json +7 -0
  18. package/spec/circuitjson-schema-snapshot.json +321 -0
  19. package/spec/circuitjson-schema-source.json +28 -0
  20. package/spec/feature-preservation.json +1 -0
  21. package/spec/library-scope.md +27 -20
  22. package/src/capabilities.mjs +1 -0
  23. package/src/core/ArchiveEntryPath.mjs +93 -0
  24. package/src/core/ArchiveLimits.mjs +31 -0
  25. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  26. package/src/core/AsyncInputOwnership.mjs +56 -0
  27. package/src/core/AttachedValueLimits.mjs +67 -0
  28. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  29. package/src/core/CircuitJsonDocument.mjs +19 -61
  30. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  31. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  32. package/src/core/CircuitJsonIndexer.mjs +274 -194
  33. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  34. package/src/core/CircuitJsonParser.mjs +75 -13
  35. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  36. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  37. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  38. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  39. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  40. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  41. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  42. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  43. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  44. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  45. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  46. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  47. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  48. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  49. package/src/core/CircuitJsonUnits.mjs +13 -87
  50. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  51. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  52. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  53. package/src/core/ManufacturingService.mjs +323 -0
  54. package/src/core/Parser.mjs +343 -0
  55. package/src/core/ParserOptions.mjs +333 -0
  56. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  57. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  58. package/src/core/PcbInteractionIndex.mjs +368 -0
  59. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  60. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  61. package/src/core/ProjectLoader.mjs +975 -0
  62. package/src/core/SimulationService.mjs +790 -0
  63. package/src/core/ToolkitCapabilities.mjs +130 -0
  64. package/src/core/ZipArchiveInspector.mjs +649 -0
  65. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  66. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  67. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  68. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  69. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  70. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  71. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  72. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  73. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  74. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  75. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  76. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  77. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  78. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  79. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  80. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  81. package/src/core/contracts/DocumentResult.mjs +198 -0
  82. package/src/core/contracts/ProjectResult.mjs +96 -0
  83. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  84. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  85. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  86. package/src/core/contracts/ToolkitError.mjs +176 -0
  87. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  88. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  89. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  90. package/src/core/query/CircuitTraversal.mjs +343 -0
  91. package/src/core/query/ComponentGrouping.mjs +275 -0
  92. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  93. package/src/core/query/QueryService.mjs +435 -0
  94. package/src/core/query/RegexPattern.mjs +75 -0
  95. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  96. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  97. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  98. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  99. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  100. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  101. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  102. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  103. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  104. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  105. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  106. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  107. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  108. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  109. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  110. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  111. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  112. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  113. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  114. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  115. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  116. package/src/core/worker/WorkerRequestData.mjs +623 -0
  117. package/src/core/worker/WorkerResponseData.mjs +905 -0
  118. package/src/extensions.mjs +37 -0
  119. package/src/index.mjs +14 -9
  120. package/src/interaction.mjs +2 -0
  121. package/src/manufacturing.mjs +1 -0
  122. package/src/parser.mjs +12 -2
  123. package/src/project.mjs +5 -0
  124. package/src/query.mjs +1 -0
  125. package/src/renderers.mjs +3 -29
  126. package/src/scene3d.mjs +3 -0
  127. package/src/simulation.mjs +1 -0
  128. package/src/styles/renderers.css +24 -0
  129. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  130. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  131. package/src/testing/runToolkitContract.mjs +705 -0
  132. package/src/testing.mjs +3 -0
  133. package/src/ui/BomTableRenderer.mjs +304 -0
  134. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  135. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  136. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  137. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  138. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  139. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  140. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  141. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  142. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  143. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  144. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  145. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  146. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  147. package/src/ui/PcbSvgRenderer.mjs +41 -0
  148. package/src/ui/SafeSvgPaint.mjs +26 -0
  149. package/src/ui/SafeXmlText.mjs +60 -0
  150. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  151. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  152. package/src/ui/SchematicTableGeometry.mjs +319 -0
  153. package/src/ui/SchematicTextAnchor.mjs +55 -0
  154. package/src/ui/SchematicTextBounds.mjs +98 -0
  155. 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
+ }