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,412 @@
|
|
|
1
|
+
import { ToolkitError } from '../contracts/ToolkitError.mjs'
|
|
2
|
+
import { RuntimeProxyBoundary } from '../contracts/RuntimeProxyBoundary.mjs'
|
|
3
|
+
import { WorkerRequestData } from './WorkerRequestData.mjs'
|
|
4
|
+
|
|
5
|
+
export const TOOLKIT_WORKER_PROTOCOL = 'ecad-toolkit.worker.v1'
|
|
6
|
+
|
|
7
|
+
const OPERATIONS = new Set(['parse', 'loadProject'])
|
|
8
|
+
const MESSAGE_EVENT_DATA_GETTER =
|
|
9
|
+
typeof MessageEvent === 'function'
|
|
10
|
+
? Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'data')?.get
|
|
11
|
+
: null
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Installs and validates the shared clone-safe toolkit worker protocol.
|
|
15
|
+
*/
|
|
16
|
+
export class ToolkitWorkerProtocol {
|
|
17
|
+
/**
|
|
18
|
+
* Installs request, progress, result, error, and cancellation dispatch.
|
|
19
|
+
* @param {unknown} scope Worker-like message scope.
|
|
20
|
+
* @param {unknown} handlers Operation handlers.
|
|
21
|
+
* @returns {{ dispose: () => void }} Installation controller.
|
|
22
|
+
*/
|
|
23
|
+
static install(scope, handlers) {
|
|
24
|
+
const addEventListener = ToolkitWorkerProtocol.#method(
|
|
25
|
+
scope,
|
|
26
|
+
'addEventListener'
|
|
27
|
+
)
|
|
28
|
+
const removeEventListener = ToolkitWorkerProtocol.#method(
|
|
29
|
+
scope,
|
|
30
|
+
'removeEventListener'
|
|
31
|
+
)
|
|
32
|
+
const postMessage = ToolkitWorkerProtocol.#method(scope, 'postMessage')
|
|
33
|
+
const reportError = ToolkitWorkerProtocol.#method(scope, 'reportError')
|
|
34
|
+
if (!addEventListener || !postMessage) {
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
'Toolkit worker scope must expose message event and post methods.'
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
const normalizedHandlers = ToolkitWorkerProtocol.#handlers(handlers)
|
|
40
|
+
const controllers = new Map()
|
|
41
|
+
let disposed = false
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Posts one response and any worker-owned transferable buffers.
|
|
45
|
+
* @param {unknown} message Outgoing message.
|
|
46
|
+
* @param {Transferable[]} [transfer] Transfer list.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
const post = (message, transfer = []) => {
|
|
50
|
+
postMessage(message, transfer)
|
|
51
|
+
}
|
|
52
|
+
/** @param {{ data?: unknown }} event Worker message event. @returns {void} */
|
|
53
|
+
const onMessage = (event) => {
|
|
54
|
+
if (disposed) return
|
|
55
|
+
ToolkitWorkerProtocol.#dispatch(
|
|
56
|
+
ToolkitWorkerProtocol.#eventData(event),
|
|
57
|
+
normalizedHandlers,
|
|
58
|
+
controllers,
|
|
59
|
+
post
|
|
60
|
+
).catch((error) => {
|
|
61
|
+
if (reportError) {
|
|
62
|
+
reportError(error)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
queueMicrotask(() => {
|
|
66
|
+
throw error
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
addEventListener('message', onMessage)
|
|
71
|
+
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
/** Stops dispatch and aborts active protocol operations. */
|
|
74
|
+
dispose() {
|
|
75
|
+
if (disposed) return
|
|
76
|
+
disposed = true
|
|
77
|
+
if (removeEventListener) {
|
|
78
|
+
removeEventListener('message', onMessage)
|
|
79
|
+
}
|
|
80
|
+
for (const controller of controllers.values()) {
|
|
81
|
+
controller.abort()
|
|
82
|
+
}
|
|
83
|
+
controllers.clear()
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Dispatches one data-only protocol message.
|
|
90
|
+
* @param {unknown} candidate Incoming message candidate.
|
|
91
|
+
* @param {Record<string, Function>} handlers Normalized handlers.
|
|
92
|
+
* @param {Map<string, AbortController>} controllers Active controllers.
|
|
93
|
+
* @param {(message: object, transfer?: Transferable[]) => void} post Outgoing post function.
|
|
94
|
+
* @returns {Promise<void>} Dispatch completion.
|
|
95
|
+
*/
|
|
96
|
+
static async #dispatch(candidate, handlers, controllers, post) {
|
|
97
|
+
let message
|
|
98
|
+
let requestId = ''
|
|
99
|
+
try {
|
|
100
|
+
message = ToolkitWorkerProtocol.#record(candidate, 'Worker message')
|
|
101
|
+
requestId = ToolkitWorkerProtocol.#requestId(message.requestId)
|
|
102
|
+
if (message.protocol !== TOOLKIT_WORKER_PROTOCOL) {
|
|
103
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
104
|
+
'Unknown toolkit worker protocol.',
|
|
105
|
+
requestId
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
const type = message.type
|
|
109
|
+
if (typeof type !== 'string') {
|
|
110
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
111
|
+
'Toolkit worker message type is invalid.',
|
|
112
|
+
requestId
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (type === 'cancel') {
|
|
116
|
+
ToolkitWorkerProtocol.#keys(
|
|
117
|
+
message,
|
|
118
|
+
['protocol', 'type', 'requestId'],
|
|
119
|
+
requestId
|
|
120
|
+
)
|
|
121
|
+
const controller = controllers.get(requestId)
|
|
122
|
+
if (controller) {
|
|
123
|
+
controllers.delete(requestId)
|
|
124
|
+
controller.abort()
|
|
125
|
+
}
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (!OPERATIONS.has(type) || !handlers[type]) {
|
|
129
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
130
|
+
'Unknown toolkit worker operation.',
|
|
131
|
+
requestId
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
const valueField = type === 'parse' ? 'input' : 'entries'
|
|
135
|
+
ToolkitWorkerProtocol.#keys(
|
|
136
|
+
message,
|
|
137
|
+
['protocol', 'type', 'requestId', valueField, 'options'],
|
|
138
|
+
requestId
|
|
139
|
+
)
|
|
140
|
+
if (!Object.hasOwn(message, valueField)) {
|
|
141
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
142
|
+
'Toolkit worker request payload is missing.',
|
|
143
|
+
requestId
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
if (controllers.has(requestId)) {
|
|
147
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
148
|
+
'Toolkit worker request id is already active.',
|
|
149
|
+
requestId
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const payload = {
|
|
154
|
+
[valueField]: message[valueField],
|
|
155
|
+
options: Object.hasOwn(message, 'options')
|
|
156
|
+
? message.options
|
|
157
|
+
: {}
|
|
158
|
+
}
|
|
159
|
+
WorkerRequestData.assertCloneSafe(payload)
|
|
160
|
+
const controller = new AbortController()
|
|
161
|
+
controllers.set(requestId, controller)
|
|
162
|
+
try {
|
|
163
|
+
const value = await handlers[type](payload, {
|
|
164
|
+
signal: controller.signal,
|
|
165
|
+
onProgress: (progress) => {
|
|
166
|
+
if (controller.signal.aborted) return
|
|
167
|
+
post({
|
|
168
|
+
protocol: TOOLKIT_WORKER_PROTOCOL,
|
|
169
|
+
type: 'progress',
|
|
170
|
+
requestId,
|
|
171
|
+
progress
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
if (controllers.get(requestId) !== controller) return
|
|
176
|
+
const prepared = WorkerRequestData.prepareResult(value)
|
|
177
|
+
post(
|
|
178
|
+
{
|
|
179
|
+
protocol: TOOLKIT_WORKER_PROTOCOL,
|
|
180
|
+
type: 'result',
|
|
181
|
+
requestId,
|
|
182
|
+
value: prepared.value
|
|
183
|
+
},
|
|
184
|
+
prepared.transfer
|
|
185
|
+
)
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (controllers.get(requestId) !== controller) return
|
|
188
|
+
ToolkitWorkerProtocol.#postError(post, requestId, error)
|
|
189
|
+
} finally {
|
|
190
|
+
if (controllers.get(requestId) === controller) {
|
|
191
|
+
controllers.delete(requestId)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
ToolkitWorkerProtocol.#postError(post, requestId, error)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Posts only canonical ToolkitError fields.
|
|
201
|
+
* @param {(message: object, transfer?: Transferable[]) => void} post Outgoing post function.
|
|
202
|
+
* @param {string} requestId Request id.
|
|
203
|
+
* @param {unknown} error Failure candidate.
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
static #postError(post, requestId, error) {
|
|
207
|
+
const trusted = ToolkitError.trustedRecord(error)
|
|
208
|
+
const normalized =
|
|
209
|
+
trusted ||
|
|
210
|
+
new ToolkitError('Toolkit worker operation failed.', {
|
|
211
|
+
code: 'ERR_WORKER_RUNTIME',
|
|
212
|
+
category: 'runtime',
|
|
213
|
+
cause: error
|
|
214
|
+
}).toJSON()
|
|
215
|
+
post({
|
|
216
|
+
protocol: TOOLKIT_WORKER_PROTOCOL,
|
|
217
|
+
type: 'error',
|
|
218
|
+
requestId,
|
|
219
|
+
error: normalized,
|
|
220
|
+
diagnostics: []
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Requires an exact canonical message field set.
|
|
226
|
+
* @param {Record<string, any>} message Message fields.
|
|
227
|
+
* @param {string[]} allowed Allowed names.
|
|
228
|
+
* @param {string} requestId Request id.
|
|
229
|
+
* @returns {void}
|
|
230
|
+
*/
|
|
231
|
+
static #keys(message, allowed, requestId) {
|
|
232
|
+
const keys = Object.keys(message)
|
|
233
|
+
if (
|
|
234
|
+
keys.length !== allowed.length ||
|
|
235
|
+
allowed.some((key) => !Object.hasOwn(message, key))
|
|
236
|
+
) {
|
|
237
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
238
|
+
'Toolkit worker message fields are invalid.',
|
|
239
|
+
requestId
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Reads a genuine MessageEvent or a plain data-only test record.
|
|
246
|
+
* @param {unknown} event Message event candidate.
|
|
247
|
+
* @returns {unknown} Event data or undefined.
|
|
248
|
+
*/
|
|
249
|
+
static #eventData(event) {
|
|
250
|
+
try {
|
|
251
|
+
RuntimeProxyBoundary.assert(event, 'Worker message event')
|
|
252
|
+
} catch {
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
if (!event || !['object', 'function'].includes(typeof event)) {
|
|
256
|
+
return undefined
|
|
257
|
+
}
|
|
258
|
+
if (MESSAGE_EVENT_DATA_GETTER) {
|
|
259
|
+
try {
|
|
260
|
+
return Reflect.apply(MESSAGE_EVENT_DATA_GETTER, event, [])
|
|
261
|
+
} catch {
|
|
262
|
+
// Plain worker test records are handled below.
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
let owner = event
|
|
266
|
+
for (let depth = 0; owner && depth < 16; depth += 1) {
|
|
267
|
+
let descriptor
|
|
268
|
+
try {
|
|
269
|
+
descriptor = Object.getOwnPropertyDescriptor(owner, 'data')
|
|
270
|
+
owner = Object.getPrototypeOf(owner)
|
|
271
|
+
} catch {
|
|
272
|
+
return undefined
|
|
273
|
+
}
|
|
274
|
+
if (!descriptor) continue
|
|
275
|
+
return Object.hasOwn(descriptor, 'value')
|
|
276
|
+
? descriptor.value
|
|
277
|
+
: undefined
|
|
278
|
+
}
|
|
279
|
+
return undefined
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Normalizes supported operation handlers without invoking accessors.
|
|
284
|
+
* @param {unknown} value Handler record.
|
|
285
|
+
* @returns {Record<string, Function>} Bound operation handlers.
|
|
286
|
+
*/
|
|
287
|
+
static #handlers(value) {
|
|
288
|
+
const fields = ToolkitWorkerProtocol.#record(
|
|
289
|
+
value,
|
|
290
|
+
'Worker protocol handlers'
|
|
291
|
+
)
|
|
292
|
+
const handlers = Object.create(null)
|
|
293
|
+
for (const operation of OPERATIONS) {
|
|
294
|
+
if (fields[operation] === undefined) continue
|
|
295
|
+
if (typeof fields[operation] !== 'function') {
|
|
296
|
+
throw new TypeError(
|
|
297
|
+
`Toolkit worker ${operation} handler must be a function.`
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
handlers[operation] = fields[operation]
|
|
301
|
+
}
|
|
302
|
+
return handlers
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Reads an accessor-free plain record.
|
|
307
|
+
* @param {unknown} value Record candidate.
|
|
308
|
+
* @param {string} label Human-readable label.
|
|
309
|
+
* @returns {Record<string, any>} Null-prototype field map.
|
|
310
|
+
*/
|
|
311
|
+
static #record(value, label) {
|
|
312
|
+
try {
|
|
313
|
+
RuntimeProxyBoundary.assert(value, label)
|
|
314
|
+
} catch {
|
|
315
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
316
|
+
`${label} must not be a Proxy.`
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
320
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
321
|
+
`${label} must be a plain object.`
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
let prototype
|
|
325
|
+
let descriptors
|
|
326
|
+
try {
|
|
327
|
+
prototype = Object.getPrototypeOf(value)
|
|
328
|
+
descriptors = Object.getOwnPropertyDescriptors(value)
|
|
329
|
+
} catch {
|
|
330
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
331
|
+
`${label} could not be inspected safely.`
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
335
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
336
|
+
`${label} must be a plain object.`
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
const result = Object.create(null)
|
|
340
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
341
|
+
const descriptor = descriptors[key]
|
|
342
|
+
if (
|
|
343
|
+
typeof key !== 'string' ||
|
|
344
|
+
descriptor.get ||
|
|
345
|
+
descriptor.set ||
|
|
346
|
+
descriptor.enumerable !== true
|
|
347
|
+
) {
|
|
348
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
349
|
+
`${label} may contain only enumerable data properties.`
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
result[key] = descriptor.value
|
|
353
|
+
}
|
|
354
|
+
return result
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Resolves one callable method without reading it through an accessor.
|
|
359
|
+
* @param {unknown} target Method owner.
|
|
360
|
+
* @param {string} name Method name.
|
|
361
|
+
* @returns {Function | null} Invocation closure.
|
|
362
|
+
*/
|
|
363
|
+
static #method(target, name) {
|
|
364
|
+
if (!target || !['object', 'function'].includes(typeof target)) {
|
|
365
|
+
return null
|
|
366
|
+
}
|
|
367
|
+
let owner = target
|
|
368
|
+
for (let depth = 0; owner && depth < 16; depth += 1) {
|
|
369
|
+
let descriptor
|
|
370
|
+
try {
|
|
371
|
+
descriptor = Object.getOwnPropertyDescriptor(owner, name)
|
|
372
|
+
owner = Object.getPrototypeOf(owner)
|
|
373
|
+
} catch {
|
|
374
|
+
return null
|
|
375
|
+
}
|
|
376
|
+
if (!descriptor) continue
|
|
377
|
+
if (descriptor.get || descriptor.set) return null
|
|
378
|
+
if (typeof descriptor.value !== 'function') return null
|
|
379
|
+
const method = descriptor.value
|
|
380
|
+
return (...args) => Reflect.apply(method, target, args)
|
|
381
|
+
}
|
|
382
|
+
return null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Normalizes a required bounded request id.
|
|
387
|
+
* @param {unknown} value Id candidate.
|
|
388
|
+
* @returns {string} Request id.
|
|
389
|
+
*/
|
|
390
|
+
static #requestId(value) {
|
|
391
|
+
if (typeof value !== 'string' || !value || value.length > 256) {
|
|
392
|
+
throw ToolkitWorkerProtocol.#messageError(
|
|
393
|
+
'Toolkit worker request id is invalid.'
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
return value
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Creates a typed invalid-message failure.
|
|
401
|
+
* @param {string} message Failure message.
|
|
402
|
+
* @param {string} [requestId] Request id.
|
|
403
|
+
* @returns {ToolkitError} Typed worker failure.
|
|
404
|
+
*/
|
|
405
|
+
static #messageError(message, requestId = '') {
|
|
406
|
+
return new ToolkitError(message, {
|
|
407
|
+
code: 'ERR_WORKER_MESSAGE',
|
|
408
|
+
category: 'validation',
|
|
409
|
+
details: requestId ? { requestId } : {}
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
}
|