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