@simantic/cloud-sim 0.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/README.md ADDED
@@ -0,0 +1,298 @@
1
+ # @simantic/cloud-sim
2
+
3
+ TypeScript client for the Cloud Sim API, which simulates MCU firmware in
4
+ the cloud. A run takes an ELF, an MCU and engine-neutral options, runs on
5
+ one of the API version's simulation engines (quartz by default), and hands
6
+ back the UART output and the engine's console, live as it runs and in full
7
+ after.
8
+
9
+ The package is ESM with no dependencies, built on `fetch`, so it runs on
10
+ Node 18 and later, in browsers, and on Deno, Bun and edge runtimes. Its
11
+ wire types are generated from the server's OpenAPI document
12
+ (`openapi/v1.json` in the cloud-sim repo), so they change only when the
13
+ contract does.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install @simantic/cloud-sim
19
+ ```
20
+
21
+ The package is private to the `simantic` npm organization for now: installing
22
+ it needs an npm token of an org member (`npm login`, or `NPM_TOKEN` on CI and
23
+ Vercel, which writes the `.npmrc` itself). The API it calls is open to any
24
+ simantic account; its contract is at https://cloud.simantic.com/v1/openapi.json.
25
+
26
+ ## Quick start
27
+
28
+ ### Node
29
+
30
+ ```ts
31
+ import { CloudSim } from '@simantic/cloud-sim'
32
+ import { fromPath } from '@simantic/cloud-sim/node'
33
+
34
+ const sim = new CloudSim({ apiKey: process.env.CLOUD_SIM_API_KEY!, version: '1.0' })
35
+
36
+ const run = await sim.runs.create({
37
+ mcu: 'STM32F072RB',
38
+ firmware: await fromPath('build/firmware.elf'),
39
+ simulatedSeconds: 5,
40
+ uartEncoding: 'ascii',
41
+ })
42
+ const done = await sim.runs.wait(run.id)
43
+ console.log(done.status, done.exit_code)
44
+ console.log(await sim.runs.output(run.id))
45
+ ```
46
+
47
+ `@simantic/cloud-sim/node` holds what needs Node (`fromPath` reads a file
48
+ into a Blob); the main entry needs nothing beyond `fetch`.
49
+
50
+ ### Browser
51
+
52
+ ```ts
53
+ import { CloudSim } from '@simantic/cloud-sim'
54
+
55
+ const sim = new CloudSim({
56
+ // Called before every request, so a refreshed token is picked up.
57
+ apiKey: async () => (await supabase.auth.getSession()).data.session?.access_token ?? '',
58
+ // A 401 calls this once, and the request is sent again.
59
+ onUnauthorized: async () => {
60
+ await supabase.auth.refreshSession()
61
+ },
62
+ })
63
+
64
+ const input = document.querySelector<HTMLInputElement>('input[type=file]')!
65
+ const run = await sim.runs.create({ mcu: 'STM32F072RB', firmware: input.files![0]! })
66
+ ```
67
+
68
+ ### The client
69
+
70
+ ```ts
71
+ new CloudSim({
72
+ apiKey, // an smtc_ API key or a dashboard session token, or a function giving one
73
+ version, // Cloud-Sim-Version, such as '1.0'; the server's newest stable without it
74
+ baseUrl, // default https://cloud.simantic.com
75
+ fetch, // default the global fetch
76
+ onUnauthorized, // called once on a 401, before the request is sent again
77
+ maxRetries, // default 3
78
+ timeoutMs, // how long each request has until its answer's headers arrive; default 60000
79
+ onRetry, // ({ attempt, error, delayMs }) => void, told before each retry
80
+ })
81
+ ```
82
+
83
+ Pin `version` in anything long-lived: a version pins each engine to one
84
+ release, so results do not change under you when the server's default
85
+ moves on. A call that makes runs or reads a catalog can name another
86
+ version for itself (`version`), so one client can serve a version picker.
87
+
88
+ Every call takes an optional `signal`. Aborting it ends the call, its
89
+ retries and the waits between them, and the call throws the signal's
90
+ reason.
91
+
92
+ ## Runs
93
+
94
+ ```ts
95
+ sim.runs.create({ mcu, engine?, firmware, simulatedSeconds?, uartEncoding?, uartMessagesOnly?,
96
+ trapUnmapped?, usbCdcConsoles?, idempotencyKey?, version?, timeoutMs?, signal? })
97
+ sim.runs.get(id, { signal? })
98
+ sim.runs.list({ limit?, before?, engine?, signal? })
99
+ sim.runs.result(id, { signal? }) // { exit_code, output, log, truncated } of a finished run
100
+ sim.runs.output(id, { signal? }) // the UART output, as text
101
+ sim.runs.log(id, { signal? }) // the engine's console, as text
102
+ sim.runs.cancel(id, { signal? })
103
+ sim.runs.wait(id, { signal?, intervalMs? })
104
+ sim.runs.follow(id, { signal?, transport?, intervalMs?, from? })
105
+ sim.runs.compare({ engines, mcu, firmware, ...options, version?, timeoutMs?, signal? })
106
+ ```
107
+
108
+ `firmware` is a `Blob` (a `File` included), a `Uint8Array`, an
109
+ `ArrayBuffer`, `{ url }` or `{ base64 }`. Bytes are uploaded as
110
+ multipart/form-data; a URL is fetched by the server (https only, at most
111
+ 24 MiB); base64 goes in JSON.
112
+
113
+ Every create carries an `Idempotency-Key`, generated when you give none, so
114
+ a create the SDK retries after a lost answer gets the run it already made
115
+ rather than a second one. To make a create safe across restarts of your
116
+ own program, pass your own key.
117
+
118
+ The client's `timeoutMs` counts the upload, since the server answers only
119
+ once it has the body. Give a create with a large firmware more:
120
+
121
+ ```ts
122
+ // A minute, and the upload at 1 Mbit/s (125 bytes a millisecond).
123
+ const timeoutMs = 60_000 + Math.ceil(file.size / 125)
124
+ await sim.runs.create({ mcu, firmware: file, timeoutMs })
125
+ ```
126
+
127
+ `runs.list()` reads nothing until used. Awaited, it is the first page;
128
+ iterated, it is every run, newest first:
129
+
130
+ ```ts
131
+ const { runs, next } = await sim.runs.list({ limit: 10 })
132
+ for await (const run of sim.runs.list({ engine: 'quartz' })) console.log(run.id, run.status)
133
+ ```
134
+
135
+ ## Following a run
136
+
137
+ `runs.follow` turns a run into events, from wherever it is to its end:
138
+
139
+ ```ts
140
+ let output = ''
141
+ const from = { log: 0, output: 0 }
142
+ for await (const event of sim.runs.follow(run.id, { signal })) {
143
+ switch (event.type) {
144
+ case 'status': console.log('now', event.run.status); break
145
+ case 'output': output += event.text; from.output = event.next; break
146
+ case 'log': process.stderr.write(event.text); from.log = event.next; break
147
+ case 'reset': if (event.stream === 'output') output = ''; break
148
+ case 'reconnecting': console.log('reconnecting:', event.error.message); break
149
+ case 'end': console.log('exit code', event.run.exit_code); break
150
+ }
151
+ }
152
+ ```
153
+
154
+ The `log` and `output` texts concatenate to the run's whole console and
155
+ UART output. A `reset` is rare: it says the run's final result differs from
156
+ what it shipped while it ran, so that stream starts over and what you kept
157
+ of it is to be dropped. Aborting `signal` ends the loop with the signal's
158
+ reason; `break` ends it quietly, and stops whatever request or retry it had
159
+ under way.
160
+
161
+ Each `log` and `output` event's `next` is where that stream has been read
162
+ to. A follow that stopped (the page was left, the retries ran out) picks up
163
+ there with `from`, and passes on only what is new:
164
+
165
+ ```ts
166
+ for await (const event of sim.runs.follow(run.id, { from })) { … }
167
+ ```
168
+
169
+ While a failure is being retried (a lost connection, a 503, the server's
170
+ `error` event), `follow` says so with a `reconnecting` event, once per
171
+ retry. Once it has recovered, the next event is a `status`, even if the
172
+ run's status is the same, so a UI can show "Reconnecting…" between the two.
173
+ When the retries run out, the loop throws the last failure.
174
+
175
+ `transport` picks how:
176
+
177
+ - `auto` (the default) reads the server-sent event stream, and falls back
178
+ to polling when that request fails before its first event (a server
179
+ without the route, something in between that will not pass an event
180
+ stream, a network failure);
181
+ - `sse` reads `GET /v1/runs/{id}/events`, parsed from `fetch` because
182
+ `EventSource` cannot send `Authorization`. The server closes each stream
183
+ after at most 10 minutes; the SDK reconnects with `Last-Event-ID` and
184
+ picks up after the last whole event. When the server cannot read the run
185
+ (a store outage) it sends an `error` event and closes; the SDK waits as
186
+ that event asks (5 s) and reconnects, and fails with the event's
187
+ `CloudSimError` only after `maxRetries` of them with nothing new between;
188
+ - `poll` pages `GET /v1/runs/{id}/live`, every `intervalMs` (750 ms) while
189
+ nothing is waiting and at once while more is.
190
+
191
+ ## Comparing engines
192
+
193
+ ```ts
194
+ import { CompareError, compareResults } from '@simantic/cloud-sim'
195
+
196
+ const { version, runs } = await sim.runs.compare({
197
+ engines: ['quartz', 'pyrite'],
198
+ mcu: 'STM32F072RB',
199
+ firmware: await fromPath('firmware.elf'),
200
+ simulatedSeconds: 5,
201
+ })
202
+ const [a, b] = await Promise.all(runs.map((r) => sim.runs.wait(r.id)))
203
+ const diff = compareResults(await sim.runs.result(a!.id), await sim.runs.result(b!.id))
204
+ // { exitCodes: [0, 0], sameExitCode: true, outputIdentical: false,
205
+ // firstDifference: { line: 12, a: '…', b: '…' } }
206
+ ```
207
+
208
+ `compare` refuses no engines, or an engine named twice, before asking
209
+ anything (400 `invalid_request`, `param: 'engines'`). It reads the version
210
+ first (its own `version`, the client's, or the server's default) and fails
211
+ with `engine_not_in_version` before uploading anything if an engine is
212
+ missing from it. It then asks for one run per engine in parallel, each with
213
+ its own idempotency key, all pinned to that version. Each run counts
214
+ against your limit of unfinished runs.
215
+
216
+ Every engine's run is asked for even when another is refused. When some
217
+ runs are made and others are not, it fails with a `CompareError`, a
218
+ `CloudSimError` whose status, code, param and request id are the first
219
+ failure's, and which is `transient` only if every failure is. Its `version`
220
+ is the version the comparison used, its `runs` the runs made, by engine,
221
+ which go on on the server, and its `errors` why each other engine's run was
222
+ not, by engine. The Python SDK's `CompareError` has the same fields:
223
+
224
+ ```ts
225
+ try {
226
+ await sim.runs.compare({ engines, mcu, firmware })
227
+ } catch (e) {
228
+ if (e instanceof CompareError) {
229
+ for (const run of Object.values(e.runs)) await sim.runs.cancel(run.id) // or follow them
230
+ for (const [engine, error] of Object.entries(e.errors)) console.log(engine, error)
231
+ }
232
+ }
233
+ ```
234
+
235
+ ## Versions and MCUs
236
+
237
+ ```ts
238
+ await sim.versions.list() // every version, and which is the default
239
+ await sim.versions.get('1.0') // engines, releases, options, limits, changes
240
+ await sim.versions.mcus('1.0', { engine: 'quartz', runnable: true })
241
+ await sim.mcus.list({ runnable: true }) // the client's version
242
+ await sim.mcus.list({ version: '1.1-preview.1' })
243
+ ```
244
+
245
+ These need no credential; a client made with `apiKey: ''` can read them.
246
+
247
+ ## Errors
248
+
249
+ Every failure is a `CloudSimError`, except an aborted `signal`, which
250
+ throws the signal's reason:
251
+
252
+ ```ts
253
+ import { CloudSimError, isTransient } from '@simantic/cloud-sim'
254
+
255
+ try {
256
+ await sim.runs.create({ mcu: 'NOPE', firmware })
257
+ } catch (e) {
258
+ if (e instanceof CloudSimError) {
259
+ e.status // the HTTP status, or 0 when no answer arrived
260
+ e.code // stable, such as 'mcu_not_supported', 'network_error', 'timeout'
261
+ e.param // the request field at fault, when there is one
262
+ e.requestId // the request's X-Request-Id, worth quoting in a bug report
263
+ e.transient // whether the same request may work later
264
+ }
265
+ isTransient(e) // the same, for any thrown value: false for anything but a CloudSimError
266
+ }
267
+ ```
268
+
269
+ Every call is retried on transient failures (no answer, a request past
270
+ `timeoutMs`, 429, 502, 503, 504) up to `maxRetries` times, waiting as
271
+ `Retry-After` says when the server says (a wait of more than a minute is
272
+ not waited out), and backing off from half a second otherwise. That is
273
+ safe for every call: a create carries an `Idempotency-Key`, and the server
274
+ answers a repeated cancel with the run as it is (a run that finished
275
+ meanwhile gets 409 `run_finished`, which is not retried). The client's
276
+ `onRetry` hears of each retry before its wait.
277
+
278
+ ## Development
279
+
280
+ This package and the MCP server (`mcp/`) are one npm workspace at the root
281
+ of the cloud-sim repo.
282
+
283
+ ```sh
284
+ npm ci # at the repo root
285
+ cd sdk/typescript
286
+ npm test
287
+ npm run typecheck
288
+ npm run build
289
+ npm run generate # after openapi/v1.json changes
290
+ npm run check:generated # fails when src/generated/v1.ts is stale
291
+ ```
292
+
293
+ Tests run against a fake `fetch`, and every JSON body the fake server
294
+ answers with is checked against the schemas in `openapi/v1.json`.
295
+
296
+ ## License
297
+
298
+ Proprietary (`UNLICENSED`), as the API itself is.
@@ -0,0 +1,32 @@
1
+ import { type Transport } from './http.js';
2
+ import type { CallOptions } from './runs.js';
3
+ import type { McuList, Version, VersionList } from './types.js';
4
+ export interface McuFilter extends CallOptions {
5
+ /** Only this engine's MCUs. An engine the version lacks is 400 `engine_not_in_version`. */
6
+ engine?: string;
7
+ /** Only MCUs that run: on `engine`, or without one on any engine. */
8
+ runnable?: boolean;
9
+ }
10
+ export interface McuListOptions extends McuFilter {
11
+ /** The API version whose catalog to read, in place of the client's. */
12
+ version?: string;
13
+ }
14
+ export declare class Versions {
15
+ private readonly http;
16
+ constructor(http: Transport);
17
+ /** Every version the server serves, oldest first, and which is the default. */
18
+ list(options?: CallOptions): Promise<VersionList>;
19
+ /** One version: its lifecycle, notes, engines, and changes against the one before. */
20
+ get(version: string, options?: CallOptions): Promise<Version>;
21
+ /** Version `version`'s MCU catalog. */
22
+ mcus(version: string, filter?: McuFilter): Promise<McuList>;
23
+ }
24
+ export declare class Mcus {
25
+ private readonly http;
26
+ constructor(http: Transport);
27
+ /**
28
+ * The MCU catalog of `version`, else of the client's version, else of the
29
+ * server's default.
30
+ */
31
+ list(options?: McuListOptions): Promise<McuList>;
32
+ }
@@ -0,0 +1,58 @@
1
+ // `client.versions` and `client.mcus`: the API versions, the engines each
2
+ // pins, and their MCU catalogs. The server needs no credential for these;
3
+ // the client sends its own anyway, which does no harm.
4
+ import { segment } from './http.js';
5
+ export class Versions {
6
+ http;
7
+ constructor(http) {
8
+ this.http = http;
9
+ }
10
+ /** Every version the server serves, oldest first, and which is the default. */
11
+ list(options = {}) {
12
+ return this.http.json({
13
+ method: 'GET',
14
+ path: '/v1/versions',
15
+ signal: options.signal,
16
+ retry: true,
17
+ });
18
+ }
19
+ /** One version: its lifecycle, notes, engines, and changes against the one before. */
20
+ get(version, options = {}) {
21
+ return this.http.json({
22
+ method: 'GET',
23
+ path: `/v1/versions/${segment(version)}`,
24
+ signal: options.signal,
25
+ retry: true,
26
+ });
27
+ }
28
+ /** Version `version`'s MCU catalog. */
29
+ mcus(version, filter = {}) {
30
+ return this.http.json({
31
+ method: 'GET',
32
+ path: `/v1/versions/${segment(version)}/mcus`,
33
+ query: { engine: filter.engine, runnable: filter.runnable },
34
+ signal: filter.signal,
35
+ retry: true,
36
+ });
37
+ }
38
+ }
39
+ export class Mcus {
40
+ http;
41
+ constructor(http) {
42
+ this.http = http;
43
+ }
44
+ /**
45
+ * The MCU catalog of `version`, else of the client's version, else of the
46
+ * server's default.
47
+ */
48
+ list(options = {}) {
49
+ return this.http.json({
50
+ method: 'GET',
51
+ path: '/v1/mcus',
52
+ query: { engine: options.engine, runnable: options.runnable },
53
+ version: options.version,
54
+ signal: options.signal,
55
+ retry: true,
56
+ });
57
+ }
58
+ }
@@ -0,0 +1,53 @@
1
+ import { Mcus, Versions } from './catalog.js';
2
+ import { type ApiKey, type RetryInfo } from './http.js';
3
+ import { Runs } from './runs.js';
4
+ export interface CloudSimOptions {
5
+ /**
6
+ * An `smtc_` API key, or a simantic dashboard session's access token. A
7
+ * function is called before every request, so it can hand back a token
8
+ * that has been refreshed since.
9
+ */
10
+ apiKey: ApiKey;
11
+ /**
12
+ * The API version (`Cloud-Sim-Version`), such as `1.0`: it pins each
13
+ * engine to one release. Without it the server answers with its newest
14
+ * stable version, which can change under a long-lived client.
15
+ */
16
+ version?: string;
17
+ /** Default `https://cloud.simantic.com`. */
18
+ baseUrl?: string;
19
+ /** Default the global fetch. */
20
+ fetch?: typeof fetch;
21
+ /**
22
+ * Called when the server refuses the credential (401), once per request,
23
+ * which is then sent again with `apiKey` as it is after. The dashboard
24
+ * refreshes its session here.
25
+ */
26
+ onUnauthorized?: () => Promise<void> | void;
27
+ /**
28
+ * How many times a transient failure (no answer, 429, 502, 503, 504) is
29
+ * retried: every call is safe to repeat (a create carries an
30
+ * Idempotency-Key, and a repeated cancel gets the run as it is). Default 3.
31
+ */
32
+ maxRetries?: number;
33
+ /**
34
+ * How long each request has until its answer's headers arrive, in
35
+ * milliseconds; past it, the request counts as unanswered (`timeout`,
36
+ * transient) and is retried as one. An upload counts against it, so
37
+ * `runs.create` takes its own. Reading the answer is not timed. 0 for no
38
+ * limit. Default 60000.
39
+ */
40
+ timeoutMs?: number;
41
+ /**
42
+ * Told before each retry: which retry, the failure, and how long until it
43
+ * is sent. For showing "Reconnecting…"; it must not throw.
44
+ */
45
+ onRetry?: (info: RetryInfo) => void;
46
+ }
47
+ /** A client of the Cloud Sim API. */
48
+ export declare class CloudSim {
49
+ readonly runs: Runs;
50
+ readonly versions: Versions;
51
+ readonly mcus: Mcus;
52
+ constructor(options: CloudSimOptions);
53
+ }
package/dist/client.js ADDED
@@ -0,0 +1,15 @@
1
+ import { Mcus, Versions } from './catalog.js';
2
+ import { Transport } from './http.js';
3
+ import { Runs } from './runs.js';
4
+ /** A client of the Cloud Sim API. */
5
+ export class CloudSim {
6
+ runs;
7
+ versions;
8
+ mcus;
9
+ constructor(options) {
10
+ const http = new Transport(options);
11
+ this.runs = new Runs(http);
12
+ this.versions = new Versions(http);
13
+ this.mcus = new Mcus(http);
14
+ }
15
+ }
@@ -0,0 +1,26 @@
1
+ import type { RunResult } from './types.js';
2
+ /** The first UART record two outputs do not share. */
3
+ export interface Difference {
4
+ /** Its line number, from 1. */
5
+ line: number;
6
+ /** The first output's record, or null if that output ended before it. */
7
+ a: string | null;
8
+ /** The second output's record, or null if that output ended before it. */
9
+ b: string | null;
10
+ }
11
+ export interface ResultComparison {
12
+ /** Each result's exit code: 0 ok, 1 the simulation failed, 2 a usage error. */
13
+ exitCodes: [number | null, number | null];
14
+ sameExitCode: boolean;
15
+ /** Whether the UART output is the same, byte for byte. */
16
+ outputIdentical: boolean;
17
+ /** Null when the outputs are identical. */
18
+ firstDifference: Difference | null;
19
+ }
20
+ /**
21
+ * How result `b` differs from result `a`: both exit codes, whether the UART
22
+ * output is identical, and the first record that differs. A record is shown
23
+ * without the newline that ends it, unless that newline is the only
24
+ * difference (`"r2\n"` against `"r2"`). A missing output counts as empty.
25
+ */
26
+ export declare function compareResults(a: Pick<RunResult, 'exit_code' | 'output'>, b: Pick<RunResult, 'exit_code' | 'output'>): ResultComparison;
@@ -0,0 +1,53 @@
1
+ // `compareResults`: how two runs' results differ, for a comparison of
2
+ // engines (`runs.compare`). Every engine writes the same UART record
3
+ // format: records, each ended by a newline. So the first record that
4
+ // differs is the first line that does, and a trailing newline ends the last
5
+ // record rather than starting an empty one. The Python SDK's
6
+ // `compare_results` reports the same.
7
+ /**
8
+ * How result `b` differs from result `a`: both exit codes, whether the UART
9
+ * output is identical, and the first record that differs. A record is shown
10
+ * without the newline that ends it, unless that newline is the only
11
+ * difference (`"r2\n"` against `"r2"`). A missing output counts as empty.
12
+ */
13
+ export function compareResults(a, b) {
14
+ const exitCodes = [a.exit_code ?? null, b.exit_code ?? null];
15
+ const left = a.output ?? '';
16
+ const right = b.output ?? '';
17
+ return {
18
+ exitCodes,
19
+ sameExitCode: exitCodes[0] === exitCodes[1],
20
+ outputIdentical: left === right,
21
+ firstDifference: left === right ? null : firstDifference(left, right),
22
+ };
23
+ }
24
+ function firstDifference(a, b) {
25
+ const left = records(a);
26
+ const right = records(b);
27
+ for (let i = 0; i < Math.max(left.length, right.length); i++) {
28
+ const ra = left[i] ?? null;
29
+ const rb = right[i] ?? null;
30
+ if (ra === rb)
31
+ continue;
32
+ const shown = [bare(ra), bare(rb)];
33
+ // Records that differ only in the newline ending one: show it.
34
+ return shown[0] === shown[1]
35
+ ? { line: i + 1, a: ra, b: rb }
36
+ : { line: i + 1, a: shown[0], b: shown[1] };
37
+ }
38
+ // Records join back into their text, so different texts have a record
39
+ // that differs; this is not reached.
40
+ return null;
41
+ }
42
+ /** `text`'s records, each with the newline that ends it (the last may have none). */
43
+ function records(text) {
44
+ const pieces = text.split('\n');
45
+ const last = pieces.pop();
46
+ const found = pieces.map((piece) => `${piece}\n`);
47
+ if (last)
48
+ found.push(last);
49
+ return found;
50
+ }
51
+ function bare(record) {
52
+ return record?.endsWith('\n') ? record.slice(0, -1) : record;
53
+ }
@@ -0,0 +1,61 @@
1
+ import type { Run, Version } from './types.js';
2
+ /** The fields of a `CloudSimError`, for building one. */
3
+ export interface CloudSimErrorInit {
4
+ status: number;
5
+ code: string;
6
+ param?: string | null;
7
+ requestId?: string | null;
8
+ /** Whether it may go away on its own; by default, what `status` says. */
9
+ transient?: boolean;
10
+ }
11
+ /**
12
+ * A request the Cloud Sim API refused or never answered.
13
+ *
14
+ * `status` is the HTTP status, or 0 when no answer arrived (offline, DNS, a
15
+ * CORS refusal, `timeoutMs` passed). `code` is a stable name for programs,
16
+ * such as `mcu_not_supported`, `network_error` or `timeout`; `param` names
17
+ * the request field at fault, when one is; `requestId` is the request's
18
+ * `X-Request-Id`, worth quoting in a bug report. `transient` says whether
19
+ * the same request may work later.
20
+ */
21
+ export declare class CloudSimError extends Error {
22
+ readonly status: number;
23
+ readonly code: string;
24
+ readonly param: string | null;
25
+ readonly requestId: string | null;
26
+ readonly transient: boolean;
27
+ constructor(message: string, init: CloudSimErrorInit);
28
+ }
29
+ /**
30
+ * `runs.compare` made some of its runs but not all. The runs it made go on
31
+ * on the server and count against the caller's limit: follow them, or
32
+ * cancel them. Its status, code, param and request id are the first
33
+ * failure's, and it is `transient` only if every failure is. The Python
34
+ * SDK's `CompareError` has the same fields.
35
+ */
36
+ export declare class CompareError extends CloudSimError {
37
+ /** The version the comparison was made on. */
38
+ readonly version: Version;
39
+ /** The runs made, by engine, in the order the engines were given. */
40
+ readonly runs: Record<string, Run>;
41
+ /**
42
+ * Why each other engine's run was not made, by engine, in the same order:
43
+ * a `CloudSimError`, or whatever else the create threw.
44
+ */
45
+ readonly errors: Record<string, unknown>;
46
+ constructor(version: Version, runs: Record<string, Run>, errors: Record<string, unknown>);
47
+ }
48
+ /**
49
+ * Whether `error` is a failure that may go away if the same request is made
50
+ * again: a `CloudSimError` that is `transient`.
51
+ */
52
+ export declare function isTransient(error: unknown): boolean;
53
+ /**
54
+ * Whether an answer with `status` may change if the request is made again:
55
+ * none arrived (0), the caller is at their run limit (429), or the server or
56
+ * the edge in front of it could not answer (502, 503, 504). Any other status
57
+ * is the server's answer to this request.
58
+ */
59
+ export declare function transientStatus(status: number): boolean;
60
+ /** The code the server gives an error of `status` that names none of its own. */
61
+ export declare function generalCode(status: number): string;