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
package/docs/api.md CHANGED
@@ -1,143 +1,548 @@
1
- # API
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 André Fiedler
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
2
5
 
3
- The package exports public APIs from `circuitjson-toolkit` and
4
- `circuitjson-toolkit/parser`.
6
+ # Canonical API
7
+
8
+ Version 1.1.0 exposes one common API contract for `circuitjson-toolkit`,
9
+ `gerber-toolkit`, `altium-toolkit`, and `kicad-toolkit`. CircuitJSON is the
10
+ shared immutable model. Every common service accepts a canonical
11
+ `DocumentResult`, its `model` array, or a prepared `CircuitJsonDocumentContext`
12
+ unless a narrower input is stated below.
13
+
14
+ The 1.1.0 convergence is intentionally breaking. Use the canonical classes in
15
+ this document for new code. Thirty-seven previous CircuitJSON-specific classes
16
+ remain under `circuitjson-toolkit/extensions`; the three documented viewer
17
+ compatibility classes remain on the root. See [migration.md](migration.md).
18
+
19
+ ## Common conventions
20
+
21
+ ### Document input
22
+
23
+ Parser calls use an input record:
24
+
25
+ ```js
26
+ {
27
+ fileName: 'board.json',
28
+ data: string | ArrayBuffer | Uint8Array,
29
+ assets?: object[]
30
+ }
31
+ ```
32
+
33
+ `Parser.parse()` and `Parser.parseAsync()` return the exact
34
+ `ecad-toolkit.document.v1` envelope:
35
+
36
+ ```js
37
+ {
38
+ schema: 'ecad-toolkit.document.v1',
39
+ id: 'document-...',
40
+ modelSchema: { name: 'circuit-json', version: '0.0.446' },
41
+ model: [],
42
+ source: { format: 'circuitjson', fileName: 'board.json', fileType: 'circuitjson' },
43
+ extensions: {},
44
+ assets: [],
45
+ diagnostics: [],
46
+ statistics: {}
47
+ }
48
+ ```
49
+
50
+ The shared fields have the same names and meanings in every source toolkit.
51
+ Source-only facts belong in `extensions.<format>` and are never discarded to
52
+ make the common shape smaller.
53
+
54
+ Validated source documents own selected extension values as one deeply
55
+ immutable, descriptor-safe snapshot. This uses a distinct 128 MiB payload and
56
+ 2,000,000-item ceiling so large native renderer graphs do not inherit the
57
+ compact metadata limit. The shared worker protocol applies the same ownership
58
+ boundary after structured cloning and retains its 250 MB whole-result ceiling.
59
+ Binary values preserve their `ArrayBuffer`/typed-view shape behind defensive
60
+ copy access, so payload bytes do not inflate into one JavaScript number per
61
+ byte.
62
+
63
+ ### Errors
64
+
65
+ Public failures are `ToolkitError` instances. The stable serialized fields are
66
+ `name`, `message`, `code`, `category`, `format`, `source`, `details`, and
67
+ `cause`. `error.toJSON()` produces a clone-safe record. `ToolkitError.from()`
68
+ normalizes an unknown failure without invoking hostile accessors.
69
+
70
+ ### Progress, cancellation, and workers
71
+
72
+ Asynchronous parser and project calls accept:
73
+
74
+ - `signal`: an `AbortSignal`;
75
+ - `onProgress(progress)`: ordered clone-safe progress rows;
76
+ - `worker`: `false`, `true`, or `'auto'`;
77
+ - `transferInput`: explicit permission to detach transferable caller input.
78
+
79
+ `worker: 'auto'` falls back to direct work only when worker construction is
80
+ unavailable. Protocol, post-message, parser, and runtime errors stay visible.
81
+ Worker and direct results have the same serialized shape.
82
+
83
+ `retainSource` is exactly `'none' | 'reference'` and defaults to `'none'`.
84
+ `'reference'` is an in-process parser option: the returned document has a
85
+ non-enumerable `sourceReference` property whose value is the exact caller input
86
+ object. The toolkit neither freezes nor copies that caller object, and
87
+ serialization or structured cloning intentionally omits the reference.
88
+ `worker: true` rejects this mode because cross-thread caller identity cannot be
89
+ preserved; `worker: 'auto'` selects the direct path instead.
90
+
91
+ ### Reusing work
92
+
93
+ Prepare a context once when several operations use the same document:
94
+
95
+ ```js
96
+ const context = CircuitJsonDocumentContext.prepare(document, {
97
+ indexes: ['elements', 'relations', 'connectivity', 'spatial']
98
+ })
99
+ ```
100
+
101
+ Validation, indexes, render primitives, and other derived values are cached by
102
+ the context. Public outputs remain detached or immutable; a caller cannot
103
+ mutate later results through a returned object.
104
+
105
+ ## Root entrypoint
106
+
107
+ `circuitjson-toolkit` has an exact 17-class root. The 14 canonical classes are:
108
+
109
+ - `Parser`
110
+ - `ProjectLoader`
111
+ - `CircuitJsonDocumentContext`
112
+ - `PcbSvgRenderer`
113
+ - `SchematicSvgRenderer`
114
+ - `BomTableRenderer`
115
+ - `PcbInteractionIndex`
116
+ - `QueryService`
117
+ - `ManufacturingService`
118
+ - `SimulationService`
119
+ - `PcbScene3dBuilder`
120
+ - `PcbScene3dPreparator`
121
+ - `ToolkitCapabilities`
122
+ - `ToolkitError`
123
+
124
+ `CircuitJsonDocument`, `CircuitJsonIndexer`, and `CircuitJsonUnits` remain
125
+ temporarily on the root for viewer compatibility. All other former root
126
+ exports are explicitly retained by `/extensions`.
127
+
128
+ ### `CircuitJsonDocument.normalizeModel(model, options?)`
129
+
130
+ Projects supported pre-union aliases onto the canonical element union. The
131
+ default copy-on-write call preserves the exact input and element identities
132
+ when no projection is needed. `{ owned: true }` may update a toolkit-owned
133
+ mutable model in place before its single validation/proof pass.
134
+
135
+ The canonical union is the pinned upstream schema plus the source-neutral
136
+ `schematic_image` and `schematic_sheet_symbol` contracts. Use
137
+ `CircuitJsonElementValidator.knownElementTypes()` for the exact upstream
138
+ snapshot, `canonicalElementTypes()` for all accepted types, and
139
+ `extensionElementTypes()` for the toolkit-owned additions.
140
+
141
+ The projection covers legacy schematic table row/column/span geometry, PCB
142
+ artwork `points`/`width` paths, pad-clearance diagnostic relations, courtyard
143
+ line/path/polygon forms, outer-layer aliases, and retained stroke dash fields.
144
+ It derives geometry from the supplied data and does not recognize filenames,
145
+ fixtures, vendors, or application state.
146
+
147
+ ### `CircuitJsonPcbHolePrimitiveModel.build(element, center)`
148
+
149
+ Normalizes circular, rectangular, pill, and polygon-plated holes for shared
150
+ rendering and interaction consumers. Polygon `pad_outline` points determine
151
+ rotation-local outer width and height; pill drill width, height, diameter, and
152
+ board-space rotation remain distinct from outer-pad rotation. Legal
153
+ `outer_width`, `outer_height`, `rect_ccw_rotation`, and `hole_ccw_rotation`
154
+ variants are preserved. Import this retained source-neutral helper from
155
+ `circuitjson-toolkit/extensions`.
156
+
157
+ Packed release checks reject any missing or additional root export.
5
158
 
