circuitjson-toolkit 1.0.17 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/NOTICE.md +31 -0
  2. package/README.md +267 -107
  3. package/docs/api.md +501 -96
  4. package/docs/capabilities.md +70 -0
  5. package/docs/migration/behaviors.md +45 -0
  6. package/docs/migration/parser.md +60 -0
  7. package/docs/migration/renderers.md +515 -0
  8. package/docs/migration/root.md +740 -0
  9. package/docs/migration.md +120 -0
  10. package/docs/model-format.md +175 -57
  11. package/docs/provenance.md +206 -0
  12. package/docs/release-notes-v1.1.0.md +154 -0
  13. package/docs/testing.md +117 -7
  14. package/package.json +31 -5
  15. package/spec/api-baseline-v1.0.17.json +1 -0
  16. package/spec/baseline-provenance-v1.0.17.json +7 -0
  17. package/spec/circuitjson-schema-snapshot.json +321 -0
  18. package/spec/circuitjson-schema-source.json +28 -0
  19. package/spec/feature-preservation.json +1 -0
  20. package/spec/library-scope.md +27 -20
  21. package/src/capabilities.mjs +1 -0
  22. package/src/core/ArchiveEntryPath.mjs +93 -0
  23. package/src/core/ArchiveLimits.mjs +31 -0
  24. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  25. package/src/core/AsyncInputOwnership.mjs +56 -0
  26. package/src/core/AttachedValueLimits.mjs +67 -0
  27. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  28. package/src/core/CircuitJsonDocument.mjs +19 -61
  29. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  30. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  31. package/src/core/CircuitJsonIndexer.mjs +274 -194
  32. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  33. package/src/core/CircuitJsonParser.mjs +75 -13
  34. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  35. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  36. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  37. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  38. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  39. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  40. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  41. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  42. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  43. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  44. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  45. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  46. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  47. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  48. package/src/core/CircuitJsonUnits.mjs +13 -87
  49. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  50. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  51. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  52. package/src/core/ManufacturingService.mjs +323 -0
  53. package/src/core/Parser.mjs +343 -0
  54. package/src/core/ParserOptions.mjs +333 -0
  55. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  56. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  57. package/src/core/PcbInteractionIndex.mjs +368 -0
  58. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  59. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  60. package/src/core/ProjectLoader.mjs +975 -0
  61. package/src/core/SimulationService.mjs +790 -0
  62. package/src/core/ToolkitCapabilities.mjs +130 -0
  63. package/src/core/ZipArchiveInspector.mjs +649 -0
  64. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  65. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  66. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  67. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  68. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  69. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  70. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  71. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  72. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  73. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  74. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  75. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  76. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  77. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  78. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  79. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  80. package/src/core/contracts/DocumentResult.mjs +198 -0
  81. package/src/core/contracts/ProjectResult.mjs +96 -0
  82. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  83. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  84. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  85. package/src/core/contracts/ToolkitError.mjs +176 -0
  86. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  87. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  88. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  89. package/src/core/query/CircuitTraversal.mjs +343 -0
  90. package/src/core/query/ComponentGrouping.mjs +275 -0
  91. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  92. package/src/core/query/QueryService.mjs +435 -0
  93. package/src/core/query/RegexPattern.mjs +75 -0
  94. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  95. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  96. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  97. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  98. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  99. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  100. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  101. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  102. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  103. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  104. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  105. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  106. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  107. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  108. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  109. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  110. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  111. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  112. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  113. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  114. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  115. package/src/core/worker/WorkerRequestData.mjs +623 -0
  116. package/src/core/worker/WorkerResponseData.mjs +905 -0
  117. package/src/extensions.mjs +37 -0
  118. package/src/index.mjs +14 -9
  119. package/src/interaction.mjs +2 -0
  120. package/src/manufacturing.mjs +1 -0
  121. package/src/parser.mjs +12 -2
  122. package/src/project.mjs +5 -0
  123. package/src/query.mjs +1 -0
  124. package/src/renderers.mjs +3 -29
  125. package/src/scene3d.mjs +3 -0
  126. package/src/simulation.mjs +1 -0
  127. package/src/styles/renderers.css +24 -0
  128. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  129. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  130. package/src/testing/runToolkitContract.mjs +705 -0
  131. package/src/testing.mjs +3 -0
  132. package/src/ui/BomTableRenderer.mjs +304 -0
  133. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  134. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  135. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  136. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  137. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  138. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  139. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  140. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  141. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  142. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  143. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  144. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  145. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  146. package/src/ui/PcbSvgRenderer.mjs +41 -0
  147. package/src/ui/SafeSvgPaint.mjs +26 -0
  148. package/src/ui/SafeXmlText.mjs +60 -0
  149. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  150. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  151. package/src/ui/SchematicTableGeometry.mjs +319 -0
  152. package/src/ui/SchematicTextAnchor.mjs +55 -0
  153. package/src/ui/SchematicTextBounds.mjs +98 -0
  154. package/src/workers/parser.worker.mjs +59 -0
