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.
- package/NOTICE.md +31 -0
- package/README.md +267 -107
- package/docs/api.md +501 -96
- package/docs/capabilities.md +70 -0
- package/docs/migration/behaviors.md +45 -0
- package/docs/migration/parser.md +60 -0
- package/docs/migration/renderers.md +515 -0
- package/docs/migration/root.md +740 -0
- package/docs/migration.md +120 -0
- package/docs/model-format.md +175 -57
- package/docs/provenance.md +206 -0
- package/docs/release-notes-v1.1.0.md +154 -0
- package/docs/testing.md +117 -7
- package/package.json +31 -5
- package/spec/api-baseline-v1.0.17.json +1 -0
- package/spec/baseline-provenance-v1.0.17.json +7 -0
- package/spec/circuitjson-schema-snapshot.json +321 -0
- package/spec/circuitjson-schema-source.json +28 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +27 -20
- package/src/capabilities.mjs +1 -0
- package/src/core/ArchiveEntryPath.mjs +93 -0
- package/src/core/ArchiveLimits.mjs +31 -0
- package/src/core/ArchiveLimitsValidator.mjs +107 -0
- package/src/core/AsyncInputOwnership.mjs +56 -0
- package/src/core/AttachedValueLimits.mjs +67 -0
- package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
- package/src/core/CircuitJsonDocument.mjs +19 -61
- package/src/core/CircuitJsonElementTypes.mjs +10 -0
- package/src/core/CircuitJsonElementValidator.mjs +98 -847
- package/src/core/CircuitJsonIndexer.mjs +274 -194
- package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
- package/src/core/CircuitJsonParser.mjs +75 -13
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
- package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
- package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
- package/src/core/CircuitJsonUnitParsers.mjs +101 -0
- package/src/core/CircuitJsonUnits.mjs +13 -87
- package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
- package/src/core/CircuitJsonValidationUnits.mjs +6 -0
- package/src/core/ManufacturingService.mjs +323 -0
- package/src/core/Parser.mjs +343 -0
- package/src/core/ParserOptions.mjs +333 -0
- package/src/core/PcbBoundsSelectionModel.mjs +55 -19
- package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
- package/src/core/PcbInteractionIndex.mjs +368 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
- package/src/core/ProjectAsyncInputOwner.mjs +70 -0
- package/src/core/ProjectLoader.mjs +975 -0
- package/src/core/SimulationService.mjs +790 -0
- package/src/core/ToolkitCapabilities.mjs +130 -0
- package/src/core/ZipArchiveInspector.mjs +649 -0
- package/src/core/context/BinaryDataSnapshot.mjs +217 -0
- package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
- package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
- package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
- package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
- package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
- package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
- package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
- package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
- package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
- package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
- package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
- package/src/core/context/PcbSpatialIndex.mjs +701 -0
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
- package/src/core/context/StructuredDataSnapshot.mjs +683 -0
- package/src/core/contracts/DocumentResult.mjs +198 -0
- package/src/core/contracts/ProjectResult.mjs +96 -0
- package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
- package/src/core/contracts/ToolkitAsset.mjs +493 -0
- package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
- package/src/core/contracts/ToolkitError.mjs +176 -0
- package/src/core/contracts/ToolkitProgress.mjs +89 -0
- package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
- package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
- package/src/core/query/CircuitTraversal.mjs +343 -0
- package/src/core/query/ComponentGrouping.mjs +275 -0
- package/src/core/query/QueryNetlistBuilder.mjs +306 -0
- package/src/core/query/QueryService.mjs +435 -0
- package/src/core/query/RegexPattern.mjs +75 -0
- package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
- package/src/core/rendering/CanonicalBomRows.mjs +92 -0
- package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
- package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
- package/src/core/rendering/PcbRenderPlan.mjs +429 -0
- package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
- package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
- package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
- package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
- package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
- package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
- package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
- package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
- package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
- package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
- package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
- package/src/core/scene3d/Scene3dOptions.mjs +363 -0
- package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
- package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
- package/src/core/worker/ParserWorkerClient.mjs +997 -0
- package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
- package/src/core/worker/WorkerRequestData.mjs +623 -0
- package/src/core/worker/WorkerResponseData.mjs +905 -0
- package/src/extensions.mjs +37 -0
- package/src/index.mjs +14 -9
- package/src/interaction.mjs +2 -0
- package/src/manufacturing.mjs +1 -0
- package/src/parser.mjs +12 -2
- package/src/project.mjs +5 -0
- package/src/query.mjs +1 -0
- package/src/renderers.mjs +3 -29
- package/src/scene3d.mjs +3 -0
- package/src/simulation.mjs +1 -0
- package/src/styles/renderers.css +24 -0
- package/src/testing/ToolkitContractFixtures.mjs +124 -0
- package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
- package/src/testing/runToolkitContract.mjs +705 -0
- package/src/testing.mjs +3 -0
- package/src/ui/BomTableRenderer.mjs +304 -0
- package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
- package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
- package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
- package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
- package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
- package/src/ui/PcbSvgRenderer.mjs +41 -0
- package/src/ui/SafeSvgPaint.mjs +26 -0
- package/src/ui/SafeXmlText.mjs +60 -0
- package/src/ui/SchematicGeometryBounds.mjs +540 -0
- package/src/ui/SchematicSvgRenderer.mjs +110 -0
- package/src/ui/SchematicTableGeometry.mjs +319 -0
- package/src/ui/SchematicTextAnchor.mjs +55 -0
- package/src/ui/SchematicTextBounds.mjs +98 -0
- 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)
|