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,335 @@
1
+ const SELECTED_SCOPE = 1
2
+ const OTHER_SCOPE = 2
3
+
4
+ /**
5
+ * Selects one CircuitJSON schematic sheet through explicit and owned relations.
6
+ */
7
+ export class SchematicSheetSelector {
8
+ /**
9
+ * Builds an element-index view for one schematic sheet.
10
+ * @param {{ elements?: object[], elementsByType: Map<string, object[]> }} index Full element index.
11
+ * @param {string} sheetId Selected sheet id.
12
+ * @returns {{ elements: object[], elementsByType: Map<string, object[]>, statistics: { scopeUpdates: number, scopedElements: number } }} Sheet index view.
13
+ */
14
+ static select(index, sheetId) {
15
+ const elements = Array.isArray(index.elements)
16
+ ? index.elements
17
+ : Array.from(index.elementsByType.values()).flat()
18
+ const sheetCount = (index.elementsByType.get('schematic_sheet') || [])
19
+ .length
20
+ const scoped = SchematicSheetSelector.#scopes(elements, sheetId)
21
+ const elementsByType = new Map()
22
+ const selectedElements = []
23
+ for (const [type, rows] of index.elementsByType) {
24
+ if (!type.startsWith('schematic_')) continue
25
+ const selected = rows.filter((element) =>
26
+ SchematicSheetSelector.#belongs(
27
+ element,
28
+ type,
29
+ sheetId,
30
+ sheetCount,
31
+ scoped.masks
32
+ )
33
+ )
34
+ if (selected.length) {
35
+ elementsByType.set(type, selected)
36
+ for (const element of selected) {
37
+ selectedElements.push(element)
38
+ }
39
+ }
40
+ }
41
+ SchematicSheetSelector.#appendReferencedSources(
42
+ index,
43
+ selectedElements,
44
+ elementsByType,
45
+ 'source_component',
46
+ 'source_component_id'
47
+ )
48
+ SchematicSheetSelector.#appendReferencedSources(
49
+ index,
50
+ selectedElements,
51
+ elementsByType,
52
+ 'source_port',
53
+ 'source_port_id'
54
+ )
55
+ for (const [type, rows] of elementsByType) {
56
+ if (!type.startsWith('source_')) continue
57
+ for (const row of rows) selectedElements.push(row)
58
+ }
59
+ return {
60
+ elements: selectedElements,
61
+ elementsByType,
62
+ statistics: {
63
+ scopeUpdates: scoped.updates,
64
+ scopedElements: scoped.masks.size
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Retains only source rows directly referenced by selected schematic rows.
71
+ * @param {{ elementsByType: Map<string, object[]> }} index Full index.
72
+ * @param {object[]} selectedElements Selected schematic rows.
73
+ * @param {Map<string, object[]>} elementsByType Selected type map.
74
+ * @param {string} sourceType Source element type.
75
+ * @param {string} idField Qualified id field.
76
+ * @returns {void}
77
+ */
78
+ static #appendReferencedSources(
79
+ index,
80
+ selectedElements,
81
+ elementsByType,
82
+ sourceType,
83
+ idField
84
+ ) {
85
+ const ids = new Set(
86
+ selectedElements
87
+ .map((element) => String(element[idField] || ''))
88
+ .filter(Boolean)
89
+ )
90
+ const sources = (index.elementsByType.get(sourceType) || []).filter(
91
+ (element) => ids.has(String(element[idField] || ''))
92
+ )
93
+ if (sources.length) elementsByType.set(sourceType, sources)
94
+ }
95
+
96
+ /**
97
+ * Resolves constant-size target/other scope masks through ownership.
98
+ * @param {object[]} elements CircuitJSON elements.
99
+ * @param {string} targetSheetId Selected sheet id.
100
+ * @returns {{ masks: Map<object, number>, updates: number }} Scope state.
101
+ */
102
+ static #scopes(elements, targetSheetId) {
103
+ const schematicElements = elements.filter((element) =>
104
+ String(element.type || '').startsWith('schematic_')
105
+ )
106
+ const masks = new Map(schematicElements.map((element) => [element, 0]))
107
+ const references = SchematicSheetSelector.#references(schematicElements)
108
+ const subcircuitMasks = new Map()
109
+ let updates = 0
110
+ for (const element of schematicElements) {
111
+ if (element.type !== 'schematic_sheet') continue
112
+ const sheetId = String(element.schematic_sheet_id || '')
113
+ const subcircuitId = String(element.subcircuit_id || '')
114
+ if (!sheetId || !subcircuitId) continue
115
+ const mask =
116
+ sheetId === targetSheetId ? SELECTED_SCOPE : OTHER_SCOPE
117
+ subcircuitMasks.set(
118
+ subcircuitId,
119
+ (subcircuitMasks.get(subcircuitId) || 0) | mask
120
+ )
121
+ }
122
+ const explicit = new Set()
123
+ for (const element of schematicElements) {
124
+ const sheetId = String(element.schematic_sheet_id || '')
125
+ if (!sheetId) continue
126
+ masks.set(
127
+ element,
128
+ sheetId === targetSheetId ? SELECTED_SCOPE : OTHER_SCOPE
129
+ )
130
+ explicit.add(element)
131
+ updates += 1
132
+ }
133
+ const dependents = SchematicSheetSelector.#dependents(
134
+ schematicElements,
135
+ references
136
+ )
137
+ updates += SchematicSheetSelector.#propagateScopes(
138
+ masks,
139
+ dependents,
140
+ [...explicit],
141
+ explicit
142
+ )
143
+ const authoritative = new Set(
144
+ schematicElements.filter((element) => masks.get(element))
145
+ )
146
+ const fallback = []
147
+ for (const element of schematicElements) {
148
+ if (masks.get(element)) continue
149
+ const mask =
150
+ subcircuitMasks.get(String(element.subcircuit_id || '')) || 0
151
+ if (!mask) continue
152
+ masks.set(element, mask)
153
+ fallback.push(element)
154
+ updates += 1
155
+ }
156
+ updates += SchematicSheetSelector.#propagateScopes(
157
+ masks,
158
+ dependents,
159
+ fallback,
160
+ authoritative
161
+ )
162
+ return { masks, updates }
163
+ }
164
+
165
+ /**
166
+ * Builds owner-to-dependent scope relationships.
167
+ * @param {object[]} elements Schematic elements.
168
+ * @param {Map<string, object>} references Qualified id lookup.
169
+ * @returns {Map<object, Set<object>>} Dependency graph.
170
+ */
171
+ static #dependents(elements, references) {
172
+ const dependents = new Map(
173
+ elements.map((element) => [element, new Set()])
174
+ )
175
+ for (const element of elements) {
176
+ for (const owner of SchematicSheetSelector.#owners(
177
+ element,
178
+ references
179
+ )) {
180
+ dependents.get(owner)?.add(element)
181
+ }
182
+ if (element.type === 'schematic_component') {
183
+ const symbol = references.get(
184
+ 'schematic_symbol_id:' +
185
+ String(element.schematic_symbol_id || '')
186
+ )
187
+ if (symbol) dependents.get(element)?.add(symbol)
188
+ }
189
+ if (element.type === 'schematic_group') {
190
+ for (const member of SchematicSheetSelector.#groupMembers(
191
+ element,
192
+ references
193
+ )) {
194
+ dependents.get(element)?.add(member)
195
+ dependents.get(member)?.add(element)
196
+ }
197
+ }
198
+ }
199
+ return dependents
200
+ }
201
+
202
+ /**
203
+ * Propagates two-bit scope masks through ownership dependencies.
204
+ * @param {Map<object, number>} masks Mutable masks.
205
+ * @param {Map<object, Set<object>>} dependents Owner-to-dependent graph.
206
+ * @param {object[]} seeds Initially resolved elements.
207
+ * @param {Set<object>} protectedElements Authoritative elements.
208
+ * @returns {number} Number of bounded mask updates.
209
+ */
210
+ static #propagateScopes(masks, dependents, seeds, protectedElements) {
211
+ let updates = 0
212
+ const queue = [...seeds]
213
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
214
+ const owner = queue[cursor]
215
+ for (const dependent of dependents.get(owner) || []) {
216
+ if (protectedElements.has(dependent)) continue
217
+ const current = masks.get(dependent) || 0
218
+ const merged = current | (masks.get(owner) || 0)
219
+ if (merged === current) continue
220
+ masks.set(dependent, merged)
221
+ queue.push(dependent)
222
+ updates += 1
223
+ }
224
+ }
225
+ return updates
226
+ }
227
+
228
+ /**
229
+ * Builds ownership lookup maps for schematic relation ids.
230
+ * @param {object[]} elements CircuitJSON elements.
231
+ * @returns {Map<string, object>} Qualified owner lookup.
232
+ */
233
+ static #references(elements) {
234
+ const result = new Map()
235
+ for (const element of elements) {
236
+ const field = String(element.type || '') + '_id'
237
+ const value = element[field]
238
+ if (
239
+ field.startsWith('schematic_') &&
240
+ typeof value === 'string' &&
241
+ value
242
+ ) {
243
+ result.set(field + ':' + value, element)
244
+ }
245
+ }
246
+ return result
247
+ }
248
+
249
+ /**
250
+ * Resolves direct owner elements, including nested trace endpoints.
251
+ * @param {object} element CircuitJSON element.
252
+ * @param {Map<string, object>} references Qualified owner lookup.
253
+ * @returns {object[]} Referenced owners.
254
+ */
255
+ static #owners(element, references) {
256
+ const owners = new Set()
257
+ const ownIdField = String(element.type || '') + '_id'
258
+ for (const [field, value] of SchematicSheetSelector.#nestedFields(
259
+ element
260
+ )) {
261
+ const match = field.match(/(schematic_[a-z0-9_]+_id)$/u)
262
+ const ownerField = match?.[1] || ''
263
+ if (
264
+ !ownerField ||
265
+ ownerField === 'schematic_sheet_id' ||
266
+ ownerField === ownIdField ||
267
+ typeof value !== 'string'
268
+ ) {
269
+ continue
270
+ }
271
+ const owner = references.get(ownerField + ':' + value)
272
+ if (owner && owner !== element) owners.add(owner)
273
+ }
274
+ return [...owners]
275
+ }
276
+
277
+ /**
278
+ * Resolves group member components for bidirectional scope inference.
279
+ * @param {object} group Schematic group.
280
+ * @param {Map<string, object>} references Qualified owner lookup.
281
+ * @returns {object[]} Member components.
282
+ */
283
+ static #groupMembers(group, references) {
284
+ return (
285
+ Array.isArray(group.schematic_component_ids)
286
+ ? group.schematic_component_ids
287
+ : []
288
+ )
289
+ .map((id) =>
290
+ references.get('schematic_component_id:' + String(id || ''))
291
+ )
292
+ .filter(Boolean)
293
+ }
294
+
295
+ /**
296
+ * Streams scalar fields in validated nested CircuitJSON data.
297
+ * @param {object} root Root element.
298
+ * @returns {Generator<[string, unknown], void, unknown>} Field/value pairs.
299
+ */
300
+ static *#nestedFields(root) {
301
+ const stack = [root]
302
+ const seen = new WeakSet()
303
+ while (stack.length) {
304
+ const value = stack.pop()
305
+ if (!value || typeof value !== 'object' || seen.has(value)) continue
306
+ seen.add(value)
307
+ if (Array.isArray(value)) {
308
+ for (const entry of value) stack.push(entry)
309
+ continue
310
+ }
311
+ for (const [field, entry] of Object.entries(value)) {
312
+ yield [field, entry]
313
+ if (entry && typeof entry === 'object') stack.push(entry)
314
+ }
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Returns whether one element belongs in the selected sheet view.
320
+ * @param {object} element CircuitJSON element.
321
+ * @param {string} type Element type.
322
+ * @param {string} sheetId Selected sheet id.
323
+ * @param {number} sheetCount Document sheet count.
324
+ * @param {Map<object, number>} masks Resolved two-bit scopes.
325
+ * @returns {boolean} Whether to retain the row.
326
+ */
327
+ static #belongs(element, type, sheetId, sheetCount, masks) {
328
+ if (type === 'schematic_sheet') {
329
+ return element.schematic_sheet_id === sheetId
330
+ }
331
+ const mask = masks.get(element) || 0
332
+ if (mask) return Boolean(mask & SELECTED_SCOPE)
333
+ return sheetCount <= 1
334
+ }
335
+ }