@@ -0,0 +1,47 @@
1
+ import { freezeScene } from './Scene3dFreeze.mjs'
2
+ import { Scene3dOptions } from './Scene3dOptions.mjs'
3
+ import { PcbScene3dBuilder } from './PcbScene3dBuilder.mjs'
4
+ import { SceneAssetResolver } from './SceneAssetResolver.mjs'
5
+
6
+ /**
7
+ * Asynchronously prepares canonical scenes and resolves requested model assets.
8
+ */
9
+ export class PcbScene3dPreparator {
10
+ /**
11
+ * Builds one scene and resolves its assets only through injected options.
12
+ * @param {unknown} input Document result, CircuitJSON model, or context.
13
+ * @param {unknown} [options] Scene preparation options.
14
+ * @returns {Promise<object>} Prepared canonical scene.
15
+ */
16
+ static async prepare(input, options = {}) {
17
+ const normalized = Scene3dOptions.normalize(options)
18
+ Scene3dOptions.assertNotAborted(normalized.signal)
19
+ const scene = PcbScene3dBuilder.build(input, options)
20
+ const hasUnresolvedAssets = scene.assets.some(
21
+ (asset) => asset.data === null
22
+ )
23
+ if (
24
+ hasUnresolvedAssets &&
25
+ !normalized.resolveAsset &&
26
+ normalized.fidelity !== 'native'
27
+ ) {
28
+ return scene
29
+ }
30
+ const assets = await SceneAssetResolver.resolveAll(
31
+ scene.assets,
32
+ options
33
+ )
34
+ Scene3dOptions.assertNotAborted(normalized.signal)
35
+ if (!assets.length) return scene
36
+
37
+ return freezeScene({
38
+ ...scene,
39
+ assets,
40
+ statistics: {
41
+ ...scene.statistics,
42
+ assetCount: assets.length,
43
+ resolvedAssetCount: assets.length
44
+ }
45
+ })
46
+ }
47
+ }
@@ -0,0 +1,284 @@
1
+ import { ToolkitAsset } from '../contracts/ToolkitAsset.mjs'
2
+ import { ToolkitError } from '../contracts/ToolkitError.mjs'
3
+ import { CircuitJsonReadOnlyDocument } from '../context/CircuitJsonReadOnlyDocument.mjs'
4
+ import { Scene3dModelReference } from './Scene3dModelReference.mjs'
5
+ import { Scene3dOptions } from './Scene3dOptions.mjs'
6
+
7
+ /**
8
+ * Selects bounded scene assets through exact canonical path aliases.
9
+ */
10
+ export class Scene3dAssetIndex {
11
+ #byAlias
12
+ #byAsset
13
+ #byReference
14
+ #claims
15
+ #options
16
+ #selected
17
+ #totalBytes
18
+
19
+ /**
20
+ * Creates one request-local asset index without copying payload bytes.
21
+ * @param {unknown} values Canonical document assets.
22
+ * @param {ReturnType<Scene3dOptions['normalize']>} options Scene options.
23
+ */
24
+ constructor(values, options) {
25
+ const assets = Scene3dOptions.dataArray(
26
+ values === undefined ? [] : values,
27
+ 'Scene assets',
28
+ Scene3dOptions.maxAssetCount
29
+ )
30
+ this.#options = options
31
+ this.#byAlias = new Map()
32
+ this.#byAsset = new WeakMap()
33
+ this.#byReference = new Map()
34
+ this.#claims = new Map()
35
+ this.#selected = []
36
+ this.#totalBytes = 0
37
+ const ids = new Map()
38
+ const metadataBudget =
39
+ CircuitJsonReadOnlyDocument.createMetadataBudget()
40
+ for (const asset of assets) {
41
+ const metadata = Scene3dAssetIndex.#lookupMetadata(
42
+ CircuitJsonReadOnlyDocument.copyAssetIndexFields(
43
+ asset,
44
+ metadataBudget
45
+ )
46
+ )
47
+ if (
48
+ metadata.id &&
49
+ ids.has(metadata.id) &&
50
+ ids.get(metadata.id) !== asset
51
+ ) {
52
+ throw Scene3dAssetIndex.#ambiguousId(metadata.id)
53
+ }
54
+ if (metadata.id) ids.set(metadata.id, asset)
55
+ const entry = { asset, metadata }
56
+ for (const alias of Scene3dAssetIndex.#aliases(metadata)) {
57
+ if (!this.#byAlias.has(alias)) {
58
+ this.#byAlias.set(alias, entry)
59
+ } else if (this.#byAlias.get(alias)?.asset !== asset) {
60
+ this.#byAlias.set(alias, null)
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Returns selected assets in deterministic first-reference order.
68
+ * @returns {object[]} Canonical scene assets.
69
+ */
70
+ get assets() {
71
+ return [...this.#selected]
72
+ }
73
+
74
+ /**
75
+ * Resolves one exact model reference or creates an unresolved request.
76
+ * @param {{ name: string, mediaType: string }} reference Model reference.
77
+ * @returns {object} Canonical scene asset.
78
+ */
79
+ resolve(reference) {
80
+ const wanted = Scene3dModelReference.normalizedPath(reference.name)
81
+ if (this.#byReference.has(wanted)) {
82
+ return this.#byReference.get(wanted)
83
+ }
84
+ const entry = this.#byAlias.get(wanted)
85
+ if (entry === null) {
86
+ throw new ToolkitError(
87
+ `Scene asset reference is ambiguous: ${reference.name}.`,
88
+ {
89
+ code: 'ERR_ASSET_AMBIGUOUS',
90
+ category: 'unsupported',
91
+ source: reference.name,
92
+ details: { name: reference.name }
93
+ }
94
+ )
95
+ }
96
+ const asset = entry
97
+ ? this.#materialize(entry)
98
+ : ToolkitAsset.create({
99
+ kind: 'model3d',
100
+ name: reference.name,
101
+ mediaType: reference.mediaType,
102
+ data: null,
103
+ source: { uri: reference.name }
104
+ })
105
+ const identity = entry?.asset || `unresolved:${wanted}`
106
+ this.#claim(asset, identity)
107
+ this.#byReference.set(wanted, asset)
108
+ return asset
109
+ }
110
+
111
+ /**
112
+ * Copies one matched payload only after both byte limits pass.
113
+ * @param {{ asset: object, metadata: object }} entry Indexed asset entry.
114
+ * @returns {object} Canonical copied asset.
115
+ */
116
+ #materialize(entry) {
117
+ const cached = this.#byAsset.get(entry.asset)
118
+ if (cached) return cached
119
+ const bytes = CircuitJsonReadOnlyDocument.assetPayloadByteLength(
120
+ entry.asset
121
+ )
122
+ if (bytes > this.#options.maxAssetBytes) {
123
+ throw Scene3dAssetIndex.#limitError(
124
+ entry.metadata,
125
+ bytes,
126
+ this.#options.maxAssetBytes,
127
+ 'asset'
128
+ )
129
+ }
130
+ if (this.#totalBytes + bytes > this.#options.maxTotalAssetBytes) {
131
+ throw Scene3dAssetIndex.#limitError(
132
+ entry.metadata,
133
+ this.#totalBytes + bytes,
134
+ this.#options.maxTotalAssetBytes,
135
+ 'total'
136
+ )
137
+ }
138
+ const result = ToolkitAsset.create(
139
+ CircuitJsonReadOnlyDocument.copyAssetFields(entry.asset)
140
+ )
141
+ this.#totalBytes += bytes
142
+ this.#byAsset.set(entry.asset, result)
143
+ return result
144
+ }
145
+
146
+ /**
147
+ * Adds a unique selected asset and rejects deterministic-id collisions.
148
+ * @param {object} asset Canonical scene asset.
149
+ * @param {object | string} identity Source identity.
150
+ * @returns {void}
151
+ */
152
+ #claim(asset, identity) {
153
+ if (this.#claims.has(asset.id)) {
154
+ if (this.#claims.get(asset.id) !== identity) {
155
+ throw Scene3dAssetIndex.#ambiguousId(asset.id)
156
+ }
157
+ return
158
+ }
159
+ if (this.#selected.length >= Scene3dOptions.maxAssetCount) {
160
+ throw new ToolkitError(
161
+ 'Scene asset count exceeds the safe limit.',
162
+ {
163
+ code: 'ERR_ASSET_LIMIT',
164
+ category: 'unsupported',
165
+ details: {
166
+ count: this.#selected.length + 1,
167
+ maximum: Scene3dOptions.maxAssetCount
168
+ }
169
+ }
170
+ )
171
+ }
172
+ this.#claims.set(asset.id, identity)
173
+ this.#selected.push(asset)
174
+ }
175
+
176
+ /**
177
+ * Builds exact normalized aliases without basename guessing.
178
+ * @param {object} asset Canonical asset metadata.
179
+ * @returns {Set<string>} Alias set.
180
+ */
181
+ static #aliases(asset) {
182
+ return new Set(
183
+ [
184
+ asset.name,
185
+ asset.source.entryName,
186
+ asset.source.projectRelativePath,
187
+ asset.source.project_relative_path,
188
+ asset.source.relativePath,
189
+ asset.source.url,
190
+ asset.source.uri
191
+ ]
192
+ .map((value) => Scene3dModelReference.normalizedPath(value))
193
+ .filter(Boolean)
194
+ )
195
+ }
196
+
197
+ /**
198
+ * Retains only scalar identity and exact path aliases for eager indexing.
199
+ * @param {Record<string, any>} fields Descriptor-safe asset fields.
200
+ * @returns {{ id: string, name: string, source: Record<string, string> }} Lightweight lookup metadata.
201
+ */
202
+ static #lookupMetadata(fields) {
203
+ const source = Object.create(null)
204
+ const candidate = fields.source
205
+ if (candidate && typeof candidate === 'object') {
206
+ for (const key of [
207
+ 'entryName',
208
+ 'projectRelativePath',
209
+ 'project_relative_path',
210
+ 'relativePath',
211
+ 'url',
212
+ 'uri'
213
+ ]) {
214
+ let descriptor
215
+ try {
216
+ descriptor = Object.getOwnPropertyDescriptor(candidate, key)
217
+ } catch {
218
+ throw new TypeError(
219
+ 'Canonical asset source aliases could not be inspected safely.'
220
+ )
221
+ }
222
+ if (!descriptor) continue
223
+ if (!Object.hasOwn(descriptor, 'value')) {
224
+ throw new TypeError(
225
+ 'Canonical asset source aliases must be data properties.'
226
+ )
227
+ }
228
+ source[key] = Scene3dAssetIndex.#scalarText(
229
+ descriptor.value,
230
+ ''
231
+ )
232
+ }
233
+ }
234
+ return {
235
+ id: Scene3dAssetIndex.#scalarText(fields.id, ''),
236
+ name: Scene3dAssetIndex.#scalarText(fields.name, ''),
237
+ source
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Converts an already validated primitive without object coercion.
243
+ * @param {unknown} value Primitive candidate.
244
+ * @param {string} fallback Fallback string.
245
+ * @returns {string} Primitive text.
246
+ */
247
+ static #scalarText(value, fallback) {
248
+ if (!value) return fallback
249
+ if (!['string', 'number', 'boolean', 'bigint'].includes(typeof value)) {
250
+ return fallback
251
+ }
252
+ return String(value)
253
+ }
254
+
255
+ /**
256
+ * Creates an asset-id ambiguity failure.
257
+ * @param {string} assetId Colliding canonical id.
258
+ * @returns {ToolkitError} Typed failure.
259
+ */
260
+ static #ambiguousId(assetId) {
261
+ return new ToolkitError(`Scene asset id is ambiguous: ${assetId}.`, {
262
+ code: 'ERR_ASSET_AMBIGUOUS',
263
+ category: 'unsupported',
264
+ details: { assetId }
265
+ })
266
+ }
267
+
268
+ /**
269
+ * Creates a synchronous asset-size failure.
270
+ * @param {object} asset Canonical asset metadata.
271
+ * @param {number} actual Selected bytes.
272
+ * @param {number} maximum Configured byte maximum.
273
+ * @param {'asset' | 'total'} scope Limit scope.
274
+ * @returns {ToolkitError} Typed failure.
275
+ */
276
+ static #limitError(asset, actual, maximum, scope) {
277
+ return new ToolkitError('Scene asset bytes exceed the safe limit.', {
278
+ code: 'ERR_ASSET_LIMIT',
279
+ category: 'unsupported',
280
+ source: asset.name,
281
+ details: { assetId: asset.id, scope, actual, maximum }
282
+ })
283
+ }
284
+ }