circuitjson-toolkit 1.0.16 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +15 -0
  2. package/NOTICE.md +31 -0
  3. package/README.md +267 -107
  4. package/docs/api.md +501 -96
  5. package/docs/capabilities.md +70 -0
  6. package/docs/migration/behaviors.md +45 -0
  7. package/docs/migration/parser.md +60 -0
  8. package/docs/migration/renderers.md +515 -0
  9. package/docs/migration/root.md +740 -0
  10. package/docs/migration.md +120 -0
  11. package/docs/model-format.md +175 -57
  12. package/docs/provenance.md +206 -0
  13. package/docs/release-notes-v1.1.0.md +154 -0
  14. package/docs/testing.md +117 -7
  15. package/package.json +31 -5
  16. package/spec/api-baseline-v1.0.17.json +1 -0
  17. package/spec/baseline-provenance-v1.0.17.json +7 -0
  18. package/spec/circuitjson-schema-snapshot.json +321 -0
  19. package/spec/circuitjson-schema-source.json +28 -0
  20. package/spec/feature-preservation.json +1 -0
  21. package/spec/library-scope.md +27 -20
  22. package/src/capabilities.mjs +1 -0
  23. package/src/core/ArchiveEntryPath.mjs +93 -0
  24. package/src/core/ArchiveLimits.mjs +31 -0
  25. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  26. package/src/core/AsyncInputOwnership.mjs +56 -0
  27. package/src/core/AttachedValueLimits.mjs +67 -0
  28. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  29. package/src/core/CircuitJsonDocument.mjs +19 -61
  30. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  31. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  32. package/src/core/CircuitJsonIndexer.mjs +274 -194
  33. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  34. package/src/core/CircuitJsonParser.mjs +75 -13
  35. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  36. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  37. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  38. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  39. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  40. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  41. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  42. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  43. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  44. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  45. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  46. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  47. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  48. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  49. package/src/core/CircuitJsonUnits.mjs +13 -87
  50. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  51. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  52. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  53. package/src/core/ManufacturingService.mjs +323 -0
  54. package/src/core/Parser.mjs +343 -0
  55. package/src/core/ParserOptions.mjs +333 -0
  56. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  57. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  58. package/src/core/PcbInteractionIndex.mjs +368 -0
  59. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  60. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  61. package/src/core/ProjectLoader.mjs +975 -0
  62. package/src/core/SimulationService.mjs +790 -0
  63. package/src/core/ToolkitCapabilities.mjs +130 -0
  64. package/src/core/ZipArchiveInspector.mjs +649 -0
  65. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  66. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  67. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  68. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  69. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  70. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  71. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  72. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  73. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  74. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  75. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  76. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  77. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  78. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  79. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  80. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  81. package/src/core/contracts/DocumentResult.mjs +198 -0
  82. package/src/core/contracts/ProjectResult.mjs +96 -0
  83. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  84. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  85. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  86. package/src/core/contracts/ToolkitError.mjs +176 -0
  87. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  88. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  89. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  90. package/src/core/query/CircuitTraversal.mjs +343 -0
  91. package/src/core/query/ComponentGrouping.mjs +275 -0
  92. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  93. package/src/core/query/QueryService.mjs +435 -0
  94. package/src/core/query/RegexPattern.mjs +75 -0
  95. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  96. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  97. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  98. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  99. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  100. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  101. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  102. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  103. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  104. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  105. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  106. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  107. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  108. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  109. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  110. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  111. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  112. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  113. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  114. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  115. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  116. package/src/core/worker/WorkerRequestData.mjs +623 -0
  117. package/src/core/worker/WorkerResponseData.mjs +905 -0
  118. package/src/extensions.mjs +37 -0
  119. package/src/index.mjs +14 -9
  120. package/src/interaction.mjs +2 -0
  121. package/src/manufacturing.mjs +1 -0
  122. package/src/parser.mjs +12 -2
  123. package/src/project.mjs +5 -0
  124. package/src/query.mjs +1 -0
  125. package/src/renderers.mjs +3 -29
  126. package/src/scene3d.mjs +3 -0
  127. package/src/simulation.mjs +1 -0
  128. package/src/styles/renderers.css +24 -0
  129. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  130. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  131. package/src/testing/runToolkitContract.mjs +705 -0
  132. package/src/testing.mjs +3 -0
  133. package/src/ui/BomTableRenderer.mjs +304 -0
  134. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  135. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  136. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  137. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  138. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  139. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  140. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  141. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  142. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  143. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  144. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  145. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  146. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  147. package/src/ui/PcbSvgRenderer.mjs +41 -0
  148. package/src/ui/SafeSvgPaint.mjs +26 -0
  149. package/src/ui/SafeXmlText.mjs +60 -0
  150. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  151. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  152. package/src/ui/SchematicTableGeometry.mjs +319 -0
  153. package/src/ui/SchematicTextAnchor.mjs +55 -0
  154. package/src/ui/SchematicTextBounds.mjs +98 -0
  155. package/src/workers/parser.worker.mjs +59 -0
