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,353 @@
1
+ import { DocumentResult } from '../contracts/DocumentResult.mjs'
2
+ import { CircuitJsonLegacyNormalizer } from './CircuitJsonLegacyNormalizer.mjs'
3
+ import { CircuitJsonContextIndexes } from './CircuitJsonContextIndexes.mjs'
4
+ import { CircuitJsonDerivedCache } from './CircuitJsonDerivedCache.mjs'
5
+ import { CircuitJsonReadOnlyDocument } from './CircuitJsonReadOnlyDocument.mjs'
6
+ import { CircuitJsonValidationProof } from './CircuitJsonValidationProof.mjs'
7
+
8
+ const DOCUMENT_SCHEMA = 'ecad-toolkit.document.v1'
9
+ const CONTEXT_CONSTRUCTION_AUTHORITY = Object.freeze({})
10
+
11
+ /**
12
+ * Owns one immutable CircuitJSON model and its request-scoped reusable data.
13
+ */
14
+ export class CircuitJsonDocumentContext {
15
+ #derived
16
+ #document
17
+ #indexes
18
+ #model
19
+ #source
20
+ #extensions
21
+ #assets
22
+ #statistics
23
+
24
+ /**
25
+ * Creates a context from a proven document envelope.
26
+ * @param {Record<string, any>} document Proven document envelope.
27
+ * @param {object[]} model Proven CircuitJSON model.
28
+ * @param {number} validationPasses Validation passes performed by prepare.
29
+ * @param {object} authority Module-private construction authority.
30
+ */
31
+ constructor(document, model, validationPasses, authority) {
32
+ if (authority !== CONTEXT_CONSTRUCTION_AUTHORITY) {
33
+ throw new TypeError(
34
+ 'Use CircuitJsonDocumentContext.prepare() to create a context.'
35
+ )
36
+ }
37
+ this.#document = document
38
+ this.#model = model
39
+ this.#source = CircuitJsonReadOnlyDocument.copyReadonlyMetadataValue(
40
+ CircuitJsonDocumentContext.#ownData(document, 'source', false) || {}
41
+ )
42
+ this.#extensions =
43
+ CircuitJsonReadOnlyDocument.copyReadonlyMetadataValue(
44
+ CircuitJsonDocumentContext.#ownData(
45
+ document,
46
+ 'extensions',
47
+ false
48
+ ) || {}
49
+ )
50
+ this.#assets = CircuitJsonDocumentContext.#ownData(
51
+ document,
52
+ 'assets',
53
+ false
54
+ )
55
+ this.#statistics = {
56
+ validationPasses,
57
+ indexBuilds: {},
58
+ derivedBuilds: {}
59
+ }
60
+ this.#indexes = new CircuitJsonContextIndexes(
61
+ document,
62
+ model,
63
+ this.#statistics.indexBuilds
64
+ )
65
+ this.#derived = new CircuitJsonDerivedCache(
66
+ this.#statistics.derivedBuilds
67
+ )
68
+ }
69
+
70
+ /**
71
+ * Prepares a document or extends an existing context with named indexes.
72
+ * @param {unknown} input Document result, CircuitJSON model, or context.
73
+ * @param {{ indexes?: unknown }} [options] Requested context options.
74
+ * @returns {CircuitJsonDocumentContext} Prepared request-scoped context.
75
+ */
76
+ static prepare(input, options = {}) {
77
+ const context = CircuitJsonDocumentContext.#isContext(input)
78
+ ? input
79
+ : CircuitJsonDocumentContext.#fromInput(input)
80
+ context.#indexes.ensure(options?.indexes || [])
81
+ return context
82
+ }
83
+
84
+ /**
85
+ * Returns the originating canonical document envelope.
86
+ * @returns {Record<string, any>} Document envelope.
87
+ */
88
+ get document() {
89
+ return this.#document
90
+ }
91
+
92
+ /**
93
+ * Returns the immutable CircuitJSON model.
94
+ * @returns {object[]} CircuitJSON model.
95
+ */
96
+ get model() {
97
+ return this.#model
98
+ }
99
+
100
+ /**
101
+ * Returns the document source metadata.
102
+ * @returns {Record<string, any>} Source metadata.
103
+ */
104
+ get source() {
105
+ return this.#source
106
+ }
107
+
108
+ /**
109
+ * Returns the source-owned document extensions.
110
+ * @returns {Record<string, any>} Extension map.
111
+ */
112
+ get extensions() {
113
+ return this.#extensions
114
+ }
115
+
116
+ /**
117
+ * Returns the document assets.
118
+ * @returns {object[]} Asset records.
119
+ */
120
+ get assets() {
121
+ return this.#assets
122
+ }
123
+
124
+ /**
125
+ * Returns a clone-safe snapshot of context work counters.
126
+ * @returns {{ validationPasses: number, indexBuilds: Record<string, number>, derivedBuilds: Record<string, number> }} Context statistics.
127
+ */
128
+ get statistics() {
129
+ return {
130
+ validationPasses: this.#statistics.validationPasses,
131
+ indexBuilds: { ...this.#statistics.indexBuilds },
132
+ derivedBuilds: { ...this.#statistics.derivedBuilds }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Returns one named index, creating it when first requested.
138
+ * @param {unknown} name Requested index name.
139
+ * @returns {Record<string, any>} Prepared index.
140
+ */
141
+ getIndex(name) {
142
+ return this.#indexes.get(name)
143
+ }
144
+
145
+ /**
146
+ * Returns true when one named index is already prepared.
147
+ * @param {unknown} name Requested index name.
148
+ * @returns {boolean} Whether the named index exists.
149
+ */
150
+ hasIndex(name) {
151
+ return this.#indexes.has(name)
152
+ }
153
+
154
+ /**
155
+ * Returns or creates one request-scoped derived value.
156
+ * @param {unknown} namespace Cache namespace.
157
+ * @param {unknown} key Cache key.
158
+ * @param {() => any} factory Value factory.
159
+ * @returns {any} Cached or newly built value.
160
+ */
161
+ getOrCreateDerived(namespace, key, factory) {
162
+ return this.#derived.getOrCreate(
163
+ String(namespace),
164
+ String(key),
165
+ factory
166
+ )
167
+ }
168
+
169
+ /**
170
+ * Returns whether a derived object belongs to this exact context key.
171
+ * @param {unknown} namespace Cache namespace.
172
+ * @param {unknown} key Cache key.
173
+ * @param {unknown} value Derived value candidate.
174
+ * @returns {boolean} Whether exact cache ownership matches.
175
+ * @internal
176
+ */
177
+ ownsDerived(namespace, key, value) {
178
+ return this.#derived.owns(String(namespace), String(key), value)
179
+ }
180
+
181
+ /**
182
+ * Creates one context and establishes a matching immutable model proof.
183
+ * @param {unknown} input Document result or CircuitJSON model.
184
+ * @returns {CircuitJsonDocumentContext} New context.
185
+ */
186
+ static #fromInput(input) {
187
+ const document = CircuitJsonDocumentContext.#normalizeDocument(input)
188
+ const validationPasses = CircuitJsonValidationProof.has(document)
189
+ ? 0
190
+ : 1
191
+ const readonlyDocument =
192
+ CircuitJsonValidationProof.validateAndAttach(document)
193
+ const model = CircuitJsonDocumentContext.#ownData(
194
+ readonlyDocument,
195
+ 'model'
196
+ )
197
+ return new CircuitJsonDocumentContext(
198
+ readonlyDocument,
199
+ model,
200
+ validationPasses,
201
+ CONTEXT_CONSTRUCTION_AUTHORITY
202
+ )
203
+ }
204
+
205
+ /**
206
+ * Normalizes supported context input into a canonical document envelope.
207
+ * @param {unknown} input Document result or CircuitJSON model.
208
+ * @returns {Record<string, any>} Canonical document envelope.
209
+ */
210
+ static #normalizeDocument(input) {
211
+ if (Array.isArray(input)) {
212
+ const fileType = CircuitJsonDocumentContext.#ownData(
213
+ input,
214
+ 'fileType',
215
+ false
216
+ )
217
+ return DocumentResult.create({
218
+ fileName: CircuitJsonDocumentContext.#ownData(
219
+ input,
220
+ 'fileName',
221
+ false
222
+ ),
223
+ fileType:
224
+ fileType ??
225
+ CircuitJsonDocumentContext.#ownData(input, 'kind', false),
226
+ format:
227
+ CircuitJsonDocumentContext.#ownData(
228
+ input,
229
+ 'sourceFormat',
230
+ false
231
+ ) ??
232
+ CircuitJsonDocumentContext.#ownData(input, 'format', false),
233
+ model: CircuitJsonLegacyNormalizer.normalize(
234
+ CircuitJsonDocumentContext.#canonicalModel(input)
235
+ )
236
+ })
237
+ }
238
+ if (!input || typeof input !== 'object') {
239
+ throw new TypeError(
240
+ 'Expected a DocumentResult, CircuitJSON element array, or document context.'
241
+ )
242
+ }
243
+ const schema = CircuitJsonDocumentContext.#ownData(input, 'schema')
244
+ const model = CircuitJsonDocumentContext.#ownData(input, 'model')
245
+ if (schema === DOCUMENT_SCHEMA && Array.isArray(model)) {
246
+ return input
247
+ }
248
+ throw new TypeError(
249
+ 'Expected a DocumentResult, CircuitJSON element array, or document context.'
250
+ )
251
+ }
252
+
253
+ /**
254
+ * Preserves pure model-array identity and removes enumerable legacy
255
+ * metadata fields from hybrid arrays before validation.
256
+ * @param {any[]} model CircuitJSON model array.
257
+ * @returns {any[]} Pure dense model array.
258
+ */
259
+ static #canonicalModel(model) {
260
+ let prototype
261
+ let descriptors
262
+ try {
263
+ prototype = Object.getPrototypeOf(model)
264
+ descriptors = Object.getOwnPropertyDescriptors(model)
265
+ } catch {
266
+ return model
267
+ }
268
+ const length = descriptors.length?.value
269
+ if (
270
+ prototype !== Array.prototype ||
271
+ !Number.isSafeInteger(length) ||
272
+ length < 0
273
+ ) {
274
+ return model
275
+ }
276
+ for (let index = 0; index < length; index += 1) {
277
+ const descriptor = descriptors[String(index)]
278
+ if (
279
+ !descriptor ||
280
+ !Object.hasOwn(descriptor, 'value') ||
281
+ descriptor.enumerable !== true
282
+ ) {
283
+ return model
284
+ }
285
+ }
286
+ const keys = Reflect.ownKeys(descriptors)
287
+ if (keys.length === length + 1) return model
288
+ for (const key of keys) {
289
+ if (key === 'length') continue
290
+ const index =
291
+ typeof key === 'string' && /^(?:0|[1-9]\d*)$/u.test(key)
292
+ ? Number(key)
293
+ : -1
294
+ if (Number.isSafeInteger(index) && index < length) continue
295
+ const descriptor = descriptors[key]
296
+ if (
297
+ typeof key !== 'string' ||
298
+ !Object.hasOwn(descriptor, 'value') ||
299
+ descriptor.enumerable !== true
300
+ ) {
301
+ return model
302
+ }
303
+ }
304
+ const canonical = new Array(length)
305
+ for (let index = 0; index < length; index += 1) {
306
+ canonical[index] = descriptors[String(index)].value
307
+ }
308
+ return canonical
309
+ }
310
+
311
+ /**
312
+ * Detects genuine contexts through a private slot without proxy traps.
313
+ * @param {unknown} value Context candidate.
314
+ * @returns {boolean} Whether the private context slot is present.
315
+ */
316
+ static #isContext(value) {
317
+ try {
318
+ return Boolean(value.#document)
319
+ } catch {
320
+ return false
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Reads one own data property without ordinary property access.
326
+ * @param {object} owner Canonical envelope or model array.
327
+ * @param {PropertyKey} key Property key.
328
+ * @param {boolean} [required] Whether absence is rejected.
329
+ * @returns {any} Own data value.
330
+ */
331
+ static #ownData(owner, key, required = true) {
332
+ let descriptor
333
+ try {
334
+ descriptor = Object.getOwnPropertyDescriptor(owner, key)
335
+ } catch {
336
+ throw new TypeError(
337
+ 'CircuitJSON document properties could not be inspected safely.'
338
+ )
339
+ }
340
+ if (!descriptor) {
341
+ if (!required) return undefined
342
+ throw new TypeError(
343
+ `CircuitJSON document ${String(key)} must be an own data property.`
344
+ )
345
+ }
346
+ if (!Object.hasOwn(descriptor, 'value')) {
347
+ throw new TypeError(
348
+ `CircuitJSON document ${String(key)} must be an own data property.`
349
+ )
350
+ }
351
+ return descriptor.value
352
+ }
353
+ }
@@ -0,0 +1,147 @@
1
+ import { CircuitJsonValidationProof } from './CircuitJsonValidationProof.mjs'
2
+
3
+ const PROVEN_SOURCES = new WeakMap()
4
+ const PREPARED_INDEXES = new WeakMap()
5
+
6
+ /**
7
+ * Owns the temporary hybrid-array migration view without repeating validation.
8
+ */
9
+ export class CircuitJsonLegacyModel {
10
+ /**
11
+ * Creates a mutable metadata envelope over proven immutable elements.
12
+ * @param {Record<string, any>} document Proven canonical document.
13
+ * @returns {object[]} Fresh legacy array view.
14
+ */
15
+ static create(document) {
16
+ if (!CircuitJsonValidationProof.has(document)) {
17
+ throw new TypeError(
18
+ 'Legacy CircuitJSON views require a validated document.'
19
+ )
20
+ }
21
+ const view = [...document.model]
22
+ PROVEN_SOURCES.set(view, document.model)
23
+ return view
24
+ }
25
+
26
+ /**
27
+ * Attaches metadata after public validation has already succeeded.
28
+ * @template {object[]} T
29
+ * @param {T} model Validated CircuitJSON model.
30
+ * @param {Record<string, any>} metadata Legacy metadata fields.
31
+ * @returns {T} The supplied hybrid model.
32
+ */
33
+ static attachValidated(model, metadata) {
34
+ Object.defineProperties(model, {
35
+ fileName: CircuitJsonLegacyModel.#property(
36
+ String(metadata.fileName || '')
37
+ ),
38
+ fileType: CircuitJsonLegacyModel.#property(
39
+ String(metadata.fileType || 'circuitjson')
40
+ ),
41
+ kind: CircuitJsonLegacyModel.#property(
42
+ String(metadata.kind || 'pcb')
43
+ ),
44
+ sourceFormat: CircuitJsonLegacyModel.#property('circuitjson'),
45
+ diagnostics: CircuitJsonLegacyModel.#property(
46
+ Array.isArray(metadata.diagnostics) ? metadata.diagnostics : []
47
+ ),
48
+ bom: CircuitJsonLegacyModel.#property(
49
+ Array.isArray(metadata.bom) ? metadata.bom : []
50
+ ),
51
+ supportMatrix: CircuitJsonLegacyModel.#property(
52
+ metadata.supportMatrix || null
53
+ ),
54
+ manufacturing: CircuitJsonLegacyModel.#property(
55
+ metadata.manufacturing || {
56
+ pickAndPlaceRows: [],
57
+ routingDsn: ''
58
+ }
59
+ )
60
+ })
61
+ return model
62
+ }
63
+
64
+ /**
65
+ * Returns whether a legacy view still exactly references its proven model.
66
+ * @param {unknown} model Legacy model candidate.
67
+ * @returns {boolean} Whether element validation may be reused.
68
+ */
69
+ static permitsIndex(model) {
70
+ return CircuitJsonLegacyModel.#matches(model)
71
+ }
72
+
73
+ /**
74
+ * Stores an unexposed full index for the next legacy consumer.
75
+ * @param {object[]} model Owned legacy model view.
76
+ * @param {Record<string, any>} index Full prepared index.
77
+ * @returns {void}
78
+ */
79
+ static setPreparedIndex(model, index) {
80
+ if (!CircuitJsonLegacyModel.#matches(model)) {
81
+ throw new TypeError(
82
+ 'Prepared legacy indexes require an unchanged proven view.'
83
+ )
84
+ }
85
+ PREPARED_INDEXES.set(model, { ...index, elements: model })
86
+ }
87
+
88
+ /**
89
+ * Takes a full index once when its legacy model is still unchanged.
90
+ * @param {unknown} model Legacy model candidate.
91
+ * @returns {Record<string, any> | null} Prepared index or null.
92
+ */
93
+ static takePreparedIndex(model) {
94
+ const index = PREPARED_INDEXES.get(model)
95
+ if (!index) return null
96
+ PREPARED_INDEXES.delete(model)
97
+ return CircuitJsonLegacyModel.#matches(model) ? index : null
98
+ }
99
+
100
+ /**
101
+ * Returns whether a legacy view still has its original dense element slots.
102
+ * @param {unknown} model Legacy model candidate.
103
+ * @returns {boolean} Whether the proof-bound elements are unchanged.
104
+ */
105
+ static #matches(model) {
106
+ if (!Array.isArray(model)) return false
107
+ const source = PROVEN_SOURCES.get(model)
108
+ if (
109
+ !source ||
110
+ Object.getPrototypeOf(model) !== Array.prototype ||
111
+ model.length !== source.length
112
+ ) {
113
+ return false
114
+ }
115
+ for (let index = 0; index < source.length; index += 1) {
116
+ const descriptor = Object.getOwnPropertyDescriptor(
117
+ model,
118
+ String(index)
119
+ )
120
+ if (
121
+ !descriptor ||
122
+ !Object.hasOwn(descriptor, 'value') ||
123
+ descriptor.value !== source[index]
124
+ ) {
125
+ return false
126
+ }
127
+ }
128
+ return true
129
+ }
130
+
131
+ /**
132
+ * Creates one writable enumerable configurable metadata descriptor.
133
+ * @param {unknown} value Property value.
134
+ * @returns {PropertyDescriptor} Property descriptor.
135
+ */
136
+ static #property(value) {
137
+ return {
138
+ configurable: true,
139
+ enumerable: true,
140
+ value,
141
+ writable: true
142
+ }
143
+ }
144
+ }
145
+
146
+ Object.freeze(CircuitJsonLegacyModel.prototype)
147
+ Object.freeze(CircuitJsonLegacyModel)