circuitjson-toolkit 1.0.17 → 1.1.1

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