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,790 @@
|
|
|
1
|
+
import { CircuitJsonDocumentContext } from './context/CircuitJsonDocumentContext.mjs'
|
|
2
|
+
import { ToolkitDiagnostic } from './contracts/ToolkitDiagnostic.mjs'
|
|
3
|
+
import { ToolkitError } from './contracts/ToolkitError.mjs'
|
|
4
|
+
import { ToolkitProgress } from './contracts/ToolkitProgress.mjs'
|
|
5
|
+
import { SimulationParameterCloner } from './simulation/SimulationParameterCloner.mjs'
|
|
6
|
+
import { SpiceSimulationService } from './spice/SpiceSimulationService.mjs'
|
|
7
|
+
|
|
8
|
+
const MODEL_TYPES = new Set([
|
|
9
|
+
'simulation_current_probe',
|
|
10
|
+
'simulation_current_source',
|
|
11
|
+
'simulation_op_amp',
|
|
12
|
+
'simulation_oscilloscope_trace',
|
|
13
|
+
'simulation_switch',
|
|
14
|
+
'simulation_voltage_probe',
|
|
15
|
+
'simulation_voltage_source'
|
|
16
|
+
])
|
|
17
|
+
const EXPORT = {
|
|
18
|
+
id: 'simulation-circuitjson-json',
|
|
19
|
+
format: 'json',
|
|
20
|
+
mediaType: 'application/json;charset=utf-8',
|
|
21
|
+
fileExtension: '.json'
|
|
22
|
+
}
|
|
23
|
+
const ABORTED_GETTER = Object.getOwnPropertyDescriptor(
|
|
24
|
+
AbortSignal.prototype,
|
|
25
|
+
'aborted'
|
|
26
|
+
)?.get
|
|
27
|
+
const ADD_EVENT_LISTENER = EventTarget.prototype.addEventListener
|
|
28
|
+
const REMOVE_EVENT_LISTENER = EventTarget.prototype.removeEventListener
|
|
29
|
+
const APPLY = Reflect.apply
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Exposes data-only simulation discovery, export, and injected execution.
|
|
33
|
+
*/
|
|
34
|
+
export class SimulationService {
|
|
35
|
+
/**
|
|
36
|
+
* Builds a canonical simulation definition from CircuitJSON rows.
|
|
37
|
+
* @param {unknown} document DocumentInput or prepared context.
|
|
38
|
+
* @param {Record<string, any>} [options] Reserved build options.
|
|
39
|
+
* @returns {{ schema: string, circuits: object[], analyses: object[], models: object[], diagnostics: object[], statistics: object }} Simulation definition.
|
|
40
|
+
*/
|
|
41
|
+
static build(document, options = {}) {
|
|
42
|
+
const context = SimulationService.#context(document, options)
|
|
43
|
+
return SimulationService.#definition(context)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Exports one canonical simulation definition file.
|
|
48
|
+
* @param {unknown} document DocumentInput or prepared context.
|
|
49
|
+
* @param {Record<string, any>} request Export request.
|
|
50
|
+
* @param {Record<string, any>} [options] Reserved build options.
|
|
51
|
+
* @returns {{ fileName: string, mediaType: string, data: string, diagnostics: object[] }} File result.
|
|
52
|
+
*/
|
|
53
|
+
static export(document, request, options = {}) {
|
|
54
|
+
const normalized = SimulationService.#exportRequest(request)
|
|
55
|
+
const context = SimulationService.#context(document, options)
|
|
56
|
+
const built = SimulationService.#built(context)
|
|
57
|
+
const capability = SimulationService.#exports(built).find(
|
|
58
|
+
(entry) => entry.id === normalized.id
|
|
59
|
+
)
|
|
60
|
+
if (!capability || capability.status !== 'available') {
|
|
61
|
+
throw SimulationService.#unavailable(capability?.reason)
|
|
62
|
+
}
|
|
63
|
+
const simulation = SimulationService.#definition(context)
|
|
64
|
+
return {
|
|
65
|
+
fileName:
|
|
66
|
+
SimulationService.#fileBase(context.source.fileName) +
|
|
67
|
+
'-circuitjson.json',
|
|
68
|
+
mediaType: capability.mediaType,
|
|
69
|
+
data: JSON.stringify(simulation, null, 2) + '\n',
|
|
70
|
+
diagnostics: structuredClone(simulation.diagnostics)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Runs one analysis only through an explicitly injected engine.
|
|
76
|
+
* @param {unknown} document DocumentInput or prepared context.
|
|
77
|
+
* @param {Record<string, any>} request Analysis request.
|
|
78
|
+
* @param {{ engine?: object, signal?: AbortSignal, onProgress?: Function }} [options] Execution dependencies.
|
|
79
|
+
* @returns {Promise<{ schema: string, status: string, traces: object[], measurements: object, diagnostics: object[], statistics: object }>} Simulation result.
|
|
80
|
+
*/
|
|
81
|
+
static async run(document, request, options = {}) {
|
|
82
|
+
const normalizedRequest = SimulationService.#runRequest(request)
|
|
83
|
+
const normalizedOptions = SimulationService.#runOptions(options)
|
|
84
|
+
const engine = normalizedOptions.engine
|
|
85
|
+
const run = SimulationService.#method(engine, 'run')
|
|
86
|
+
const simulate = SimulationService.#method(engine, 'simulate')
|
|
87
|
+
if (!run && !simulate) throw SimulationService.#unavailable()
|
|
88
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
89
|
+
let progress = SimulationService.#progress(
|
|
90
|
+
normalizedOptions.onProgress,
|
|
91
|
+
{
|
|
92
|
+
stage: 'detect',
|
|
93
|
+
detail: 'simulation-request',
|
|
94
|
+
completed: 0,
|
|
95
|
+
total: 4
|
|
96
|
+
},
|
|
97
|
+
null
|
|
98
|
+
)
|
|
99
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
100
|
+
|
|
101
|
+
const context = SimulationService.#context(document, {})
|
|
102
|
+
const simulation = SimulationService.build(context)
|
|
103
|
+
const analysis = simulation.analyses.find(
|
|
104
|
+
(entry) =>
|
|
105
|
+
String(entry.simulation_experiment_id || '') ===
|
|
106
|
+
normalizedRequest.analysisId
|
|
107
|
+
)
|
|
108
|
+
if (!analysis) {
|
|
109
|
+
throw SimulationService.#unavailable(
|
|
110
|
+
'The requested simulation analysis is unavailable.'
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
progress = SimulationService.#progress(
|
|
114
|
+
normalizedOptions.onProgress,
|
|
115
|
+
{
|
|
116
|
+
stage: 'decode',
|
|
117
|
+
detail: 'simulation-definition',
|
|
118
|
+
completed: 1,
|
|
119
|
+
total: 4
|
|
120
|
+
},
|
|
121
|
+
progress
|
|
122
|
+
)
|
|
123
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
124
|
+
const supports = SimulationService.#method(engine, 'supportsAnalysis')
|
|
125
|
+
let supported = true
|
|
126
|
+
if (supports) {
|
|
127
|
+
const supportAnalysis = structuredClone(analysis)
|
|
128
|
+
const supportParameters = structuredClone(
|
|
129
|
+
normalizedRequest.parameters
|
|
130
|
+
)
|
|
131
|
+
const operation = SimulationService.#invokeEngine(() =>
|
|
132
|
+
supports(supportAnalysis, supportParameters, {
|
|
133
|
+
signal: normalizedOptions.signal
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
supported = await SimulationService.#raceWithAbort(
|
|
137
|
+
operation,
|
|
138
|
+
normalizedOptions.signal
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
if (!supported) {
|
|
142
|
+
throw SimulationService.#unavailable(
|
|
143
|
+
'The injected engine does not support this analysis.'
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
147
|
+
progress = SimulationService.#progress(
|
|
148
|
+
normalizedOptions.onProgress,
|
|
149
|
+
{
|
|
150
|
+
stage: 'project',
|
|
151
|
+
detail: 'simulation-execution',
|
|
152
|
+
completed: 2,
|
|
153
|
+
total: 4
|
|
154
|
+
},
|
|
155
|
+
progress
|
|
156
|
+
)
|
|
157
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
158
|
+
|
|
159
|
+
const engineRequest = run
|
|
160
|
+
? {
|
|
161
|
+
schema: 'ecad-toolkit.simulation-run.v1',
|
|
162
|
+
document: context.model,
|
|
163
|
+
simulation,
|
|
164
|
+
analysis: structuredClone(analysis),
|
|
165
|
+
analysisId: normalizedRequest.analysisId,
|
|
166
|
+
parameters: structuredClone(normalizedRequest.parameters),
|
|
167
|
+
signal: normalizedOptions.signal
|
|
168
|
+
}
|
|
169
|
+
: null
|
|
170
|
+
const operation = run
|
|
171
|
+
? SimulationService.#invokeEngine(() => run(engineRequest))
|
|
172
|
+
: SimulationService.#simulate(engine, normalizedRequest.parameters)
|
|
173
|
+
const raw = await SimulationService.#raceWithAbort(
|
|
174
|
+
operation,
|
|
175
|
+
normalizedOptions.signal
|
|
176
|
+
)
|
|
177
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
178
|
+
progress = SimulationService.#progress(
|
|
179
|
+
normalizedOptions.onProgress,
|
|
180
|
+
{
|
|
181
|
+
stage: 'validate',
|
|
182
|
+
detail: 'simulation-result',
|
|
183
|
+
completed: 3,
|
|
184
|
+
total: 4
|
|
185
|
+
},
|
|
186
|
+
progress
|
|
187
|
+
)
|
|
188
|
+
SimulationService.#throwIfAborted(normalizedOptions.signal)
|
|
189
|
+
const result = SimulationService.#result(raw, context)
|
|
190
|
+
SimulationService.#progress(
|
|
191
|
+
normalizedOptions.onProgress,
|
|
192
|
+
{
|
|
193
|
+
stage: 'complete',
|
|
194
|
+
detail: 'simulation-result',
|
|
195
|
+
completed: 4,
|
|
196
|
+
total: 4
|
|
197
|
+
},
|
|
198
|
+
progress
|
|
199
|
+
)
|
|
200
|
+
return result
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Builds a detached canonical simulation definition from cached families.
|
|
205
|
+
* @param {CircuitJsonDocumentContext} context Prepared context.
|
|
206
|
+
* @returns {{ schema: string, circuits: object[], analyses: object[], models: object[], diagnostics: object[], statistics: object }} Simulation definition.
|
|
207
|
+
*/
|
|
208
|
+
static #definition(context) {
|
|
209
|
+
const built = SimulationService.#built(context)
|
|
210
|
+
return {
|
|
211
|
+
schema: 'ecad-toolkit.simulation.v1',
|
|
212
|
+
circuits: structuredClone(built.circuits),
|
|
213
|
+
analyses: structuredClone(built.analyses),
|
|
214
|
+
models: structuredClone(built.models),
|
|
215
|
+
diagnostics: structuredClone(built.diagnostics),
|
|
216
|
+
statistics: SimulationService.#statistics(context)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Builds the request-scoped internal simulation families.
|
|
222
|
+
* @param {CircuitJsonDocumentContext} context Prepared context.
|
|
223
|
+
* @returns {Record<string, any>} Internal simulation definition.
|
|
224
|
+
*/
|
|
225
|
+
static #built(context) {
|
|
226
|
+
return context.getOrCreateDerived('simulation', 'build-v1', () => {
|
|
227
|
+
const elements = context.getIndex('elements').elements || []
|
|
228
|
+
const simulationRows = elements.filter((element) =>
|
|
229
|
+
String(element.type || '').startsWith('simulation_')
|
|
230
|
+
)
|
|
231
|
+
return {
|
|
232
|
+
circuits: simulationRows.filter(
|
|
233
|
+
(element) => element.type === 'simulation_spice_subcircuit'
|
|
234
|
+
),
|
|
235
|
+
analyses: simulationRows.filter(
|
|
236
|
+
(element) => element.type === 'simulation_experiment'
|
|
237
|
+
),
|
|
238
|
+
models: simulationRows.filter((element) =>
|
|
239
|
+
MODEL_TYPES.has(element.type)
|
|
240
|
+
),
|
|
241
|
+
diagnostics: simulationRows
|
|
242
|
+
.filter((element) =>
|
|
243
|
+
/(?:error|warning)/u.test(String(element.type || ''))
|
|
244
|
+
)
|
|
245
|
+
.map((element) =>
|
|
246
|
+
ToolkitDiagnostic.create({
|
|
247
|
+
severity: String(element.type).includes('warning')
|
|
248
|
+
? 'warning'
|
|
249
|
+
: 'error',
|
|
250
|
+
code: String(
|
|
251
|
+
element.error_type ||
|
|
252
|
+
element.warning_type ||
|
|
253
|
+
element.type ||
|
|
254
|
+
'simulation_diagnostic'
|
|
255
|
+
),
|
|
256
|
+
message: String(
|
|
257
|
+
element.message || 'Simulation diagnostic.'
|
|
258
|
+
)
|
|
259
|
+
})
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Prepares one context and element index.
|
|
267
|
+
* @param {unknown} document Document input.
|
|
268
|
+
* @param {unknown} options Reserved options.
|
|
269
|
+
* @returns {CircuitJsonDocumentContext} Prepared context.
|
|
270
|
+
*/
|
|
271
|
+
static #context(document, options) {
|
|
272
|
+
SimulationService.#record(options, new Set())
|
|
273
|
+
try {
|
|
274
|
+
return CircuitJsonDocumentContext.prepare(document, {
|
|
275
|
+
indexes: ['elements']
|
|
276
|
+
})
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw ToolkitError.from(error, {
|
|
279
|
+
code: 'ERR_SIMULATION_DOCUMENT',
|
|
280
|
+
category: 'validation',
|
|
281
|
+
format: 'circuitjson'
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Builds simulation export availability rows.
|
|
288
|
+
* @param {Record<string, any>} built Internal simulation definition.
|
|
289
|
+
* @returns {object[]} Export capability rows.
|
|
290
|
+
*/
|
|
291
|
+
static #exports(built) {
|
|
292
|
+
const available =
|
|
293
|
+
built.circuits.length ||
|
|
294
|
+
built.analyses.length ||
|
|
295
|
+
built.models.length
|
|
296
|
+
return [
|
|
297
|
+
{
|
|
298
|
+
...EXPORT,
|
|
299
|
+
status: available ? 'available' : 'unavailable',
|
|
300
|
+
reason: available
|
|
301
|
+
? ''
|
|
302
|
+
: 'No simulation definition is available.'
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Normalizes one simulation export request.
|
|
309
|
+
* @param {unknown} request Request candidate.
|
|
310
|
+
* @returns {{ id: string, options: object }} Request.
|
|
311
|
+
*/
|
|
312
|
+
static #exportRequest(request) {
|
|
313
|
+
const normalized = SimulationService.#record(
|
|
314
|
+
request,
|
|
315
|
+
new Set(['id', 'options'])
|
|
316
|
+
)
|
|
317
|
+
if (
|
|
318
|
+
typeof normalized.id !== 'string' ||
|
|
319
|
+
!normalized.id.trim() ||
|
|
320
|
+
normalized.id.length > 256
|
|
321
|
+
) {
|
|
322
|
+
throw SimulationService.#requestError(
|
|
323
|
+
'Simulation export id must be a bounded string.'
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
id: normalized.id.trim(),
|
|
328
|
+
options: SimulationService.#record(
|
|
329
|
+
normalized.options ?? {},
|
|
330
|
+
new Set()
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Normalizes one analysis request.
|
|
337
|
+
* @param {unknown} request Request candidate.
|
|
338
|
+
* @returns {{ analysisId: string, parameters: object }} Request.
|
|
339
|
+
*/
|
|
340
|
+
static #runRequest(request) {
|
|
341
|
+
const normalized = SimulationService.#record(
|
|
342
|
+
request,
|
|
343
|
+
new Set(['analysisId', 'parameters'])
|
|
344
|
+
)
|
|
345
|
+
if (
|
|
346
|
+
typeof normalized.analysisId !== 'string' ||
|
|
347
|
+
!normalized.analysisId.trim() ||
|
|
348
|
+
normalized.analysisId.length > 256
|
|
349
|
+
) {
|
|
350
|
+
throw SimulationService.#requestError(
|
|
351
|
+
'Simulation analysisId must be a bounded string.'
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
const parameters = SimulationService.#cloneRecord(
|
|
355
|
+
normalized.parameters ?? {}
|
|
356
|
+
)
|
|
357
|
+
return { analysisId: normalized.analysisId.trim(), parameters }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Normalizes injected execution dependencies without invoking accessors.
|
|
362
|
+
* @param {unknown} options Options candidate.
|
|
363
|
+
* @returns {{ engine: object | null, signal: AbortSignal | null, onProgress: Function | null }} Options.
|
|
364
|
+
*/
|
|
365
|
+
static #runOptions(options) {
|
|
366
|
+
const normalized = SimulationService.#record(
|
|
367
|
+
options,
|
|
368
|
+
new Set(['engine', 'signal', 'onProgress'])
|
|
369
|
+
)
|
|
370
|
+
if (
|
|
371
|
+
normalized.onProgress !== undefined &&
|
|
372
|
+
typeof normalized.onProgress !== 'function'
|
|
373
|
+
) {
|
|
374
|
+
throw SimulationService.#requestError(
|
|
375
|
+
'Simulation onProgress must be a function.'
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
normalized.signal !== undefined &&
|
|
380
|
+
normalized.signal !== null &&
|
|
381
|
+
!SimulationService.#isAbortSignal(normalized.signal)
|
|
382
|
+
) {
|
|
383
|
+
throw SimulationService.#requestError(
|
|
384
|
+
'Simulation signal must be an AbortSignal.'
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
engine: normalized.engine || null,
|
|
389
|
+
signal: normalized.signal || null,
|
|
390
|
+
onProgress: normalized.onProgress || null
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Brand-checks an AbortSignal through its built-in state getter.
|
|
396
|
+
* @param {unknown} value Signal candidate.
|
|
397
|
+
* @returns {boolean} Whether the value owns AbortSignal internal state.
|
|
398
|
+
*/
|
|
399
|
+
static #isAbortSignal(value) {
|
|
400
|
+
try {
|
|
401
|
+
return (
|
|
402
|
+
typeof ABORTED_GETTER === 'function' &&
|
|
403
|
+
typeof ABORTED_GETTER.call(value) === 'boolean'
|
|
404
|
+
)
|
|
405
|
+
} catch {
|
|
406
|
+
return false
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Reads cancellation state through the built-in AbortSignal getter.
|
|
412
|
+
* @param {AbortSignal | null} signal Validated signal.
|
|
413
|
+
* @returns {boolean} Whether cancellation was requested.
|
|
414
|
+
*/
|
|
415
|
+
static #isAborted(signal) {
|
|
416
|
+
if (!signal) return false
|
|
417
|
+
try {
|
|
418
|
+
return ABORTED_GETTER.call(signal)
|
|
419
|
+
} catch {
|
|
420
|
+
throw SimulationService.#requestError(
|
|
421
|
+
'Simulation signal could not be inspected safely.'
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Races pending engine work against a genuine AbortSignal.
|
|
428
|
+
* @param {unknown} operation Promise or result to observe.
|
|
429
|
+
* @param {AbortSignal | null} signal Validated signal.
|
|
430
|
+
* @returns {Promise<unknown>} Operation result or typed cancellation.
|
|
431
|
+
*/
|
|
432
|
+
static #raceWithAbort(operation, signal) {
|
|
433
|
+
const pending = Promise.resolve(operation)
|
|
434
|
+
if (!signal) return pending
|
|
435
|
+
if (SimulationService.#isAborted(signal)) {
|
|
436
|
+
pending.catch(() => {})
|
|
437
|
+
return Promise.reject(SimulationService.#cancelled())
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
let settled = false
|
|
442
|
+
let listening = false
|
|
443
|
+
const cleanup = () => {
|
|
444
|
+
if (!listening) return
|
|
445
|
+
REMOVE_EVENT_LISTENER.call(signal, 'abort', onAbort)
|
|
446
|
+
listening = false
|
|
447
|
+
}
|
|
448
|
+
const settle = (callback, value) => {
|
|
449
|
+
if (settled) return
|
|
450
|
+
settled = true
|
|
451
|
+
cleanup()
|
|
452
|
+
callback(value)
|
|
453
|
+
}
|
|
454
|
+
const onAbort = () => settle(reject, SimulationService.#cancelled())
|
|
455
|
+
|
|
456
|
+
pending.then(
|
|
457
|
+
(value) => settle(resolve, value),
|
|
458
|
+
(error) => settle(reject, error)
|
|
459
|
+
)
|
|
460
|
+
try {
|
|
461
|
+
ADD_EVENT_LISTENER.call(signal, 'abort', onAbort, {
|
|
462
|
+
once: true
|
|
463
|
+
})
|
|
464
|
+
listening = true
|
|
465
|
+
if (SimulationService.#isAborted(signal)) onAbort()
|
|
466
|
+
} catch (error) {
|
|
467
|
+
settle(
|
|
468
|
+
reject,
|
|
469
|
+
ToolkitError.from(error, {
|
|
470
|
+
code: 'ERR_SIMULATION_REQUEST',
|
|
471
|
+
category: 'validation',
|
|
472
|
+
format: 'circuitjson'
|
|
473
|
+
})
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Copies a bounded plain parameter record into isolated data.
|
|
481
|
+
* @param {unknown} value Parameter candidate.
|
|
482
|
+
* @returns {object} Clone-safe parameters.
|
|
483
|
+
*/
|
|
484
|
+
static #cloneRecord(value) {
|
|
485
|
+
return SimulationParameterCloner.cloneRecord(value)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Reads an accessor-free plain record.
|
|
490
|
+
* @param {unknown} value Record candidate.
|
|
491
|
+
* @param {Set<string>} allowed Allowed keys.
|
|
492
|
+
* @returns {Record<string, any>} Safe shallow copy.
|
|
493
|
+
*/
|
|
494
|
+
static #record(value, allowed) {
|
|
495
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
496
|
+
throw SimulationService.#requestError(
|
|
497
|
+
'Simulation options must be a plain object.'
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
let prototype
|
|
501
|
+
let descriptors
|
|
502
|
+
try {
|
|
503
|
+
prototype = Object.getPrototypeOf(value)
|
|
504
|
+
descriptors = Object.getOwnPropertyDescriptors(value)
|
|
505
|
+
} catch {
|
|
506
|
+
throw SimulationService.#requestError(
|
|
507
|
+
'Simulation options could not be inspected safely.'
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
511
|
+
throw SimulationService.#requestError(
|
|
512
|
+
'Simulation options must be a plain object.'
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
const result = {}
|
|
516
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
517
|
+
const descriptor = descriptors[key]
|
|
518
|
+
if (
|
|
519
|
+
typeof key !== 'string' ||
|
|
520
|
+
!allowed.has(key) ||
|
|
521
|
+
descriptor.enumerable !== true ||
|
|
522
|
+
descriptor.get ||
|
|
523
|
+
descriptor.set
|
|
524
|
+
) {
|
|
525
|
+
throw SimulationService.#requestError(
|
|
526
|
+
'Simulation options contain an unsupported field.'
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
result[key] = descriptor.value
|
|
530
|
+
}
|
|
531
|
+
return result
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Resolves one data method without executing accessor properties.
|
|
536
|
+
* @param {unknown} target Method owner.
|
|
537
|
+
* @param {string} name Method name.
|
|
538
|
+
* @returns {Function | null} Bound method.
|
|
539
|
+
*/
|
|
540
|
+
static #method(target, name) {
|
|
541
|
+
if (!target || !['object', 'function'].includes(typeof target)) {
|
|
542
|
+
return null
|
|
543
|
+
}
|
|
544
|
+
let owner = target
|
|
545
|
+
for (let depth = 0; owner && depth < 16; depth += 1) {
|
|
546
|
+
let descriptor
|
|
547
|
+
try {
|
|
548
|
+
descriptor = Object.getOwnPropertyDescriptor(owner, name)
|
|
549
|
+
owner = Object.getPrototypeOf(owner)
|
|
550
|
+
} catch {
|
|
551
|
+
return null
|
|
552
|
+
}
|
|
553
|
+
if (!descriptor) continue
|
|
554
|
+
if (descriptor.get || descriptor.set) return null
|
|
555
|
+
if (typeof descriptor.value !== 'function') return null
|
|
556
|
+
const method = descriptor.value
|
|
557
|
+
return (...args) => APPLY(method, target, args)
|
|
558
|
+
}
|
|
559
|
+
return null
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Normalizes every value rejected across one injected callable boundary.
|
|
564
|
+
* @param {Function} invoke Deferred injected callable invocation.
|
|
565
|
+
* @returns {Promise<unknown>} Injected operation result.
|
|
566
|
+
*/
|
|
567
|
+
static async #invokeEngine(invoke) {
|
|
568
|
+
try {
|
|
569
|
+
return await invoke()
|
|
570
|
+
} catch (error) {
|
|
571
|
+
throw SimulationService.#engineFailure(error)
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Runs a legacy injected SPICE engine with an explicit netlist parameter.
|
|
577
|
+
* @param {object} engine Injected simulation engine.
|
|
578
|
+
* @param {Record<string, any>} parameters Analysis parameters.
|
|
579
|
+
* @returns {Promise<object>} Legacy simulation result.
|
|
580
|
+
*/
|
|
581
|
+
static async #simulate(engine, parameters) {
|
|
582
|
+
const spiceString = parameters.spiceString
|
|
583
|
+
if (
|
|
584
|
+
typeof spiceString !== 'string' ||
|
|
585
|
+
!spiceString.trim() ||
|
|
586
|
+
spiceString.length > 10_000_000
|
|
587
|
+
) {
|
|
588
|
+
throw SimulationService.#unavailable(
|
|
589
|
+
'The injected SPICE engine requires parameters.spiceString.'
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
return new SpiceSimulationService({ engine }).simulate(spiceString)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Normalizes a generic or legacy engine result.
|
|
597
|
+
* @param {unknown} raw Engine result.
|
|
598
|
+
* @param {CircuitJsonDocumentContext} context Prepared context.
|
|
599
|
+
* @returns {Record<string, any>} Canonical simulation result.
|
|
600
|
+
*/
|
|
601
|
+
static #result(raw, context) {
|
|
602
|
+
if (!raw || typeof raw !== 'object') {
|
|
603
|
+
throw new ToolkitError('Simulation engine returned no result.', {
|
|
604
|
+
code: 'ERR_SIMULATION_RESULT',
|
|
605
|
+
category: 'runtime',
|
|
606
|
+
format: 'circuitjson'
|
|
607
|
+
})
|
|
608
|
+
}
|
|
609
|
+
let traces
|
|
610
|
+
let measurements
|
|
611
|
+
let diagnostics
|
|
612
|
+
let status
|
|
613
|
+
try {
|
|
614
|
+
traces = structuredClone(
|
|
615
|
+
raw.traces ?? raw.simulationResultCircuitJson ?? []
|
|
616
|
+
)
|
|
617
|
+
measurements = structuredClone(
|
|
618
|
+
raw.measurements ?? raw.graphSummary ?? {}
|
|
619
|
+
)
|
|
620
|
+
const diagnosticRows = raw.diagnostics ?? []
|
|
621
|
+
if (!Array.isArray(diagnosticRows)) {
|
|
622
|
+
throw new TypeError('Simulation diagnostics must be an array.')
|
|
623
|
+
}
|
|
624
|
+
diagnostics = []
|
|
625
|
+
for (let index = 0; index < diagnosticRows.length; index += 1) {
|
|
626
|
+
diagnostics.push(
|
|
627
|
+
ToolkitDiagnostic.create(diagnosticRows[index])
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
const inferredStatus = diagnostics.some(
|
|
631
|
+
(entry) => entry.severity === 'error'
|
|
632
|
+
)
|
|
633
|
+
? 'failed'
|
|
634
|
+
: 'success'
|
|
635
|
+
status = String(raw.status || inferredStatus)
|
|
636
|
+
} catch {
|
|
637
|
+
throw new ToolkitError(
|
|
638
|
+
'Simulation engine result must be clone-safe data.',
|
|
639
|
+
{
|
|
640
|
+
code: 'ERR_SIMULATION_RESULT',
|
|
641
|
+
category: 'runtime',
|
|
642
|
+
format: 'circuitjson'
|
|
643
|
+
}
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
if (
|
|
647
|
+
!Array.isArray(traces) ||
|
|
648
|
+
!Array.isArray(diagnostics) ||
|
|
649
|
+
!measurements ||
|
|
650
|
+
typeof measurements !== 'object' ||
|
|
651
|
+
Array.isArray(measurements)
|
|
652
|
+
) {
|
|
653
|
+
throw new ToolkitError('Simulation engine result is malformed.', {
|
|
654
|
+
code: 'ERR_SIMULATION_RESULT',
|
|
655
|
+
category: 'runtime',
|
|
656
|
+
format: 'circuitjson'
|
|
657
|
+
})
|
|
658
|
+
}
|
|
659
|
+
if (!['success', 'failed', 'cancelled'].includes(status)) {
|
|
660
|
+
throw new ToolkitError('Simulation status is invalid.', {
|
|
661
|
+
code: 'ERR_SIMULATION_RESULT',
|
|
662
|
+
category: 'runtime',
|
|
663
|
+
format: 'circuitjson'
|
|
664
|
+
})
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
schema: 'ecad-toolkit.simulation-result.v1',
|
|
668
|
+
status,
|
|
669
|
+
traces,
|
|
670
|
+
measurements,
|
|
671
|
+
diagnostics,
|
|
672
|
+
statistics: {
|
|
673
|
+
...SimulationService.#statistics(context),
|
|
674
|
+
engineRuns: 1
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Emits one canonical progress event.
|
|
681
|
+
* @param {Function | null} callback Progress callback.
|
|
682
|
+
* @param {Record<string, any>} fields Progress fields.
|
|
683
|
+
* @param {Record<string, any> | null} previous Previous progress row.
|
|
684
|
+
* @returns {Record<string, any> | null} Current or previous progress row.
|
|
685
|
+
*/
|
|
686
|
+
static #progress(callback, fields, previous) {
|
|
687
|
+
if (!callback) return previous
|
|
688
|
+
const progress = ToolkitProgress.create(fields, previous)
|
|
689
|
+
callback(progress)
|
|
690
|
+
return progress
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Throws a typed cancellation when the signal is already aborted.
|
|
695
|
+
* @param {AbortSignal | null} signal Abort signal.
|
|
696
|
+
* @returns {void}
|
|
697
|
+
*/
|
|
698
|
+
static #throwIfAborted(signal) {
|
|
699
|
+
if (SimulationService.#isAborted(signal)) {
|
|
700
|
+
throw SimulationService.#cancelled()
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Builds stable context work counters.
|
|
706
|
+
* @param {CircuitJsonDocumentContext} context Prepared context.
|
|
707
|
+
* @returns {Record<string, number>} Statistics.
|
|
708
|
+
*/
|
|
709
|
+
static #statistics(context) {
|
|
710
|
+
const statistics = context.statistics
|
|
711
|
+
return {
|
|
712
|
+
validationPasses: statistics.validationPasses,
|
|
713
|
+
elementIndexBuilds: statistics.indexBuilds.elements || 0,
|
|
714
|
+
simulationBuilds:
|
|
715
|
+
statistics.derivedBuilds['simulation:build-v1'] || 0
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Creates a safe output base name.
|
|
721
|
+
* @param {unknown} fileName Source file name.
|
|
722
|
+
* @returns {string} File base.
|
|
723
|
+
*/
|
|
724
|
+
static #fileBase(fileName) {
|
|
725
|
+
const base = String(fileName || 'simulation')
|
|
726
|
+
.replaceAll('\\', '/')
|
|
727
|
+
.split('/')
|
|
728
|
+
.at(-1)
|
|
729
|
+
.replace(/\.[^.]+$/u, '')
|
|
730
|
+
.replace(/[^a-z0-9._-]+/giu, '-')
|
|
731
|
+
.replace(/^-|-$/gu, '')
|
|
732
|
+
return base || 'simulation'
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Contains any injected-engine failure in a fresh runtime ToolkitError.
|
|
737
|
+
* @param {unknown} error Engine failure candidate.
|
|
738
|
+
* @returns {ToolkitError} Fresh normalized runtime failure.
|
|
739
|
+
*/
|
|
740
|
+
static #engineFailure(error) {
|
|
741
|
+
const cause = ToolkitError.cloneSafeCause(error)
|
|
742
|
+
return new ToolkitError(cause?.message || 'Simulation engine failed.', {
|
|
743
|
+
code: 'ERR_SIMULATION_RUNTIME',
|
|
744
|
+
category: 'runtime',
|
|
745
|
+
format: 'circuitjson',
|
|
746
|
+
cause
|
|
747
|
+
})
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Creates a typed request error.
|
|
752
|
+
* @param {string} message Failure message.
|
|
753
|
+
* @returns {ToolkitError} Typed error.
|
|
754
|
+
*/
|
|
755
|
+
static #requestError(message) {
|
|
756
|
+
return new ToolkitError(message, {
|
|
757
|
+
code: 'ERR_SIMULATION_REQUEST',
|
|
758
|
+
category: 'validation',
|
|
759
|
+
format: 'circuitjson'
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Creates an unavailable-capability error.
|
|
765
|
+
* @param {string} [reason] Failure reason.
|
|
766
|
+
* @returns {ToolkitError} Typed error.
|
|
767
|
+
*/
|
|
768
|
+
static #unavailable(reason = '') {
|
|
769
|
+
return new ToolkitError(
|
|
770
|
+
reason || 'Simulation capability is unavailable.',
|
|
771
|
+
{
|
|
772
|
+
code: 'ERR_CAPABILITY_UNAVAILABLE',
|
|
773
|
+
category: 'unsupported',
|
|
774
|
+
format: 'circuitjson'
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Creates a typed cancellation error.
|
|
781
|
+
* @returns {ToolkitError} Typed error.
|
|
782
|
+
*/
|
|
783
|
+
static #cancelled() {
|
|
784
|
+
return new ToolkitError('Simulation was cancelled.', {
|
|
785
|
+
code: 'ERR_CANCELLED',
|
|
786
|
+
category: 'cancelled',
|
|
787
|
+
format: 'circuitjson'
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
}
|