@runnee/sdk 0.0.0-stage → 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/CHANGELOG.md +9 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +21 -0
- package/README.md +76 -2
- package/RELEASING.md +32 -0
- package/SECURITY.md +11 -0
- package/lib/cjs/contracts.d.ts +1279 -0
- package/lib/cjs/contracts.js +1391 -0
- package/lib/cjs/errors.d.ts +34 -0
- package/lib/cjs/errors.js +49 -0
- package/lib/cjs/http.d.ts +25 -0
- package/lib/cjs/http.js +260 -0
- package/lib/cjs/index.d.ts +57 -0
- package/lib/cjs/index.js +86 -0
- package/lib/cjs/package.json +2 -0
- package/lib/cjs/types.d.ts +304 -0
- package/lib/cjs/types.js +2 -0
- package/lib/cjs/validation.d.ts +3 -0
- package/lib/cjs/validation.js +102 -0
- package/lib/esm/contracts.d.ts +1279 -0
- package/lib/esm/contracts.js +1388 -0
- package/lib/esm/errors.d.ts +34 -0
- package/lib/esm/errors.js +34 -0
- package/lib/esm/http.d.ts +25 -0
- package/lib/esm/http.js +256 -0
- package/lib/esm/index.d.ts +57 -0
- package/lib/esm/index.js +68 -0
- package/lib/esm/types.d.ts +304 -0
- package/lib/esm/types.js +1 -0
- package/lib/esm/validation.d.ts +3 -0
- package/lib/esm/validation.js +98 -0
- package/openapi.json +7932 -0
- package/package.json +64 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Errors never retain request headers, request bodies or raw response bodies. */
|
|
2
|
+
export declare class RunneeError extends Error {
|
|
3
|
+
readonly status?: number;
|
|
4
|
+
readonly requestId?: string;
|
|
5
|
+
readonly code?: string;
|
|
6
|
+
constructor(message: string, details?: {
|
|
7
|
+
status?: number;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
code?: string;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export declare class RunneeAuthenticationError extends RunneeError {
|
|
13
|
+
}
|
|
14
|
+
export declare class RunneePermissionError extends RunneeError {
|
|
15
|
+
}
|
|
16
|
+
export declare class RunneeValidationError extends RunneeError {
|
|
17
|
+
}
|
|
18
|
+
export declare class RunneeRateLimitError extends RunneeError {
|
|
19
|
+
retryAfter?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class RunneeTimeoutError extends RunneeError {
|
|
22
|
+
}
|
|
23
|
+
export declare class RunneeNetworkError extends RunneeError {
|
|
24
|
+
}
|
|
25
|
+
export declare class RunneeServerError extends RunneeError {
|
|
26
|
+
}
|
|
27
|
+
export declare class RunneeConflictError extends RunneeError {
|
|
28
|
+
}
|
|
29
|
+
export declare class RunneeNotFoundError extends RunneeError {
|
|
30
|
+
}
|
|
31
|
+
export declare class RunneeResponseError extends RunneeError {
|
|
32
|
+
}
|
|
33
|
+
export declare class RunneeCancelledError extends RunneeError {
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Errors never retain request headers, request bodies or raw response bodies. */
|
|
2
|
+
export class RunneeError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
requestId;
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, details = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = new.target.name;
|
|
9
|
+
Object.assign(this, details);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class RunneeAuthenticationError extends RunneeError {
|
|
13
|
+
}
|
|
14
|
+
export class RunneePermissionError extends RunneeError {
|
|
15
|
+
}
|
|
16
|
+
export class RunneeValidationError extends RunneeError {
|
|
17
|
+
}
|
|
18
|
+
export class RunneeRateLimitError extends RunneeError {
|
|
19
|
+
retryAfter;
|
|
20
|
+
}
|
|
21
|
+
export class RunneeTimeoutError extends RunneeError {
|
|
22
|
+
}
|
|
23
|
+
export class RunneeNetworkError extends RunneeError {
|
|
24
|
+
}
|
|
25
|
+
export class RunneeServerError extends RunneeError {
|
|
26
|
+
}
|
|
27
|
+
export class RunneeConflictError extends RunneeError {
|
|
28
|
+
}
|
|
29
|
+
export class RunneeNotFoundError extends RunneeError {
|
|
30
|
+
}
|
|
31
|
+
export class RunneeResponseError extends RunneeError {
|
|
32
|
+
}
|
|
33
|
+
export class RunneeCancelledError extends RunneeError {
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ResponseMetadata {
|
|
2
|
+
status: number;
|
|
3
|
+
requestId?: string;
|
|
4
|
+
rateLimitRemaining?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
idempotencyKey?: string;
|
|
11
|
+
onResponse?: (metadata: ResponseMetadata) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface RunneeOptions {
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
baseURL?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
fetch?: typeof globalThis.fetch;
|
|
19
|
+
allowInsecureHTTP?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare class HTTPClient {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options?: RunneeOptions);
|
|
24
|
+
request<T>(id: string, params?: Record<string, string | number | undefined>, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
25
|
+
}
|
package/lib/esm/http.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { contracts } from './contracts.js';
|
|
2
|
+
import { validate, encodeRequest } from './validation.js';
|
|
3
|
+
import { RunneeError, RunneeAuthenticationError, RunneePermissionError, RunneeValidationError, RunneeRateLimitError, RunneeTimeoutError, RunneeNetworkError, RunneeServerError, RunneeConflictError, RunneeNotFoundError, RunneeResponseError, RunneeCancelledError, } from './errors.js';
|
|
4
|
+
const operations = contracts.operations;
|
|
5
|
+
function integer(n, min, max, name) {
|
|
6
|
+
if (!Number.isSafeInteger(n) || n < min || n > max)
|
|
7
|
+
throw new RunneeValidationError(name + ' is outside the supported range.');
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
function retryAfter(raw) {
|
|
11
|
+
if (!raw)
|
|
12
|
+
return;
|
|
13
|
+
const n = Number(raw);
|
|
14
|
+
if (Number.isFinite(n) && n >= 0)
|
|
15
|
+
return n * 1000;
|
|
16
|
+
const date = Date.parse(raw);
|
|
17
|
+
if (Number.isFinite(date))
|
|
18
|
+
return Math.max(0, date - Date.now());
|
|
19
|
+
}
|
|
20
|
+
async function pause(ms, signal) {
|
|
21
|
+
if (signal.aborted)
|
|
22
|
+
throw new RunneeCancelledError('Request cancelled.');
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const done = () => {
|
|
25
|
+
signal.removeEventListener('abort', abort);
|
|
26
|
+
resolve();
|
|
27
|
+
};
|
|
28
|
+
const timer = setTimeout(done, ms);
|
|
29
|
+
const abort = () => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
signal.removeEventListener('abort', abort);
|
|
32
|
+
reject(new RunneeCancelledError('Request cancelled.'));
|
|
33
|
+
};
|
|
34
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function readJSON(response) {
|
|
38
|
+
if (!/\bapplication\/(?:[\w.+-]*\+)?json\b/i.test(response.headers.get('content-type') ?? ''))
|
|
39
|
+
throw new RunneeResponseError('Runnee returned a non-JSON response.');
|
|
40
|
+
const reader = response.body?.getReader();
|
|
41
|
+
if (!reader)
|
|
42
|
+
throw new RunneeResponseError('Runnee returned an empty response.');
|
|
43
|
+
const decoder = new TextDecoder(), parts = [];
|
|
44
|
+
let bytes = 0;
|
|
45
|
+
try {
|
|
46
|
+
for (;;) {
|
|
47
|
+
const { value, done } = await reader.read();
|
|
48
|
+
if (done)
|
|
49
|
+
break;
|
|
50
|
+
bytes += value.byteLength;
|
|
51
|
+
if (bytes > 5 * 1024 * 1024)
|
|
52
|
+
throw new RunneeResponseError('Runnee response exceeds the SDK size limit.');
|
|
53
|
+
parts.push(decoder.decode(value, { stream: true }));
|
|
54
|
+
}
|
|
55
|
+
parts.push(decoder.decode());
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(parts.join(''));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new RunneeResponseError('Runnee returned invalid JSON.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await reader.cancel().catch(() => undefined);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export class HTTPClient {
|
|
68
|
+
#key;
|
|
69
|
+
#url;
|
|
70
|
+
#fetch;
|
|
71
|
+
#timeout;
|
|
72
|
+
#retries;
|
|
73
|
+
constructor(options = {}) {
|
|
74
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined')
|
|
75
|
+
throw new RunneeValidationError('Runnee API keys must be used on a trusted server, not in a public browser application.');
|
|
76
|
+
const env = globalThis.process?.env;
|
|
77
|
+
const key = options.apiKey ?? env?.RUNNEE_API_KEY;
|
|
78
|
+
if (typeof key !== 'string' || !/^[A-Za-z0-9_-]{8,512}$/.test(key))
|
|
79
|
+
throw new RunneeAuthenticationError('Set a valid RUNNEE_API_KEY or provide apiKey.');
|
|
80
|
+
let url;
|
|
81
|
+
try {
|
|
82
|
+
url = new URL(options.baseURL ?? env?.RUNNEE_BASE_URL ?? 'https://api.runnee.cloud');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new RunneeValidationError('baseURL must be an absolute HTTPS origin.');
|
|
86
|
+
}
|
|
87
|
+
if (url.username ||
|
|
88
|
+
url.password ||
|
|
89
|
+
url.search ||
|
|
90
|
+
url.hash ||
|
|
91
|
+
url.pathname !== '/' ||
|
|
92
|
+
!['https:', 'http:'].includes(url.protocol) ||
|
|
93
|
+
(url.protocol === 'http:' &&
|
|
94
|
+
!['localhost', '127.0.0.1', '[::1]'].includes(url.hostname) &&
|
|
95
|
+
!options.allowInsecureHTTP))
|
|
96
|
+
throw new RunneeValidationError('baseURL requires HTTPS; HTTP is allowed only for loopback or explicit development use.');
|
|
97
|
+
this.#url = url.origin;
|
|
98
|
+
this.#key = key;
|
|
99
|
+
this.#timeout = integer(options.timeout ?? 30000, 1, 300000, 'timeout');
|
|
100
|
+
this.#retries = integer(options.maxRetries ?? 2, 0, 5, 'maxRetries');
|
|
101
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
102
|
+
if (typeof this.#fetch !== 'function')
|
|
103
|
+
throw new RunneeValidationError('This runtime requires fetch or a fetch implementation.');
|
|
104
|
+
}
|
|
105
|
+
async request(id, params = {}, body, options = {}) {
|
|
106
|
+
if (options.signal && typeof options.signal.addEventListener !== 'function')
|
|
107
|
+
throw new RunneeValidationError('signal must be an AbortSignal.');
|
|
108
|
+
const op = operations.find((o) => o.id === id);
|
|
109
|
+
if (!op)
|
|
110
|
+
throw new RunneeValidationError('Unsupported SDK operation.');
|
|
111
|
+
let pathname = op.path;
|
|
112
|
+
const query = new URLSearchParams();
|
|
113
|
+
for (const [key, value] of Object.entries(params)) {
|
|
114
|
+
if (value === undefined)
|
|
115
|
+
continue;
|
|
116
|
+
const marker = '{' + key + '}';
|
|
117
|
+
if (pathname.includes(marker)) {
|
|
118
|
+
const encoded = String(value);
|
|
119
|
+
if (!encoded || encoded === '.' || encoded === '..' || /[\/\\\x00-\x1f]/.test(encoded))
|
|
120
|
+
throw new RunneeValidationError('Invalid resource identifier.');
|
|
121
|
+
pathname = pathname.replace(marker, encodeURIComponent(encoded));
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
query.set(key, String(value));
|
|
125
|
+
}
|
|
126
|
+
if (pathname.includes('{'))
|
|
127
|
+
throw new RunneeValidationError('Missing resource identifier.');
|
|
128
|
+
const requestBody = body && typeof body === 'object' && !Array.isArray(body)
|
|
129
|
+
? Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined))
|
|
130
|
+
: body;
|
|
131
|
+
if (op.request)
|
|
132
|
+
validate(requestBody, op.request, false);
|
|
133
|
+
const payload = requestBody === undefined ? undefined : encodeRequest(requestBody);
|
|
134
|
+
if (options.idempotencyKey !== undefined &&
|
|
135
|
+
(id !== 'execute' || !/^[A-Za-z0-9_.:-]{8,128}$/.test(options.idempotencyKey)))
|
|
136
|
+
throw new RunneeValidationError('Use an 8–128 character idempotencyKey only for execute.');
|
|
137
|
+
const safe = op.method === 'GET' || (id === 'execute' && !!options.idempotencyKey);
|
|
138
|
+
const retries = integer(options.maxRetries ?? (safe ? this.#retries : 0), 0, 5, 'maxRetries');
|
|
139
|
+
if (!safe && retries > 0)
|
|
140
|
+
throw new RunneeValidationError('Automatic retries require a read operation or idempotent execution.');
|
|
141
|
+
const controller = new AbortController(), abort = () => controller.abort();
|
|
142
|
+
let timedOut = false;
|
|
143
|
+
const timeout = integer(options.timeout ?? this.#timeout, 1, 300000, 'timeout');
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
timedOut = true;
|
|
146
|
+
controller.abort();
|
|
147
|
+
}, timeout);
|
|
148
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
149
|
+
if (options.signal?.aborted)
|
|
150
|
+
controller.abort();
|
|
151
|
+
try {
|
|
152
|
+
for (let attempt = 0;; attempt++) {
|
|
153
|
+
if (controller.signal.aborted)
|
|
154
|
+
throw new RunneeCancelledError('Request cancelled.');
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await this.#fetch(this.#url + pathname + (query.size ? '?' + query : ''), {
|
|
158
|
+
method: op.method,
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: 'Bearer ' + this.#key,
|
|
161
|
+
Accept: 'application/json',
|
|
162
|
+
'User-Agent': 'runnee-js/0.1.0',
|
|
163
|
+
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
164
|
+
...(options.idempotencyKey ? { 'Idempotency-Key': options.idempotencyKey } : {}),
|
|
165
|
+
},
|
|
166
|
+
body: payload,
|
|
167
|
+
signal: controller.signal,
|
|
168
|
+
redirect: 'error',
|
|
169
|
+
credentials: 'omit',
|
|
170
|
+
cache: 'no-store',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
if (controller.signal.aborted)
|
|
175
|
+
throw new RunneeCancelledError('Request cancelled.');
|
|
176
|
+
if (attempt < retries) {
|
|
177
|
+
await pause(Math.min(10000, 250 * 2 ** attempt) * (0.5 + Math.random() * 0.5), controller.signal);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw new RunneeNetworkError('Unable to reach Runnee. An interrupted execution may still have run; inspect history before submitting with a new key.');
|
|
181
|
+
}
|
|
182
|
+
const requestId = response.headers.get('x-request-id') ?? undefined;
|
|
183
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
184
|
+
options.onResponse?.({
|
|
185
|
+
status: response.status,
|
|
186
|
+
requestId,
|
|
187
|
+
rateLimitRemaining: remaining === null ? undefined : Number(remaining),
|
|
188
|
+
});
|
|
189
|
+
const after = retryAfter(response.headers.get('retry-after'));
|
|
190
|
+
if ([429, 500, 502, 503, 504].includes(response.status) &&
|
|
191
|
+
attempt < retries &&
|
|
192
|
+
(after === undefined || after <= 60000)) {
|
|
193
|
+
await response.body?.cancel();
|
|
194
|
+
await pause(after ?? Math.min(10000, 250 * 2 ** attempt) * (0.5 + Math.random() * 0.5), controller.signal);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
await response.body?.cancel();
|
|
199
|
+
const E = response.status === 401
|
|
200
|
+
? RunneeAuthenticationError
|
|
201
|
+
: response.status === 403
|
|
202
|
+
? RunneePermissionError
|
|
203
|
+
: response.status === 404
|
|
204
|
+
? RunneeNotFoundError
|
|
205
|
+
: response.status === 409
|
|
206
|
+
? RunneeConflictError
|
|
207
|
+
: response.status === 429
|
|
208
|
+
? RunneeRateLimitError
|
|
209
|
+
: response.status >= 500
|
|
210
|
+
? RunneeServerError
|
|
211
|
+
: RunneeValidationError;
|
|
212
|
+
const error = new E(response.status === 409
|
|
213
|
+
? 'The request conflicts with existing state. An execution may be admitted or its idempotency key reused; inspect history before submitting again.'
|
|
214
|
+
: response.status === 401
|
|
215
|
+
? 'Invalid, expired or revoked Runnee API key.'
|
|
216
|
+
: response.status === 403
|
|
217
|
+
? 'The API key scope or resource access policy does not permit this operation.'
|
|
218
|
+
: 'Runnee API request failed (HTTP ' + response.status + ').', { status: response.status, requestId });
|
|
219
|
+
if (error instanceof RunneeRateLimitError)
|
|
220
|
+
error.retryAfter = after === undefined ? undefined : after / 1000;
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
let value;
|
|
224
|
+
try {
|
|
225
|
+
value = await readJSON(response);
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
if (e instanceof RunneeResponseError)
|
|
229
|
+
throw new RunneeResponseError(e.message, { status: response.status, requestId });
|
|
230
|
+
if (controller.signal.aborted)
|
|
231
|
+
throw e;
|
|
232
|
+
if (attempt < retries) {
|
|
233
|
+
await pause(Math.min(10000, 250 * 2 ** attempt) * (0.5 + Math.random() * 0.5), controller.signal);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
throw new RunneeNetworkError('Unable to read a complete Runnee response. Inspect history before resubmitting an uncertain execution.');
|
|
237
|
+
}
|
|
238
|
+
validate(value, op.response, true, requestId);
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (timedOut)
|
|
244
|
+
throw new RunneeTimeoutError('Request timed out. Execution may still have completed; reuse its idempotency key or inspect history.');
|
|
245
|
+
if (options.signal?.aborted)
|
|
246
|
+
throw new RunneeCancelledError('Request cancelled. Remote execution cancellation is not guaranteed.');
|
|
247
|
+
if (error instanceof RunneeError)
|
|
248
|
+
throw error;
|
|
249
|
+
throw new RunneeNetworkError('Runnee request could not be completed.');
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
options.signal?.removeEventListener('abort', abort);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type RunneeOptions, type RequestOptions } from './http.js';
|
|
2
|
+
import type * as T from './types.js';
|
|
3
|
+
export * from './errors.js';
|
|
4
|
+
export type * from './types.js';
|
|
5
|
+
export type { RunneeOptions, RequestOptions, ResponseMetadata } from './http.js';
|
|
6
|
+
export interface ExecutionFilters {
|
|
7
|
+
before?: string;
|
|
8
|
+
status?: 'success' | 'failed';
|
|
9
|
+
mode?: 'dry-run' | 'live';
|
|
10
|
+
}
|
|
11
|
+
export declare class Runnee {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(apiKey: string, options?: Omit<RunneeOptions, 'apiKey'>);
|
|
14
|
+
constructor(options?: RunneeOptions);
|
|
15
|
+
execute(input: T.ExecuteRequest, options?: RequestOptions): Promise<T.ExecuteResponse>;
|
|
16
|
+
readonly executions: {
|
|
17
|
+
get: (id: string, options?: RequestOptions) => Promise<T.ExecutionDetail>;
|
|
18
|
+
list: (filters?: ExecutionFilters, options?: RequestOptions) => Promise<T.ExecutionPage>;
|
|
19
|
+
iterate: (filters?: ExecutionFilters, options?: RequestOptions) => AsyncGenerator<T.Execution>;
|
|
20
|
+
};
|
|
21
|
+
private iterateExecutions;
|
|
22
|
+
readonly scripts: {
|
|
23
|
+
list: (options?: RequestOptions) => Promise<T.ScriptList>;
|
|
24
|
+
create: (input: T.ScriptCreate, options?: RequestOptions) => Promise<T.Script>;
|
|
25
|
+
get: (id: string, options?: RequestOptions) => Promise<T.ScriptDetail>;
|
|
26
|
+
update: (id: string, input: T.ScriptUpdate, options?: RequestOptions) => Promise<T.Script>;
|
|
27
|
+
delete: (id: string, options?: RequestOptions) => Promise<T.Deleted>;
|
|
28
|
+
getVersion: (id: string, version: number, options?: RequestOptions) => Promise<T.ScriptVersion>;
|
|
29
|
+
restore: (id: string, input: T.ScriptRestore, options?: RequestOptions) => Promise<T.Script>;
|
|
30
|
+
storage: (options?: RequestOptions) => Promise<T.Storage>;
|
|
31
|
+
};
|
|
32
|
+
readonly secrets: {
|
|
33
|
+
list: (options?: RequestOptions) => Promise<T.SecretList>;
|
|
34
|
+
get: (name: string, options?: RequestOptions) => Promise<T.Secret>;
|
|
35
|
+
create: (input: T.SecretCreate, options?: RequestOptions) => Promise<T.Secret>;
|
|
36
|
+
update: (name: string, input: T.SecretUpdate, options?: RequestOptions) => Promise<T.Secret>;
|
|
37
|
+
delete: (name: string, options?: RequestOptions) => Promise<T.Deleted>;
|
|
38
|
+
};
|
|
39
|
+
readonly usage: {
|
|
40
|
+
get: (options?: RequestOptions) => Promise<T.BillingStatus>;
|
|
41
|
+
summary: (options?: RequestOptions) => Promise<T.UsageSummary>;
|
|
42
|
+
history: (days?: 7 | 30 | 90, options?: RequestOptions) => Promise<T.UsageHistory>;
|
|
43
|
+
};
|
|
44
|
+
readonly sandbox: {
|
|
45
|
+
get: (options?: RequestOptions) => Promise<T.Sandbox>;
|
|
46
|
+
};
|
|
47
|
+
readonly billing: {
|
|
48
|
+
plans: (options?: RequestOptions) => Promise<T.Plans>;
|
|
49
|
+
};
|
|
50
|
+
readonly health: {
|
|
51
|
+
get: (options?: RequestOptions) => Promise<T.Health>;
|
|
52
|
+
};
|
|
53
|
+
readonly telemetry: {
|
|
54
|
+
recordExecution: (input: T.TelemetryExecution, options?: RequestOptions) => Promise<T.TelemetryRecorded>;
|
|
55
|
+
recordEvent: (input: T.TelemetryEvent, options?: RequestOptions) => Promise<T.Accepted>;
|
|
56
|
+
};
|
|
57
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { HTTPClient } from './http.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export class Runnee {
|
|
4
|
+
#http;
|
|
5
|
+
constructor(apiKeyOrOptions = {}, options = {}) {
|
|
6
|
+
this.#http = new HTTPClient(typeof apiKeyOrOptions === 'string'
|
|
7
|
+
? { ...options, apiKey: apiKeyOrOptions }
|
|
8
|
+
: apiKeyOrOptions);
|
|
9
|
+
}
|
|
10
|
+
execute(input, options) {
|
|
11
|
+
return this.#http.request('execute', {}, input, options);
|
|
12
|
+
}
|
|
13
|
+
executions = {
|
|
14
|
+
get: (id, options) => this.#http.request('executions.get', { id }, undefined, options),
|
|
15
|
+
list: (filters = {}, options) => this.#http.request('executions.list', { ...filters }, undefined, options),
|
|
16
|
+
iterate: (filters = {}, options) => this.iterateExecutions(filters, options),
|
|
17
|
+
};
|
|
18
|
+
async *iterateExecutions(filters, options) {
|
|
19
|
+
let before = filters.before;
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (;;) {
|
|
22
|
+
const page = await this.executions.list({ ...filters, before }, options);
|
|
23
|
+
for (const item of page.items)
|
|
24
|
+
yield item;
|
|
25
|
+
if (!page.nextCursor)
|
|
26
|
+
break;
|
|
27
|
+
if (seen.has(page.nextCursor))
|
|
28
|
+
throw new Error('Runnee returned a repeated pagination cursor.');
|
|
29
|
+
seen.add(page.nextCursor);
|
|
30
|
+
before = page.nextCursor;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
scripts = {
|
|
34
|
+
list: (options) => this.#http.request('scripts.list', {}, undefined, options),
|
|
35
|
+
create: (input, options) => this.#http.request('scripts.create', {}, input, options),
|
|
36
|
+
get: (id, options) => this.#http.request('scripts.get', { id }, undefined, options),
|
|
37
|
+
update: (id, input, options) => this.#http.request('scripts.update', { id }, input, options),
|
|
38
|
+
delete: (id, options) => this.#http.request('scripts.delete', { id }, undefined, options),
|
|
39
|
+
getVersion: (id, version, options) => this.#http.request('scripts.getVersion', { id, version }, undefined, options),
|
|
40
|
+
restore: (id, input, options) => this.#http.request('scripts.restore', { id }, input, options),
|
|
41
|
+
storage: (options) => this.#http.request('scripts.storage', {}, undefined, options),
|
|
42
|
+
};
|
|
43
|
+
secrets = {
|
|
44
|
+
list: (options) => this.#http.request('secrets.list', {}, undefined, options),
|
|
45
|
+
get: (name, options) => this.#http.request('secrets.get', { name }, undefined, options),
|
|
46
|
+
create: (input, options) => this.#http.request('secrets.create', {}, input, options),
|
|
47
|
+
update: (name, input, options) => this.#http.request('secrets.update', { name }, input, options),
|
|
48
|
+
delete: (name, options) => this.#http.request('secrets.delete', { name }, undefined, options),
|
|
49
|
+
};
|
|
50
|
+
usage = {
|
|
51
|
+
get: (options) => this.#http.request('usage.get', {}, undefined, options),
|
|
52
|
+
summary: (options) => this.#http.request('usage.summary', {}, undefined, options),
|
|
53
|
+
history: (days = 7, options) => this.#http.request('usage.history', { days }, undefined, options),
|
|
54
|
+
};
|
|
55
|
+
sandbox = {
|
|
56
|
+
get: (options) => this.#http.request('sandbox.get', {}, undefined, options),
|
|
57
|
+
};
|
|
58
|
+
billing = {
|
|
59
|
+
plans: (options) => this.#http.request('billing.plans', {}, undefined, options),
|
|
60
|
+
};
|
|
61
|
+
health = {
|
|
62
|
+
get: (options) => this.#http.request('health.get', {}, undefined, options),
|
|
63
|
+
};
|
|
64
|
+
telemetry = {
|
|
65
|
+
recordExecution: (input, options) => this.#http.request('telemetry.recordExecution', {}, input, options),
|
|
66
|
+
recordEvent: (input, options) => this.#http.request('telemetry.recordEvent', {}, input, options),
|
|
67
|
+
};
|
|
68
|
+
}
|