6
159
  ## Parser
7
160
 
8
- ### `CircuitJsonParser.parseText(text, options?)`
161
+ Import from the root or `circuitjson-toolkit/parser`.
9
162
 
10
- Parses standalone CircuitJSON JSON text and returns a serialized element array.
163
+ ### `Parser.parse(input, options?)`
11
164
 
12
- Options:
165
+ Synchronously detects, decodes, validates, and returns one canonical document.
166
+ Synchronous calls reject `worker: true`.
13
167
 
14
- - `fileName`: optional source file name stored as non-structural metadata.
168
+ ### `Parser.parseAsync(input, options?)`
15
169
 
16
- Returned arrays receive metadata fields compatible with the parser packages:
170
+ Asynchronous equivalent with progress, cancellation, and worker support.
171
+ Direct execution snapshots binary input windows and selected assets before the
172
+ first progress callback. A callback may mutate caller buffers without changing
173
+ the in-flight result. Worker inputs rely on the already isolated protocol copy
174
+ and are not copied again in the receiver.
17
175
 
18
- - `fileName`
19
- - `fileType: 'circuitjson'`
20
- - `kind`
21
- - `sourceFormat: 'circuitjson'`
176
+ ### `Parser.tryParse(input, options?)`
22
177
 
23
- Malformed JSON throws a `SyntaxError`. Valid JSON that is not a CircuitJSON
24
- element array throws a `TypeError`.
178
+ Returns either `{ ok: true, value }` or
179
+ `{ ok: false, error, diagnostics }` instead of throwing a public parse failure.
25
180
 