@@ -0,0 +1,306 @@
1
+ import { ToolkitDiagnostic } from '../contracts/ToolkitDiagnostic.mjs'
2
+ import { ToolkitError } from '../contracts/ToolkitError.mjs'
3
+ import { ComponentGrouping } from './ComponentGrouping.mjs'
4
+
5
+ /**
6
+ * Builds one clone-safe query netlist from prepared CircuitJSON indexes.
7
+ */
8
+ export class QueryNetlistBuilder {
9
+ /**
10
+ * Builds the canonical query netlist for one document context.
11
+ * @param {import('../context/CircuitJsonDocumentContext.mjs').CircuitJsonDocumentContext} context Prepared document context.
12
+ * @returns {Record<string, any>} Canonical query netlist.
13
+ */
14
+ static build(context) {
15
+ const elements = context.getIndex('elements')
16
+ const relations = context.getIndex('relations')
17
+ const connectivity = context.getIndex('connectivity')
18
+ const portsById = new Map(
19
+ (elements.elementsByType.get('source_port') || []).map((port) => [
20
+ String(port.source_port_id || '').trim(),
21
+ port
22
+ ])
23
+ )
24
+ const internalConnections =
25
+ QueryNetlistBuilder.#internalConnections(elements)
26
+ const internalConnectionIdsByPort =
27
+ QueryNetlistBuilder.#internalConnectionIdsByPort(
28
+ internalConnections
29
+ )
30
+ const traces = [...connectivity.sourceTraceConnectivity.values()]
31
+ .map((trace) =>
32
+ QueryNetlistBuilder.#trace(
33
+ trace,
34
+ portsById,
35
+ internalConnectionIdsByPort
36
+ )
37
+ )
38
+ .filter((trace) => trace.id)
39
+ .sort((left, right) =>
40
+ ComponentGrouping.compareIds(left.id, right.id)
41
+ )
42
+ const components = QueryNetlistBuilder.#connectComponents(
43
+ ComponentGrouping.components(elements, relations),
44
+ traces
45
+ )
46
+ const nets = ComponentGrouping.nets(elements, traces)
47
+ const diagnostics = (connectivity.diagnostics || []).map((row) =>
48
+ ToolkitDiagnostic.create({
49
+ code: row.code || row.type || 'QUERY_CONNECTIVITY_DIAGNOSTIC',
50
+ severity: row.severity || 'warning',
51
+ message: row.message || '',
52
+ source: context.source.fileName || '',
53
+ details: row
54
+ })
55
+ )
56
+ return {
57
+ schema: 'ecad-toolkit.netlist.v1',
58
+ components,
59
+ nets,
60
+ traces,
61
+ internalConnections,
62
+ diagnostics,
63
+ statistics: {
64
+ componentCount: components.length,
65
+ netCount: nets.length,
66
+ traceCount: traces.length,
67
+ internalConnectionCount: internalConnections.length
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Builds explicit electrical bridges between source-component ports.
74
+ * @param {Record<string, any>} elements Prepared elements index.
75
+ * @returns {object[]} Stable internal-connection records.
76
+ */
77
+ static #internalConnections(elements) {
78
+ const recordsById = new Map()
79
+ for (const component of elements.elementsByType.get(
80
+ 'source_component'
81
+ ) || []) {
82
+ const sourceComponentId = String(
83
+ component.source_component_id || ''
84
+ ).trim()
85
+ const groups = Array.isArray(
86
+ component.internally_connected_source_port_ids
87
+ )
88
+ ? component.internally_connected_source_port_ids
89
+ : []
90
+ groups.forEach((sourcePortIds) => {
91
+ const normalizedPortIds =
92
+ QueryNetlistBuilder.#ids(sourcePortIds)
93
+ if (!sourceComponentId || normalizedPortIds.length < 2) return
94
+ QueryNetlistBuilder.#addInternalConnection(recordsById, {
95
+ id: QueryNetlistBuilder.#inlineConnectionId(
96
+ sourceComponentId,
97
+ normalizedPortIds
98
+ ),
99
+ sourceComponentId,
100
+ sourcePortIds: normalizedPortIds
101
+ })
102
+ })
103
+ }
104
+ for (const connection of elements.elementsByType.get(
105
+ 'source_component_internal_connection'
106
+ ) || []) {
107
+ const id = String(
108
+ connection.source_component_internal_connection_id || ''
109
+ ).trim()
110
+ const sourceComponentId = String(
111
+ connection.source_component_id || ''
112
+ ).trim()
113
+ const sourcePortIds = QueryNetlistBuilder.#ids(
114
+ connection.source_port_ids
115
+ )
116
+ if (!id || !sourceComponentId || sourcePortIds.length < 2) continue
117
+ QueryNetlistBuilder.#addInternalConnection(recordsById, {
118
+ id,
119
+ sourceComponentId,
120
+ sourcePortIds
121
+ })
122
+ }
123
+ return [...recordsById.values()].sort((left, right) =>
124
+ ComponentGrouping.compareIds(left.id, right.id)
125
+ )
126
+ }
127
+
128
+ /**
129
+ * Adds one unique connection or rejects a conflicting canonical id.
130
+ * @param {Map<string, object>} recordsById Connection records by id.
131
+ * @param {{ id: string, sourceComponentId: string, sourcePortIds: string[] }} record Connection record.
132
+ * @returns {void}
133
+ */
134
+ static #addInternalConnection(recordsById, record) {
135
+ const existing = recordsById.get(record.id)
136
+ if (!existing) {
137
+ recordsById.set(record.id, record)
138
+ return
139
+ }
140
+ if (QueryNetlistBuilder.#sameInternalConnection(existing, record)) {
141
+ return
142
+ }
143
+ throw new ToolkitError(
144
+ 'Conflicting source internal connections use the same canonical id.',
145
+ {
146
+ code: 'ERR_QUERY_CONNECTIVITY_ID',
147
+ category: 'validation',
148
+ format: 'circuitjson',
149
+ details: {
150
+ id: record.id,
151
+ existing,
152
+ conflicting: record
153
+ }
154
+ }
155
+ )
156
+ }
157
+
158
+ /**
159
+ * Tests whether two normalized internal-connection records are identical.
160
+ * @param {object} left Existing connection.
161
+ * @param {object} right Candidate connection.
162
+ * @returns {boolean} Whether both records describe the same connection.
163
+ */
164
+ static #sameInternalConnection(left, right) {
165
+ return (
166
+ left.sourceComponentId === right.sourceComponentId &&
167
+ left.sourcePortIds.length === right.sourcePortIds.length &&
168
+ left.sourcePortIds.every(
169
+ (sourcePortId, index) =>
170
+ sourcePortId === right.sourcePortIds[index]
171
+ )
172
+ )
173
+ }
174
+
175
+ /**
176
+ * Derives an insertion-independent id from one inline port membership.
177
+ * @param {string} sourceComponentId Source-component id.
178
+ * @param {string[]} sourcePortIds Sorted source-port ids.
179
+ * @returns {string} Stable internal-connection id.
180
+ */
181
+ static #inlineConnectionId(sourceComponentId, sourcePortIds) {
182
+ const membership = sourcePortIds.map(encodeURIComponent).join('+')
183
+ return `${sourceComponentId}:internal:${membership}`
184
+ }
185
+
186
+ /**
187
+ * Indexes internal-connection ids by every participating source port.
188
+ * @param {object[]} internalConnections Internal-connection records.
189
+ * @returns {Map<string, string[]>} Stable ids by source-port id.
190
+ */
191
+ static #internalConnectionIdsByPort(internalConnections) {
192
+ const result = new Map()
193
+ for (const connection of internalConnections) {
194
+ for (const sourcePortId of connection.sourcePortIds) {
195
+ if (!result.has(sourcePortId)) result.set(sourcePortId, [])
196
+ result.get(sourcePortId).push(connection.id)
197
+ }
198
+ }
199
+ for (const ids of result.values()) {
200
+ ids.sort(ComponentGrouping.compareIds)
201
+ }
202
+ return result
203
+ }
204
+
205
+ /**
206
+ * Adds trace-derived net ids to detached component pin records.
207
+ * @param {object[]} components Component records.
208
+ * @param {object[]} traces Trace records.
209
+ * @returns {object[]} Connected component records.
210
+ */
211
+ static #connectComponents(components, traces) {
212
+ const netIdsByPort = new Map()
213
+ for (const trace of traces) {
214
+ for (const portId of trace.sourcePortIds) {
215
+ if (!netIdsByPort.has(portId)) {
216
+ netIdsByPort.set(portId, new Set())
217
+ }
218
+ const netIds = netIdsByPort.get(portId)
219
+ for (const netId of trace.sourceNetIds) netIds.add(netId)
220
+ }
221
+ }
222
+ return components.map((component) => ({
223
+ ...component,
224
+ pins: component.pins.map((pin) => ({
225
+ ...pin,
226
+ netIds: [
227
+ ...new Set([
228
+ ...(pin.netIds || []),
229
+ ...(netIdsByPort.get(pin.id) || [])
230
+ ])
231
+ ].sort(ComponentGrouping.compareIds)
232
+ }))
233
+ }))
234
+ }
235
+
236
+ /**
237
+ * Builds one canonical trace record and its stable endpoints.
238
+ * @param {Record<string, any>} trace Connectivity index row.
239
+ * @param {Map<string, object>} portsById Source ports by id.
240
+ * @param {Map<string, string[]>} internalConnectionIdsByPort Internal connections by port.
241
+ * @returns {Record<string, any>} Trace record.
242
+ */
243
+ static #trace(trace, portsById, internalConnectionIdsByPort) {
244
+ const sourcePortIds = QueryNetlistBuilder.#ids(
245
+ trace.connectedSourcePortIds
246
+ )
247
+ const sourceNetIds = QueryNetlistBuilder.#ids(
248
+ trace.connectedSourceNetIds
249
+ )
250
+ const sourceComponentIds = [
251
+ ...new Set(
252
+ sourcePortIds
253
+ .map((id) => portsById.get(id))
254
+ .map((port) =>
255
+ String(port?.source_component_id || '').trim()
256
+ )
257
+ .filter(Boolean)
258
+ )
259
+ ].sort(ComponentGrouping.compareIds)
260
+ const internalConnectionIds = [
261
+ ...new Set(
262
+ sourcePortIds.flatMap(
263
+ (id) => internalConnectionIdsByPort.get(id) || []
264
+ )
265
+ )
266
+ ].sort(ComponentGrouping.compareIds)
267
+ const endpoints = [
268
+ ...sourcePortIds.map((id) => ({
269
+ id,
270
+ kind: 'port',
271
+ componentId: String(
272
+ portsById.get(id)?.source_component_id || ''
273
+ ).trim()
274
+ })),
275
+ ...sourceNetIds.map((id) => ({ id, kind: 'net' }))
276
+ ].sort((left, right) => {
277
+ const kindOrder = ComponentGrouping.compareIds(
278
+ left.kind,
279
+ right.kind
280
+ )
281
+ return kindOrder || ComponentGrouping.compareIds(left.id, right.id)
282
+ })
283
+ return {
284
+ id: String(trace.sourceTraceId || '').trim(),
285
+ sourcePortIds,
286
+ sourceNetIds,
287
+ sourceComponentIds,
288
+ internalConnectionIds,
289
+ endpoints
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Normalizes stable relation ids.
295
+ * @param {unknown} values Relation values.
296
+ * @returns {string[]} Unique sorted ids.
297
+ */
298
+ static #ids(values) {
299
+ const candidates = Array.isArray(values) ? values : [values]
300
+ return [
301
+ ...new Set(candidates.map((value) => String(value || '').trim()))
302
+ ]
303
+ .filter(Boolean)
304
+ .sort(ComponentGrouping.compareIds)
305
+ }
306
+ }