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.
- package/AGENTS.md +15 -0
- 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,418 @@
|
|
|
1
|
+
import { CIRCUIT_JSON_UPSTREAM_SCHEMAS } from './CircuitJsonUpstreamSchema.mjs'
|
|
2
|
+
import { CircuitJsonToolkitElementSchema } from './CircuitJsonToolkitElementSchema.mjs'
|
|
3
|
+
|
|
4
|
+
const DATETIME_PATTERNS = new Map()
|
|
5
|
+
const OBJECT_KEYS = new WeakMap()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates safe plain-data values against the generated upstream contract.
|
|
9
|
+
*/
|
|
10
|
+
export class CircuitJsonUpstreamValidator {
|
|
11
|
+
/**
|
|
12
|
+
* Validates one already descriptor-safe CircuitJSON element.
|
|
13
|
+
* @param {Record<string, any>} value Element value.
|
|
14
|
+
* @param {string} type Element type.
|
|
15
|
+
* @param {string} [location] Human-readable location suffix.
|
|
16
|
+
* @returns {string[]} Empty on success or one validation error.
|
|
17
|
+
*/
|
|
18
|
+
static validate(value, type, location = '') {
|
|
19
|
+
const schema = CIRCUIT_JSON_UPSTREAM_SCHEMAS[type]
|
|
20
|
+
if (!schema) {
|
|
21
|
+
if (CircuitJsonToolkitElementSchema.has(type)) {
|
|
22
|
+
return CircuitJsonToolkitElementSchema.validate(
|
|
23
|
+
value,
|
|
24
|
+
type,
|
|
25
|
+
location
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return [`Unsupported CircuitJSON element type: ${type}.`]
|
|
29
|
+
}
|
|
30
|
+
const matches =
|
|
31
|
+
type === 'source_net'
|
|
32
|
+
? CircuitJsonUpstreamValidator.#sourceNet(value)
|
|
33
|
+
: CircuitJsonUpstreamValidator.#matches(schema, value)
|
|
34
|
+
return matches
|
|
35
|
+
? []
|
|
36
|
+
: [
|
|
37
|
+
`CircuitJSON element ${type}${location} does not match the pinned upstream schema.`
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validates the benchmark-hot source-net schema without interpreter dispatch.
|
|
43
|
+
* @param {Record<string, any>} value Source-net value.
|
|
44
|
+
* @returns {boolean} Whether the complete upstream schema matches.
|
|
45
|
+
*/
|
|
46
|
+
static #sourceNet(value) {
|
|
47
|
+
if (
|
|
48
|
+
value.type !== 'source_net' ||
|
|
49
|
+
typeof value.source_net_id !== 'string' ||
|
|
50
|
+
typeof value.name !== 'string' ||
|
|
51
|
+
!CircuitJsonUpstreamValidator.#stringArray(
|
|
52
|
+
value.member_source_group_ids
|
|
53
|
+
)
|
|
54
|
+
) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
for (const field of [
|
|
58
|
+
'is_power',
|
|
59
|
+
'is_ground',
|
|
60
|
+
'is_digital_signal',
|
|
61
|
+
'is_analog_signal',
|
|
62
|
+
'is_positive_voltage_source'
|
|
63
|
+
]) {
|
|
64
|
+
if (
|
|
65
|
+
value[field] !== undefined &&
|
|
66
|
+
typeof value[field] !== 'boolean'
|
|
67
|
+
) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
value.trace_width !== undefined &&
|
|
73
|
+
(typeof value.trace_width !== 'number' ||
|
|
74
|
+
Number.isNaN(value.trace_width))
|
|
75
|
+
) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
for (const field of [
|
|
79
|
+
'subcircuit_id',
|
|
80
|
+
'subcircuit_connectivity_map_key'
|
|
81
|
+
]) {
|
|
82
|
+
if (
|
|
83
|
+
value[field] !== undefined &&
|
|
84
|
+
typeof value[field] !== 'string'
|
|
85
|
+
) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @param {unknown} value String-array candidate. @returns {boolean} */
|
|
93
|
+
static #stringArray(value) {
|
|
94
|
+
if (!Array.isArray(value)) return false
|
|
95
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
96
|
+
if (typeof value[index] !== 'string') return false
|
|
97
|
+
}
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Interprets one compact generated schema node.
|
|
103
|
+
* @param {any} node Compact schema node.
|
|
104
|
+
* @param {unknown} value Candidate value.
|
|
105
|
+
* @returns {boolean} Whether the value matches.
|
|
106
|
+
*/
|
|
107
|
+
static #matches(node, value) {
|
|
108
|
+
if (typeof node === 'string') {
|
|
109
|
+
if (node === 'a') return true
|
|
110
|
+
if (node === 'x') return false
|
|
111
|
+
if (node === 'b') return typeof value === 'boolean'
|
|
112
|
+
if (node === 's') return typeof value === 'string'
|
|
113
|
+
if (node === 'n') {
|
|
114
|
+
return typeof value === 'number' && !Number.isNaN(value)
|
|
115
|
+
}
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const tag = node[0]
|
|
120
|
+
if (tag === 'l') return value === node[1]
|
|
121
|
+
if (tag === 'e') return node[1].includes(value)
|
|
122
|
+
if (tag === '?') {
|
|
123
|
+
return (
|
|
124
|
+
value === undefined ||
|
|
125
|
+
CircuitJsonUpstreamValidator.#matches(node[1], value)
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (tag === '0') {
|
|
129
|
+
return (
|
|
130
|
+
value === null ||
|
|
131
|
+
CircuitJsonUpstreamValidator.#matches(node[1], value)
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
if (tag === 's') {
|
|
135
|
+
return CircuitJsonUpstreamValidator.#string(node[1], value)
|
|
136
|
+
}
|
|
137
|
+
if (tag === 'n') {
|
|
138
|
+
return CircuitJsonUpstreamValidator.#number(node[1], value)
|
|
139
|
+
}
|
|
140
|
+
if (tag === 'A') {
|
|
141
|
+
return CircuitJsonUpstreamValidator.#array(node, value)
|
|
142
|
+
}
|
|
143
|
+
if (tag === 't') {
|
|
144
|
+
return CircuitJsonUpstreamValidator.#tuple(node[1], value)
|
|
145
|
+
}
|
|
146
|
+
if (tag === 'o') {
|
|
147
|
+
return CircuitJsonUpstreamValidator.#object(node[1], value)
|
|
148
|
+
}
|
|
149
|
+
if (tag === 'r') {
|
|
150
|
+
return CircuitJsonUpstreamValidator.#record(node, value)
|
|
151
|
+
}
|
|
152
|
+
if (tag === 'u') {
|
|
153
|
+
for (let index = 1; index < node.length; index += 1) {
|
|
154
|
+
if (CircuitJsonUpstreamValidator.#matches(node[index], value)) {
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
if (tag === 'p') {
|
|
161
|
+
return CircuitJsonUpstreamValidator.#percentagePipeline(node, value)
|
|
162
|
+
}
|
|
163
|
+
if (tag === 'c') {
|
|
164
|
+
return CircuitJsonUpstreamValidator.#custom(node, value)
|
|
165
|
+
}
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @param {any[]} checks String checks. @param {unknown} value Candidate. @returns {boolean} */
|
|
170
|
+
static #string(checks, value) {
|
|
171
|
+
if (typeof value !== 'string') return false
|
|
172
|
+
for (const check of checks) {
|
|
173
|
+
if (check[0] === 'end') {
|
|
174
|
+
if (!value.endsWith(check[1])) return false
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
if (check[0] === 'dt') {
|
|
178
|
+
if (
|
|
179
|
+
!CircuitJsonUpstreamValidator.#datetime(check).test(value)
|
|
180
|
+
) {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** @param {any[]} checks Number checks. @param {unknown} value Candidate. @returns {boolean} */
|
|
191
|
+
static #number(checks, value) {
|
|
192
|
+
if (typeof value !== 'number' || Number.isNaN(value)) return false
|
|
193
|
+
for (const [kind, limit, inclusive] of checks) {
|
|
194
|
+
if (
|
|
195
|
+
kind === 'min' &&
|
|
196
|
+
(inclusive ? value < limit : value <= limit)
|
|
197
|
+
) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
if (
|
|
201
|
+
kind === 'max' &&
|
|
202
|
+
(inclusive ? value > limit : value >= limit)
|
|
203
|
+
) {
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
if (kind !== 'min' && kind !== 'max') return false
|
|
207
|
+
}
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** @param {any[]} node Array node. @param {unknown} value Candidate. @returns {boolean} */
|
|
212
|
+
static #array(node, value) {
|
|
213
|
+
if (!Array.isArray(value)) return false
|
|
214
|
+
const minimum = node[2]
|
|
215
|
+
const maximum = node[3]
|
|
216
|
+
if (minimum !== null && value.length < minimum) return false
|
|
217
|
+
if (maximum !== null && value.length > maximum) return false
|
|
218
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
219
|
+
if (!CircuitJsonUpstreamValidator.#matches(node[1], value[index])) {
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** @param {any[]} schemas Tuple item schemas. @param {unknown} value Candidate. @returns {boolean} */
|
|
227
|
+
static #tuple(schemas, value) {
|
|
228
|
+
if (!Array.isArray(value) || value.length !== schemas.length) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
for (let index = 0; index < schemas.length; index += 1) {
|
|
232
|
+
if (
|
|
233
|
+
!CircuitJsonUpstreamValidator.#matches(
|
|
234
|
+
schemas[index],
|
|
235
|
+
value[index]
|
|
236
|
+
)
|
|
237
|
+
) {
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** @param {Record<string, any>} properties Property schemas. @param {unknown} value Candidate. @returns {boolean} */
|
|
245
|
+
static #object(properties, value) {
|
|
246
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
let keys = OBJECT_KEYS.get(properties)
|
|
250
|
+
if (!keys) {
|
|
251
|
+
keys = Object.keys(properties)
|
|
252
|
+
OBJECT_KEYS.set(properties, keys)
|
|
253
|
+
}
|
|
254
|
+
for (const key of keys) {
|
|
255
|
+
const fieldSchema = properties[key]
|
|
256
|
+
const present = Object.hasOwn(value, key)
|
|
257
|
+
if (
|
|
258
|
+
!present &&
|
|
259
|
+
Array.isArray(fieldSchema) &&
|
|
260
|
+
fieldSchema[0] === '?'
|
|
261
|
+
) {
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
const field = present ? value[key] : undefined
|
|
265
|
+
if (!CircuitJsonUpstreamValidator.#matches(fieldSchema, field)) {
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return true
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @param {any[]} node Record node. @param {unknown} value Candidate. @returns {boolean} */
|
|
273
|
+
static #record(node, value) {
|
|
274
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
for (const [key, field] of Object.entries(value)) {
|
|
278
|
+
if (
|
|
279
|
+
!CircuitJsonUpstreamValidator.#matches(node[1], key) ||
|
|
280
|
+
!CircuitJsonUpstreamValidator.#matches(node[2], field)
|
|
281
|
+
) {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return true
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @param {any[]} node Pipeline node. @param {unknown} value Candidate. @returns {boolean} */
|
|
289
|
+
static #percentagePipeline(node, value) {
|
|
290
|
+
if (!CircuitJsonUpstreamValidator.#matches(node[1], value)) return false
|
|
291
|
+
let transformed = value
|
|
292
|
+
if (typeof transformed === 'string') {
|
|
293
|
+
transformed = transformed.endsWith('%')
|
|
294
|
+
? Number.parseFloat(transformed.slice(0, -1)) / 100
|
|
295
|
+
: Number.parseFloat(transformed)
|
|
296
|
+
}
|
|
297
|
+
return CircuitJsonUpstreamValidator.#matches(node[2], transformed)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** @param {any[]} node Custom node. @param {unknown} value Candidate. @returns {boolean} */
|
|
301
|
+
static #custom(node, value) {
|
|
302
|
+
if (!CircuitJsonUpstreamValidator.#matches(node[2], value)) return false
|
|
303
|
+
if (node[1] === 'battery') {
|
|
304
|
+
return (
|
|
305
|
+
typeof value !== 'string' ||
|
|
306
|
+
!Number.isNaN(Number.parseFloat(value.replace('mAh', '')))
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
if (node[1] === 'siUnitTransform') {
|
|
310
|
+
return CircuitJsonUpstreamValidator.#siUnitTransform(value)
|
|
311
|
+
}
|
|
312
|
+
if (node[1] === 'voltageProbe') {
|
|
313
|
+
return CircuitJsonUpstreamValidator.#voltageProbe(value)
|
|
314
|
+
}
|
|
315
|
+
if (node[1] === 'currentProbe') {
|
|
316
|
+
return CircuitJsonUpstreamValidator.#currentProbe(value)
|
|
317
|
+
}
|
|
318
|
+
if (node[1] === 'oscilloscopeTrace') {
|
|
319
|
+
return CircuitJsonUpstreamValidator.#oscilloscopeTrace(value)
|
|
320
|
+
}
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Preserves the pinned parseAndConvertSiUnit transform's throw boundary.
|
|
326
|
+
* Transform outputs remain intentionally unmaterialized.
|
|
327
|
+
* @param {unknown} value Transform input.
|
|
328
|
+
* @returns {boolean} Whether the pinned transform completes.
|
|
329
|
+
*/
|
|
330
|
+
static #siUnitTransform(value) {
|
|
331
|
+
if (typeof value !== 'string') return true
|
|
332
|
+
return /^-?[\d.]+$/u.test(value) || /[^\d\s]/u.test(value)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** @param {Record<string, any>} value Voltage probe. @returns {boolean} */
|
|
336
|
+
static #voltageProbe(value) {
|
|
337
|
+
const differential =
|
|
338
|
+
value.reference_input_source_port_id ||
|
|
339
|
+
value.reference_input_source_net_id
|
|
340
|
+
if (!differential) {
|
|
341
|
+
return (
|
|
342
|
+
Boolean(value.signal_input_source_port_id) !==
|
|
343
|
+
Boolean(value.signal_input_source_net_id)
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
const hasPorts = Boolean(
|
|
347
|
+
value.signal_input_source_port_id ||
|
|
348
|
+
value.reference_input_source_port_id
|
|
349
|
+
)
|
|
350
|
+
const hasNets = Boolean(
|
|
351
|
+
value.signal_input_source_net_id ||
|
|
352
|
+
value.reference_input_source_net_id
|
|
353
|
+
)
|
|
354
|
+
if (hasPorts && hasNets) return false
|
|
355
|
+
if (hasPorts) {
|
|
356
|
+
return Boolean(
|
|
357
|
+
value.signal_input_source_port_id &&
|
|
358
|
+
value.reference_input_source_port_id
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
if (hasNets) {
|
|
362
|
+
return Boolean(
|
|
363
|
+
value.signal_input_source_net_id &&
|
|
364
|
+
value.reference_input_source_net_id
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
return true
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** @param {Record<string, any>} value Current probe. @returns {boolean} */
|
|
371
|
+
static #currentProbe(value) {
|
|
372
|
+
const positivePort = Boolean(value.positive_source_port_id)
|
|
373
|
+
const negativePort = Boolean(value.negative_source_port_id)
|
|
374
|
+
const positiveNet = Boolean(value.positive_source_net_id)
|
|
375
|
+
const negativeNet = Boolean(value.negative_source_net_id)
|
|
376
|
+
const hasPorts = positivePort || negativePort
|
|
377
|
+
const hasNets = positiveNet || negativeNet
|
|
378
|
+
if (hasPorts && hasNets) return false
|
|
379
|
+
if (hasPorts) return positivePort && negativePort
|
|
380
|
+
if (hasNets) return positiveNet && negativeNet
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** @param {Record<string, any>} value Trace. @returns {boolean} */
|
|
385
|
+
static #oscilloscopeTrace(value) {
|
|
386
|
+
const voltageReferences = [
|
|
387
|
+
value.simulation_transient_voltage_graph_id,
|
|
388
|
+
value.simulation_voltage_probe_id
|
|
389
|
+
].filter((field) => field !== undefined).length
|
|
390
|
+
const currentReferences = [
|
|
391
|
+
value.simulation_transient_current_graph_id,
|
|
392
|
+
value.simulation_current_probe_id
|
|
393
|
+
].filter((field) => field !== undefined).length
|
|
394
|
+
return Boolean(
|
|
395
|
+
voltageReferences + currentReferences === 1 &&
|
|
396
|
+
!(voltageReferences > 0 && value.amps_per_div !== undefined) &&
|
|
397
|
+
!(currentReferences > 0 && value.volts_per_div !== undefined)
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @param {any[]} check Datetime instruction. @returns {RegExp} */
|
|
402
|
+
static #datetime(check) {
|
|
403
|
+
const key = check.slice(1).join(':')
|
|
404
|
+
if (DATETIME_PATTERNS.has(key)) return DATETIME_PATTERNS.get(key)
|
|
405
|
+
const [, precision, offset, local] = check
|
|
406
|
+
const date =
|
|
407
|
+
'((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))'
|
|
408
|
+
let seconds = '[0-5]\\d'
|
|
409
|
+
if (precision) seconds += `\\.\\d{${precision}}`
|
|
410
|
+
else if (precision === null) seconds += '(\\.\\d+)?'
|
|
411
|
+
const time = `([01]\\d|2[0-3]):[0-5]\\d(:${seconds})${precision ? '+' : '?'}`
|
|
412
|
+
const zones = [local ? 'Z?' : 'Z']
|
|
413
|
+
if (offset) zones.push('([+-]\\d{2}:?\\d{2})')
|
|
414
|
+
const pattern = new RegExp(`^${date}T${time}(${zones.join('|')})$`)
|
|
415
|
+
DATETIME_PATTERNS.set(key, pattern)
|
|
416
|
+
return pattern
|
|
417
|
+
}
|
|
418
|
+
}
|