26
- ### `CircuitJsonParser.parseBytes(bytes, options?)`
181
+ ### `Parser.supports(input)`
27
182
 
28
- Parses UTF-8 CircuitJSON bytes from an `ArrayBuffer` or `Uint8Array`.
183
+ Performs bounded format detection and returns a boolean.
29
184
 
30
- ## Document Validation
185
+ The parser subpath additionally exports `ParserWorkerClient`,
186
+ `ToolkitWorkerProtocol`, and the `TOOLKIT_WORKER_PROTOCOL` version constant for
187
+ hosts that own a custom worker lifecycle.
31
188
 
32
- ### `CircuitJsonDocument.isElement(value)`
189
+ ### `ToolkitAsset.measure(asset)`
33
190
 
34
- Returns true when `value` is an object with a known non-empty string `type`
35
- field and passes element-specific validation.
191
+ Returns the exact resident payload byte length without copying. Binary lengths
192
+ come from intrinsic platform slots and text uses a non-allocating UTF-8 scan.
36
193
 
37
- ### `CircuitJsonDocument.isModel(value)`
194
+ ### `ToolkitAsset.prepare(asset, options?)`
38
195
 
39
- Returns true when `value` is an array and every item is a valid CircuitJSON
40
- element.
196
+ Accepts `mode: 'none' | 'metadata' | 'full'`. `none` validates and measures but
197
+ returns `null`; `metadata` returns canonical metadata with `data: null` and
198
+ makes no payload copy; `full` creates exactly one protected payload snapshot.
199
+ The optional `acceptPayload(byteLength, identity)` callback runs before payload
200
+ allocation. Accessors, custom prototypes, and hidden fields are rejected
201
+ without executing accessors. When `mediaType` is omitted, standard ECAD model,
202
+ image, JSON, PDF, and ZIP suffixes are inferred from `name`; an explicit
203
+ `mediaType` always wins. WRL and VRML resolve to `model/vrml`, while STEP and
204
+ STP resolve to `model/step`.
41
205
 
42
- ### `CircuitJsonDocument.validateModel(value)`
206
+ ### `ToolkitAsset.prepareAll(assets, options?)`
43
207
 
44
- Returns validation error messages for a candidate CircuitJSON element array.
208
+ Applies the same contract to an exact dense asset array. Privately branded
209
+ prepared assets are idempotent, so parser/project/result boundaries do not
210
+ copy a full payload again.
45
211
 
