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,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
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ optionalAngle,
3
+ optionalLength,
4
+ optionalPoint,
5
+ optionalSize
6
+ } from './CircuitJsonUnitParsers.mjs'