46
- ### `CircuitJsonDocument.assertModel(value)`
212
+ ### `ParserWorkerClient` instance attempts
47
213
 
48
- Throws unless `value` is a valid CircuitJSON element array.
214
+ `client.parseAttempt(input, options?)` and
215
+ `client.loadProjectAttempt(entries, options?)` return `{ ok: true, value }` on
216
+ success or `{ ok: false, error, unavailable }` on failure. `unavailable` is true
217
+ only when that exact request's locally injected worker factory or construction
218
+ boundary fails. The authorization is consumed by that result and cannot be
219
+ replayed by throwing the returned error from another request.
49
220
 
50
- ### `CircuitJsonDocument.attachMetadata(circuitJson, metadata?)`
221
+ Source toolkits can use these methods to implement `worker: 'auto'` without
222
+ hiding failures: fall back to direct execution only for `unavailable: true`;
223
+ input validation, post-message, remote parser, protocol, cancellation, queue,
224
+ disposed-client, and worker runtime failures all return `unavailable: false`.
51
225
 
52
- Attaches parser-style metadata to a CircuitJSON array and returns the same
53
- array.
226
+ ## Projects
54
227
 
55
- ## Indexing
228
+ Import from the root or `circuitjson-toolkit/project`.
56
229
 
57
- ### `CircuitJsonIndexer.index(circuitJson)`
230
+ ### `ProjectLoader.load(entries, options?)`
58
231
 
59
- Builds lookup maps for one CircuitJSON element array.
232
+ Synchronously loads a bounded dense array of `{ name, data, assets? }` entries
233
+ and returns an `ecad-toolkit.project.v1` envelope. Unsupported entries are
234
+ classified consistently; archive limits and duplicate paths are validated
235
+ before parsing.
60
236
 
61
- Returns:
237
+ Attached `entry.assets` payload bytes are included in `maxEntryBytes` and
238
+ `maxTotalBytes` before parsing in every decode mode. Direct and worker paths use
239
+ the same `ToolkitAsset` preparation and accounting pass.
62
240
 
63
- - `elements`: the original element array.
64
- - `elementsByType`: `Map<string, object[]>` keyed by `type`.
65
- - `elementsById`: `Map<string, object>` keyed by `<type>:<id>`.
66
- - `sourceComponentById`: source component lookup.
67
- - `pcbComponentById`: PCB component lookup.
241
+ The loader captures one bounded, known-field snapshot before callbacks, host
242
+ yields, or worker dispatch. `maxEntries` is enforced from the array length
243
+ before any entry record is inspected. Later caller mutation cannot change
244
+ classification, accounting, or direct/worker behavior, and unknown hidden or
245
+ symbol entry fields are ignored consistently.
68
246
 
69
- Known ID fields include board, component, hole, pad, trace, via, source
70
- component, source net, and source port identifiers.
247
+ ### `ProjectLoader.loadAsync(entries, options?)`
71
248
 
72
- ## Units
249
+ Asynchronous equivalent with common progress, cancellation, and worker
250
+ behavior.
73
251
 
74
- ### `CircuitJsonUnits.mmToMil(value, fallback?)`
252
+ ### `ProjectLoader.tryLoad(entries, options?)`
75
253
 
76
- Converts a millimeter value to mils with stable rounding. Non-finite values use
77
- the fallback, defaulting to `0`.
254
+ Returns a discriminated success or failure record without throwing public load
255
+ failures.
78
256
 
79
- ### `CircuitJsonUnits.pointMmToMil(point)`
257
+ ### `ProjectLoader.supports(entries)`
80
258
 
81
- Converts `{ x, y }` millimeter points to `{ x, y }` mil points.
259
+ Returns whether the bounded entry collection contains supported project input.
82
260
 
83
- ## SPICE Simulation
261
+ ### `ZipArchiveInspector.inspect(data, options?)`
84
262
 
85
- ### `SpiceCompatibilityPreprocessor.rewrite(spiceString)`
263
+ Preflights bounded ZIP central and local records before inflation, including
264
+ exact local/central filename agreement. Entry rows
265
+ include `crc32`, compressed/uncompressed sizes, `localOffset`, and
266
+ `payloadOffset`; fixed local metadata must exactly match the central directory,
267
+ while data-descriptor fields must be zero or matching.
268
+ `maxCompressionRatio` compares total declared uncompressed member bytes with
269
+ total compressed member bytes, independent of ZIP comments or container
270
+ padding. Empty archives use ratio zero; a nonempty declared output with zero
271
+ compressed bytes is rejected as an infinite ratio.
86
272
 
87
- Rewrites narrow, supported SPICE compatibility syntax before simulation. The
88
- current preprocessor handles resistor `TC=` pairs and boolean caret operators
89
- inside compatible `VALUE` expression blocks.
273
+ ### `ZipArchiveInspector.verifyExtractedBytes(entry, data)`
90
274
 
91
- ### `SpiceSimulationService.simulate(spiceString)`
275
+ Checks the exact extracted byte length and CRC32 against an entry returned by
276
+ `inspect()`. It returns `true` or throws `ToolkitError` with
277
+ `ERR_ARCHIVE_INVALID` and expected/actual integrity details.
92
278
 
93
- Runs a local deterministic transient example and returns:
279
+ ## Prepared document context
94
280
 
95
- - `simulationResultCircuitJson`: CircuitJSON transient voltage/current graph
96
- elements.
97
- - `simulationCircuitJson`: a complete CircuitJSON element set containing the
98
- `simulation_experiment` element with `experiment_type:
99
- 'spice_transient_analysis'` followed by its graph elements.
100
- - `graphSummary`: a deterministic summary of graph ids, names, point counts,
101
- time bounds, and min/max values for renderer tests and UI previews.
102
- - `diagnostics`: non-fatal simulation diagnostics.
281
+ ### `CircuitJsonDocumentContext.prepare(document, options?)`
103
282
 
104
- The service also accepts an injected engine through `new
105
- SpiceSimulationService({ engine })`. The engine must provide a `simulate`
106
- method that accepts a preprocessed SPICE netlist string and returns real-valued
107
- rows with `time`, `voltage`, and `current` data. Returned rows are resampled to
108
- the `.tran` time grid when transient step and stop parameters are available.
109
- Probe metadata comments may use `circuitjson_voltage_probe`,
110
- `simulation_voltage_probe`, `circuitjson_current_probe`, or
111
- `simulation_current_probe` markers to preserve graph ids, names, source nodes,
112
- and source trace/component references.
113
- External `.lib` and `.include` directives, PWL REPEAT source syntax, selected
114
- PSPICE compatibility patterns, and requested `.PRINT TRAN` vectors that cannot
115
- be matched to simulator output are reported as warnings for callers that need a
116
- full simulator path.
117
- Malformed probe metadata comments are also reported as warnings. Invalid JSON
118
- uses `spice_probe_metadata_invalid_json`; parsed comments missing required
119
- string fields use `spice_probe_metadata_invalid_shape`.
283
+ Validates once and returns an immutable request-scoped context. `options.indexes`
284
+ may request `elements`, `identifiers`, `relations`, `connectivity`, and
285
+ `spatial` indexes. The compact `identifiers` index exposes an `elementsById`
286
+ set without duplicating the element graph.
120
287
 
121
- ## Entrypoints
288
+ The class constructor is not a public construction path. It throws before
289
+ observing caller input; use `prepare()` so every context carries a
290
+ validation-bound authority that downstream viewers and applications can trust.
122
291
 
123
- The root entrypoint exports all utilities:
292
+ ### `context.getIndex(name)` and `context.hasIndex(name)`
124
293
 
125
- ```js
126
- import {
127
- CircuitJsonDocument,
128
- CircuitJsonIndexer,
129
- CircuitJsonParser,
130
- CircuitJsonUnits,
131
- SpiceCompatibilityPreprocessor,
132
- SpiceSimulationService
133
- } from 'circuitjson-toolkit'
134
- ```
294
+ Access a prepared index or check its presence.
135
295
 
136
- The parser subpath is available for parser-focused hosts:
296
+ ### `context.getOrCreateDerived(namespace, key, factory)`
137
297
 
138
- ```js
139
- import {
140
- CircuitJsonDocument,
141
- CircuitJsonParser
142
- } from 'circuitjson-toolkit/parser'
298
+ Creates a derived value once per context and stable namespace/key pair. This is
299
+ the common extension point for services that need to share expensive work.
300
+
301
+ ### `context.statistics`
302
+
303
+ Returns stable validation, index-build, and derived-build counters.
304
+
305
+ ## Renderers
306
+
307
+ Import from the root or `circuitjson-toolkit/renderers`. Renderer output is
308
+ deterministic, local, and independent of DOM APIs.
309
+
310
+ ### `PcbSvgRenderer.render(document, options?)`
311
+
312
+ Returns one SVG string. Common options include `side`, selected/hidden layers,
313
+ hidden object categories, viewport controls, and shared style controls.
314
+
315
+ ### `PcbSvgRenderer.renderLayers(document, options?)`
316
+
317
+ Returns `{ schema, items, diagnostics, statistics }`. Every item contains
318
+ `id`, `side`, `layerIds`, and `svg`. All layers share one primitive preparation.
319
+
320
+ ### `SchematicSvgRenderer.render(document, options?)`
321
+
322
+ Returns one schematic SVG. Sheet selection and table/debug presentation use
323
+ the same option names across toolkits. Canonical `schematic_image` rows resolve
324
+ their exact `asset_id` from `document.assets`; full asset mode renders the
325
+ payload, while metadata-only or unresolved assets are omitted without a
326
+ placeholder. `schematic_sheet_symbol` renders a hierarchical child box and is
327
+ never treated as a selectable `schematic_sheet` page.
328
+
329
+ ### `BomTableRenderer.render(document, options?)`
330
+
331
+ Returns deterministic BOM HTML from standard source-component fields. Legacy
332
+ prepared row input is retained only as a compatibility path.
333
+
334
+ The optional stylesheet is available at
335
+ `circuitjson-toolkit/styles/renderers.css`.
336
+
337
+ ## PCB interaction
338
+
339
+ Import from the root or `circuitjson-toolkit/interaction`.
340
+
341
+ ### `PcbInteractionIndex.create(document, options?)`
342
+
343
+ Creates a reusable exact interaction service. Common defaults are `side`,
344
+ `tolerance`, `hiddenLayers`, and `hiddenObjects`.
345
+
346
+ ### `index.hitTest(point, options?)`
347
+
348
+ Returns ordered exact hits after spatial broad-phase filtering. Every row uses
349
+ the common fields `elementId`, `primitiveId`, `kind`, `side`, `layerId`,
350
+ `bounds`, `distance`, `componentId`, `componentKey`, `netName`, `groupIds`, and
351
+ `source`.
352
+
353
+ ### `index.pick(point, options?)`
354
+
355
+ Returns the first exact hit or `null`.
356
+
357
+ ### `index.selectBounds(bounds, options?)`
358
+
359
+ Returns the normalized selection bounds, center point, candidates, selected
360
+ candidate, component keys, and net names.
361
+
362
+ ### `index.selectArea(bounds, options?)`
363
+
364
+ Returns the same normalized selection as `selectBounds`. `selectArea` is the
365
+ shared alias used by source toolkits.
366
+
367
+ ### `index.selectionAt(point, options?)`
368
+
369
+ Returns point selection candidates and stable component-first/net-first
370
+ selection state.
371
+
372
+ ### `index.snap(point, options?)`
373
+
374
+ Returns `{ snapped, point }` for the nearest prepared anchor inside tolerance.
375
+
376
+ ### `index.resolveLayers()`
377
+
378
+ Returns clone-safe `{ physicalLayers, virtualLayers }`.
379
+
380
+ ### `index.resolveDiagnosticFocus(diagnosticId)`
381
+
382
+ Returns `{ id, point, bounds, relatedPrimitiveIds }` or `null`.
383
+
384
+ `PcbSpatialIndex` is also exported from the interaction subpath for bounded,
385
+ immutable spatial record indexing.
386
+
387
+ ## Queries
388
+
389
+ Import from the root or `circuitjson-toolkit/query`.
390
+
391
+ ### `QueryService.create(document, options?)`
392
+
393
+ Returns a service bound to reusable element, relation, and connectivity
394
+ indexes.
395
+
396
+ ### `service.query(request, options?)`
397
+
398
+ `request.select` is `components` or `nets`; `request.where` accepts `field`,
399
+ `pattern`, `match`, `flags`, and `caseSensitive`. The result is
400
+ `{ schema: 'ecad-toolkit.query.v1', items, diagnostics, statistics }`.
401
+
402
+ ### `service.findComponents(criteria?, options?)`
403
+
404
+ Returns matching common component records.
405
+
406
+ ### `service.findNets(criteria?, options?)`
407
+
408
+ Returns matching common net records.
409
+
410
+ ### `service.traceConnectivity(request, options?)`
411
+
412
+ Returns ordered connectivity records from canonical source identifiers with
413
+ bounded traversal controls.
414
+
415
+ ### `service.buildNetlist(options?)`
416
+
417
+ Returns a detached canonical query netlist.
418
+
419
+ ### `service.statistics`
420
+
421
+ Reports validation, index, and netlist build counts.
422
+
423
+ ## Manufacturing
424
+
425
+ Import from the root or `circuitjson-toolkit/manufacturing`.
426
+
427
+ ### `ManufacturingService.inspect(document, options?)`
428
+
429
+ Returns `{ schema, placements, fabricationNotes, exports, diagnostics,
430
+ statistics }`.
431
+
432
+ ### `ManufacturingService.listExports(document, options?)`
433
+
434
+ Returns availability rows for `fabrication-notes-json`, `pick-place-csv`, and
435
+ `routing-dsn`. An unavailable export remains discoverable with a reason.
436
+
437
+ ### `ManufacturingService.export(document, request, options?)`
438
+
439
+ Builds a requested available export and returns
440
+ `{ fileName, mediaType, data: Uint8Array, diagnostics }`.
441
+
442
+ ## Simulation
443
+
444
+ Import from the root or `circuitjson-toolkit/simulation`.
445
+
446
+ ### `SimulationService.build(document, options?)`
447
+
448
+ Builds the common simulation description and capability status from canonical
449
+ simulation elements.
450
+
451
+ ### `SimulationService.export(document, request, options?)`
452
+
453
+ Runs an explicitly injected engine or export adapter. No simulator, process,
454
+ filesystem, or network access is implicit. Unsupported analyses remain
455
+ discoverable and fail with `ERR_CAPABILITY_UNAVAILABLE` when invoked.
456
+
457
+ The previous `SpiceSimulationService` remains in `/extensions`.
458
+
459
+ ## PCB 3D scene data
460
+
461
+ Import from the root or `circuitjson-toolkit/scene3d`.
462
+
463
+ ### `PcbScene3dBuilder.build(document, options?)`
464
+
465
+ Synchronously returns a data-only `ecad-toolkit.scene3d.v1` scene in
466
+ millimeters, using a right-handed Z-up coordinate system. It performs no asset
467
+ I/O and has no Three.js dependency.
468
+
469
+ ### `PcbScene3dPreparator.prepare(document, options?)`
470
+
471
+ Asynchronously builds the same canonical scene and resolves only explicitly
472
+ requested assets through `options.resolveAsset(request, { signal })`.
473
+ Prepared assets are immutable and safe to reuse. `signal` cancels outstanding
474
+ work; resolution and concurrency limits are bounded.
475
+
476
+ Runtime rendering belongs to `pcb-scene3d-viewer`, not this package.
477
+
478
+ ## Capabilities
479
+
480
+ Import from the root or `circuitjson-toolkit/capabilities`.
481
+
482
+ ### `ToolkitCapabilities.inventory()`
483
+
484
+ Returns fresh clone-safe rows in stable id order. Each row has `id`,
485
+ `category`, `operation`, `status`, `entrypoint`, `summary`, `reason`, `tested`,
486
+ and `documented`. See [capabilities.md](capabilities.md) for the full table.
487
+
488
+ ## Extension API
489
+
490
+ `circuitjson-toolkit/extensions` exports exactly 37 retained noncanonical
491
+ 1.0.17 symbols. This includes `CircuitJsonParser`, specialized CircuitJSON renderers,
492
+ manufacturing builders, archive utilities, selected-part export, and the SPICE
493
+ compatibility service. The exact migration mapping is generated from the
494
+ verified feature ledger in [migration.md](migration.md) and its split
495
+ [appendix pages](migration/root.md).
496
+
497
+ These helpers consume source-neutral CircuitJSON. Their ledger availability is
498
+ therefore `shared` or `derived` for every toolkit, never falsely
499
+ source-unavailable.
500
+
501
+ `CircuitJsonPcbSvgRenderer.renderSides(model, sides?)` is an extension-only
502
+ migration helper that renders several sides after one legacy primitive build.
503
+
504
+ ## Testing API
505
+
506
+ `circuitjson-toolkit/testing` exports:
507
+
508
+ - `ToolkitContractFixtures`: small synthetic, source-format-specific fixtures;
509
+ - `ToolkitLoopbackWorker`: a real structured-clone/transfer loopback worker
510
+ constructor for cross-toolkit worker parity tests;
511
+ - `runToolkitContract(adapter)`: the packed downstream conformance harness.
512
+
513
+ A source toolkit adapter supplies its package name, parser, project loader,
514
+ canonical services, capability inventory, and any native source fixture. The
515
+ harness verifies observable parser/project shapes, direct/worker equivalence,
516
+ context reuse, renderers, interaction, query, manufacturing, simulation, 3D
517
+ scene data, errors, and capability identifiers. Optional operations are invoked
518
+ according to their capability row: available statuses must return the
519
+ canonical shape, while `unavailable` must throw
520
+ `ERR_CAPABILITY_UNAVAILABLE`.
521
+
522
+ ## Worker module
523
+
524
+ `circuitjson-toolkit/workers/parser.worker.mjs` implements
525
+ `ecad-toolkit.worker.v1` for both parser and project operations. Hosts normally
526
+ select it through `worker: true` or `worker: 'auto'`; importing the module
527
+ directly is reserved for custom worker construction.
528
+
529
+ ## Package export map
530
+
531
+ The complete supported subpath list is:
532
+
533
+ ```text
534
+ circuitjson-toolkit
535
+ circuitjson-toolkit/parser
536
+ circuitjson-toolkit/project
537
+ circuitjson-toolkit/renderers
538
+ circuitjson-toolkit/interaction
539
+ circuitjson-toolkit/query
540
+ circuitjson-toolkit/manufacturing
541
+ circuitjson-toolkit/simulation
542
+ circuitjson-toolkit/scene3d
543
+ circuitjson-toolkit/capabilities
544
+ circuitjson-toolkit/extensions
545
+ circuitjson-toolkit/testing
546
+ circuitjson-toolkit/workers/parser.worker.mjs
547
+ circuitjson-toolkit/styles/renderers.css
143
548
  ```