withruntime 0.11.2 → 0.12.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/BLAXEL.md +2 -2
- package/COMPATIBILITY.md +74 -9
- package/DAYTONA.md +33 -5
- package/E2B.md +85 -24
- package/README.md +9 -8
- package/README.src.md +8 -7
- package/VERCEL.md +43 -5
- package/compatibility-lock.json +58 -49
- package/dist/billing-cli.d.ts +1 -1
- package/dist/billing-cli.js +77 -1
- package/dist/blaxel/errors.d.ts +14 -0
- package/dist/blaxel/errors.js +29 -0
- package/dist/blaxel/index.d.ts +2 -2
- package/dist/blaxel/index.js +4 -4
- package/dist/blaxel/interpreter.d.ts +2 -5
- package/dist/blaxel/sandbox.d.ts +14 -5
- package/dist/blaxel/sandbox.js +63 -7
- package/dist/blaxel/snapshot.d.ts +4 -0
- package/dist/blaxel/snapshot.js +4 -0
- package/dist/cli/logs.d.ts +21 -0
- package/dist/cli/logs.js +188 -0
- package/dist/cli/logs.test.d.ts +1 -0
- package/dist/cli/logs.test.js +272 -0
- package/dist/cli-extras.d.ts +3 -3
- package/dist/cli-extras.js +17 -12
- package/dist/cli-words.d.ts +10 -1
- package/dist/cli-words.js +18 -0
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +405 -204
- package/dist/client.d.ts +2 -4
- package/dist/client.js +27 -11
- package/dist/cloudflare/errors.d.ts +143 -0
- package/dist/cloudflare/errors.js +167 -0
- package/dist/cloudflare/index.d.ts +175 -6
- package/dist/cloudflare/index.js +416 -26
- package/dist/cloudflare/interpreter.d.ts +119 -0
- package/dist/cloudflare/interpreter.js +223 -0
- package/dist/cloudflare/streams.d.ts +38 -0
- package/dist/cloudflare/streams.js +145 -0
- package/dist/cloudflare/watch.d.ts +9 -0
- package/dist/cloudflare/watch.js +24 -0
- package/dist/codesandbox/index.d.ts +216 -28
- package/dist/codesandbox/index.js +478 -78
- package/dist/compat/core.d.ts +18 -0
- package/dist/compat/core.js +29 -0
- package/dist/compat/files.d.ts +1 -0
- package/dist/compat/files.js +25 -5
- package/dist/daytona/computer-use.d.ts +13 -4
- package/dist/daytona/computer-use.js +66 -8
- package/dist/daytona/daytona.d.ts +6 -0
- package/dist/daytona/daytona.js +30 -3
- package/dist/daytona/errors.d.ts +11 -0
- package/dist/daytona/errors.js +18 -0
- package/dist/daytona/filesystem.js +2 -1
- package/dist/daytona/image.d.ts +13 -9
- package/dist/daytona/image.js +18 -12
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/sandbox.d.ts +8 -2
- package/dist/daytona/sandbox.js +16 -2
- package/dist/domains/index.d.ts +9 -0
- package/dist/domains/index.js +10 -0
- package/dist/e2b/commands.d.ts +1 -1
- package/dist/e2b/commands.js +6 -7
- package/dist/e2b/errors.d.ts +17 -0
- package/dist/e2b/errors.js +40 -1
- package/dist/e2b/index.d.ts +2 -2
- package/dist/e2b/index.js +1 -1
- package/dist/e2b/sandbox.d.ts +27 -21
- package/dist/e2b/sandbox.js +34 -8
- package/dist/e2b/unsupported.js +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js +16 -4
- package/dist/freestyle/index.d.ts +285 -37
- package/dist/freestyle/index.js +656 -144
- package/dist/identity.d.ts +3 -0
- package/dist/identity.js +27 -0
- package/dist/images/index.d.ts +9 -0
- package/dist/images/index.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/ips/index.d.ts +9 -0
- package/dist/ips/index.js +10 -0
- package/dist/jobs/index.d.ts +9 -0
- package/dist/jobs/index.js +10 -0
- package/dist/keys.js +2 -0
- package/dist/login.js +2 -0
- package/dist/machine.d.ts +12 -3
- package/dist/machine.js +20 -3
- package/dist/mcp.js +1 -1
- package/dist/modal/index.d.ts +339 -61
- package/dist/modal/index.js +781 -121
- package/dist/network-cli.d.ts +3 -1
- package/dist/network-cli.js +78 -36
- package/dist/networks/index.d.ts +11 -0
- package/dist/networks/index.js +10 -0
- package/dist/ports/index.d.ts +9 -0
- package/dist/ports/index.js +10 -0
- package/dist/product-names.d.ts +52 -0
- package/dist/product-names.js +164 -0
- package/dist/product-renames.d.ts +103 -0
- package/dist/product-renames.js +189 -0
- package/dist/products/apps.d.ts +176 -0
- package/dist/products/apps.js +165 -0
- package/dist/products/billing.d.ts +41 -0
- package/dist/products/billing.js +13 -0
- package/dist/products/browser.d.ts +10 -0
- package/dist/products/browsers.d.ts +71 -0
- package/dist/products/browsers.js +102 -0
- package/dist/products/builds.d.ts +126 -0
- package/dist/products/builds.js +221 -0
- package/dist/products/index.d.ts +3 -16
- package/dist/products/index.js +3 -14
- package/dist/products/jobs.d.ts +17 -0
- package/dist/products/jobs.js +27 -10
- package/dist/products/logs.d.ts +245 -0
- package/dist/products/logs.js +274 -0
- package/dist/products/logs.test.d.ts +1 -0
- package/dist/products/logs.test.js +343 -0
- package/dist/products/network-products.d.ts +38 -7
- package/dist/products/network-products.js +31 -18
- package/dist/products/observability.d.ts +3 -2
- package/dist/products/private-network.js +1 -1
- package/dist/products/queues.d.ts +149 -0
- package/dist/products/queues.js +213 -0
- package/dist/products/referrals.d.ts +21 -7
- package/dist/products/referrals.js +3 -3
- package/dist/products/secrets.d.ts +7 -3
- package/dist/products/secrets.js +39 -18
- package/dist/products/signed-requests.d.ts +21 -0
- package/dist/products/signed-requests.js +11 -0
- package/dist/products/streams.d.ts +73 -0
- package/dist/products/streams.js +17 -0
- package/dist/products/switching.d.ts +35 -7
- package/dist/products/switching.js +4 -3
- package/dist/products/volumes.d.ts +3 -0
- package/dist/record-cli.d.ts +23 -0
- package/dist/record-cli.js +308 -0
- package/dist/renamed-products.d.ts +38 -0
- package/dist/renamed-products.js +34 -0
- package/dist/runloop/index.d.ts +9 -2
- package/dist/runloop/index.js +29 -8
- package/dist/runloop/sdk.d.ts +131 -0
- package/dist/runloop/sdk.js +167 -0
- package/dist/sandbox.d.ts +3 -2
- package/dist/sandbox.js +14 -9
- package/dist/sandboxes/index.d.ts +11 -0
- package/dist/sandboxes/index.js +12 -0
- package/dist/snapshots/index.d.ts +9 -0
- package/dist/snapshots/index.js +11 -0
- package/dist/snapshots.d.ts +7 -4
- package/dist/sprites/index.d.ts +343 -31
- package/dist/sprites/index.js +866 -84
- package/dist/ssh.d.ts +1 -1
- package/dist/ssh.js +11 -11
- package/dist/terminal.d.ts +24 -0
- package/dist/terminal.js +32 -0
- package/dist/transport.d.ts +15 -1
- package/dist/transport.js +56 -3
- package/dist/tunnel.js +1 -1
- package/dist/types.d.ts +63 -0
- package/dist/vercel/sandbox.js +5 -3
- package/dist/vercel/snapshot.js +10 -2
- package/dist/volumes/index.d.ts +9 -0
- package/dist/volumes/index.js +10 -0
- package/package.json +48 -2
package/dist/products/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { sandboxDesktop } from "./desktop.js";
|
|
|
3
3
|
import { sandboxBrowser } from "./browser.js";
|
|
4
4
|
import { sandboxInterpreter } from "./interpreter.js";
|
|
5
5
|
import { sandboxNetwork } from "./network.js";
|
|
6
|
-
import {
|
|
6
|
+
import { domains, ips, networks, ports } from "./network-products.js";
|
|
7
7
|
import { sandboxPreviews } from "./previews.js";
|
|
8
8
|
import { referrals } from "./referrals.js";
|
|
9
9
|
import { billing } from "./billing.js";
|
|
@@ -50,19 +50,8 @@ export const clientExtensions = {
|
|
|
50
50
|
sso,
|
|
51
51
|
domains,
|
|
52
52
|
ports,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
network,
|
|
56
|
-
};
|
|
57
|
-
/** Canonical CLI product names share the existing factory instance. Account
|
|
58
|
-
* commands such as secrets, events and billing retain their current names. */
|
|
59
|
-
export const clientProductAliases = {
|
|
60
|
-
image: "images",
|
|
61
|
-
volume: "volumes",
|
|
62
|
-
job: "jobs",
|
|
63
|
-
domain: "domains",
|
|
64
|
-
port: "ports",
|
|
65
|
-
address: "addresses",
|
|
53
|
+
ips,
|
|
54
|
+
networks,
|
|
66
55
|
};
|
|
67
56
|
export const sandboxExtensions = {
|
|
68
57
|
interpreter: sandboxInterpreter,
|
package/dist/products/jobs.d.ts
CHANGED
|
@@ -53,6 +53,9 @@ export type Job = {
|
|
|
53
53
|
version: number;
|
|
54
54
|
/** Unix milliseconds, or null when nothing more is scheduled. */
|
|
55
55
|
nextRunAt: number | null;
|
|
56
|
+
/** When `trigger` asked for a run that has not started yet, in Unix
|
|
57
|
+
* milliseconds; null otherwise. */
|
|
58
|
+
runRequestedAt: number | null;
|
|
56
59
|
/** Why the last due run could not start, retried every 30 seconds:
|
|
57
60
|
* `credits` (add credit), `capacity`, `cost_cap` (maxTotalCostMicros or a
|
|
58
61
|
* key's daily limit) or `permission` (the key that made the job lost it). */
|
|
@@ -68,6 +71,8 @@ export type JobRun = {
|
|
|
68
71
|
occurrenceId: string;
|
|
69
72
|
attempt: number;
|
|
70
73
|
scheduledFor: number;
|
|
74
|
+
/** `manual`: a run `trigger` asked for, and its retries. */
|
|
75
|
+
trigger: "schedule" | "manual";
|
|
71
76
|
resourceId: string;
|
|
72
77
|
/** `unknown`: the outcome could not be observed (the sandbox stopped under
|
|
73
78
|
* it). It is never retried by itself. */
|
|
@@ -159,6 +164,9 @@ export declare const JOB_LIMITS: {
|
|
|
159
164
|
};
|
|
160
165
|
/** Unix milliseconds from a Date, an ISO time or a number. */
|
|
161
166
|
export declare function jobTime(at: Date | string | number): number;
|
|
167
|
+
/** The API's schedule from what `create` and `reschedule` take: `{ at }`,
|
|
168
|
+
* `{ cron, timezone? }` or the raw `{ kind: ... }`. */
|
|
169
|
+
export declare function jobSchedule(input: CreateJob["schedule"]): JobSchedule;
|
|
162
170
|
/** The API's job body from `CreateJob`, with every default filled in and the
|
|
163
171
|
* sandbox's size checked before anything is sent. */
|
|
164
172
|
export declare function jobBody(input: CreateJob): Record<string, unknown>;
|
|
@@ -211,4 +219,13 @@ export declare function jobs(transport: Transport): {
|
|
|
211
219
|
/** End the job for good and stop any run in progress. A run that already
|
|
212
220
|
* finished keeps its result. Cancelling again answers the same job. */
|
|
213
221
|
cancel: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
222
|
+
/** Run it now, once, beside its schedule, which does not move. A paused
|
|
223
|
+
* job runs once and stays paused. While a run is starting or going it is
|
|
224
|
+
* refused with `run_in_progress`; a cancelled or finished job with
|
|
225
|
+
* `invalid_state`. The run is in `runs` with `trigger: "manual"`. */
|
|
226
|
+
trigger: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
227
|
+
/** A new schedule, in any form `create` takes: `{ cron, timezone? }` or
|
|
228
|
+
* `{ at }`. The next run is the new schedule's next time; a run going
|
|
229
|
+
* finishes as it began, and a paused job stays paused. */
|
|
230
|
+
reschedule: (id: string, schedule: CreateJob["schedule"], options?: RequestOptions) => Promise<Job>;
|
|
214
231
|
};
|
package/dist/products/jobs.js
CHANGED
|
@@ -47,25 +47,28 @@ export function jobTime(at) {
|
|
|
47
47
|
throw invalid(`schedule.at must be a time: a Date, an ISO time such as 2026-10-01T03:00:00Z, or Unix milliseconds; got ${String(at)}.`, "schedule.at");
|
|
48
48
|
return ms;
|
|
49
49
|
}
|
|
50
|
-
/** The API's
|
|
51
|
-
*
|
|
52
|
-
export function
|
|
53
|
-
const s = input
|
|
54
|
-
let schedule;
|
|
50
|
+
/** The API's schedule from what `create` and `reschedule` take: `{ at }`,
|
|
51
|
+
* `{ cron, timezone? }` or the raw `{ kind: ... }`. */
|
|
52
|
+
export function jobSchedule(input) {
|
|
53
|
+
const s = (input ?? {});
|
|
55
54
|
if (s.kind === "once" || (s.kind === undefined && s.at !== undefined))
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
return { kind: "once", at: jobTime(s.at) };
|
|
56
|
+
if (s.kind === "cron" || (s.kind === undefined && s.cron !== undefined)) {
|
|
58
57
|
const expression = String(s.expression ?? s.cron).trim();
|
|
59
58
|
if (expression.split(/\s+/).length !== 5)
|
|
60
59
|
throw invalid(`A cron schedule has five fields, minute hour day-of-month month day-of-week, such as "0 3 * * *"; got "${expression}".`, "schedule.cron");
|
|
61
|
-
|
|
60
|
+
return {
|
|
62
61
|
kind: "cron",
|
|
63
62
|
expression,
|
|
64
63
|
...(s.timezone === undefined ? {} : { timezone: s.timezone }),
|
|
65
64
|
};
|
|
66
65
|
}
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
throw invalid("schedule takes { at } or { cron }.", "schedule");
|
|
67
|
+
}
|
|
68
|
+
/** The API's job body from `CreateJob`, with every default filled in and the
|
|
69
|
+
* sandbox's size checked before anything is sent. */
|
|
70
|
+
export function jobBody(input) {
|
|
71
|
+
const schedule = jobSchedule(input.schedule);
|
|
69
72
|
const command = Array.isArray(input.command) ? { argv: input.command } : input.command;
|
|
70
73
|
if (!command?.argv?.length)
|
|
71
74
|
throw invalid('command is an argv array such as ["python3", "job.py"].', "command");
|
|
@@ -186,5 +189,19 @@ export function jobs(transport) {
|
|
|
186
189
|
/** End the job for good and stop any run in progress. A run that already
|
|
187
190
|
* finished keeps its result. Cancelling again answers the same job. */
|
|
188
191
|
cancel: (id, options) => verb(id, "cancel", options),
|
|
192
|
+
/** Run it now, once, beside its schedule, which does not move. A paused
|
|
193
|
+
* job runs once and stays paused. While a run is starting or going it is
|
|
194
|
+
* refused with `run_in_progress`; a cancelled or finished job with
|
|
195
|
+
* `invalid_state`. The run is in `runs` with `trigger: "manual"`. */
|
|
196
|
+
trigger: (id, options) => verb(id, "trigger", options),
|
|
197
|
+
/** A new schedule, in any form `create` takes: `{ cron, timezone? }` or
|
|
198
|
+
* `{ at }`. The next run is the new schedule's next time; a run going
|
|
199
|
+
* finishes as it began, and a paused job stays paused. */
|
|
200
|
+
reschedule: (id, schedule, options) => t.json({
|
|
201
|
+
method: "POST",
|
|
202
|
+
path: `/v1/jobs/${enc(id)}:reschedule`,
|
|
203
|
+
body: { schedule: jobSchedule(schedule) },
|
|
204
|
+
...options,
|
|
205
|
+
}),
|
|
189
206
|
};
|
|
190
207
|
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Page } from "../page.js";
|
|
2
|
+
import type { RequestOptions, Transport } from "../transport.js";
|
|
3
|
+
/** Native logsd hit_json. Nanoseconds stay strings so no precision is lost. */
|
|
4
|
+
export type LogLine = {
|
|
5
|
+
time: string;
|
|
6
|
+
timeUnixNano: string;
|
|
7
|
+
severity: number;
|
|
8
|
+
severityText: string;
|
|
9
|
+
body: unknown;
|
|
10
|
+
labels: Record<string, string>;
|
|
11
|
+
attributes: Record<string, unknown>;
|
|
12
|
+
traceId: string | null;
|
|
13
|
+
spanId: string | null;
|
|
14
|
+
};
|
|
15
|
+
export type LogSearch = {
|
|
16
|
+
query: string;
|
|
17
|
+
start?: string;
|
|
18
|
+
end?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
direction?: "forward" | "backward";
|
|
21
|
+
cursor?: string;
|
|
22
|
+
};
|
|
23
|
+
export type LogSearchResult = {
|
|
24
|
+
lines: LogLine[];
|
|
25
|
+
next?: string | null;
|
|
26
|
+
stats: {
|
|
27
|
+
ms: number;
|
|
28
|
+
blocks: number;
|
|
29
|
+
skippedByTime: number;
|
|
30
|
+
skippedByLabels: number;
|
|
31
|
+
skippedByWords: number;
|
|
32
|
+
scanned: number;
|
|
33
|
+
bytesRead: number;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export type TailGap = {
|
|
37
|
+
gap: true;
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
};
|
|
41
|
+
export type TailLine = LogLine & {
|
|
42
|
+
cursor: string;
|
|
43
|
+
gap?: false;
|
|
44
|
+
};
|
|
45
|
+
export type TailEvent = TailLine | TailGap;
|
|
46
|
+
export type LogQuery = {
|
|
47
|
+
query: string;
|
|
48
|
+
time?: string;
|
|
49
|
+
limit?: number;
|
|
50
|
+
direction?: "forward" | "backward";
|
|
51
|
+
};
|
|
52
|
+
export type LogRange = Omit<LogQuery, "time"> & {
|
|
53
|
+
start?: string;
|
|
54
|
+
end?: string;
|
|
55
|
+
step?: string;
|
|
56
|
+
interval?: string;
|
|
57
|
+
};
|
|
58
|
+
export type LokiResult = {
|
|
59
|
+
status: "success";
|
|
60
|
+
data: ({
|
|
61
|
+
resultType: "streams";
|
|
62
|
+
result: {
|
|
63
|
+
stream: Record<string, string>;
|
|
64
|
+
values: [string, string, Record<string, string>?][];
|
|
65
|
+
}[];
|
|
66
|
+
} | {
|
|
67
|
+
resultType: "matrix";
|
|
68
|
+
result: {
|
|
69
|
+
metric: Record<string, string>;
|
|
70
|
+
values: [number, string][];
|
|
71
|
+
}[];
|
|
72
|
+
} | {
|
|
73
|
+
resultType: "vector";
|
|
74
|
+
result: {
|
|
75
|
+
metric: Record<string, string>;
|
|
76
|
+
value: [number, string];
|
|
77
|
+
}[];
|
|
78
|
+
} | {
|
|
79
|
+
resultType: "scalar";
|
|
80
|
+
result: [number, string];
|
|
81
|
+
}) & {
|
|
82
|
+
stats?: Record<string, unknown>;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export type LabelResult = {
|
|
86
|
+
status: "success";
|
|
87
|
+
data: string[];
|
|
88
|
+
};
|
|
89
|
+
export type ViewInput = {
|
|
90
|
+
name: string;
|
|
91
|
+
labels: Record<string, string>;
|
|
92
|
+
query: string;
|
|
93
|
+
defaultRange?: string;
|
|
94
|
+
};
|
|
95
|
+
export type SavedView = ViewInput & {
|
|
96
|
+
id: string;
|
|
97
|
+
state: string;
|
|
98
|
+
createdAt: string;
|
|
99
|
+
};
|
|
100
|
+
export type AlertInput = {
|
|
101
|
+
name: string;
|
|
102
|
+
labels: Record<string, string>;
|
|
103
|
+
query: string;
|
|
104
|
+
condition: string;
|
|
105
|
+
for: string;
|
|
106
|
+
actions: Record<string, unknown>[];
|
|
107
|
+
};
|
|
108
|
+
export type Alert = AlertInput & {
|
|
109
|
+
id: string;
|
|
110
|
+
state: string;
|
|
111
|
+
createdAt: string;
|
|
112
|
+
};
|
|
113
|
+
/** OTLP/HTTP JSON; the server validates the signal's complete OTLP schema. */
|
|
114
|
+
export type OtlpLogs = {
|
|
115
|
+
resourceLogs: Record<string, unknown>[];
|
|
116
|
+
};
|
|
117
|
+
export type OtlpTraces = {
|
|
118
|
+
resourceSpans: Record<string, unknown>[];
|
|
119
|
+
};
|
|
120
|
+
export type TraceSpan = {
|
|
121
|
+
traceId: string;
|
|
122
|
+
spanId: string;
|
|
123
|
+
parentSpanId?: string;
|
|
124
|
+
name: string;
|
|
125
|
+
startTimeUnixNano: string;
|
|
126
|
+
endTimeUnixNano: string;
|
|
127
|
+
attributes?: {
|
|
128
|
+
key: string;
|
|
129
|
+
value: Record<string, unknown>;
|
|
130
|
+
}[];
|
|
131
|
+
status?: {
|
|
132
|
+
code?: number;
|
|
133
|
+
message?: string;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
export type Trace = {
|
|
137
|
+
resourceSpans: {
|
|
138
|
+
resource?: {
|
|
139
|
+
attributes?: {
|
|
140
|
+
key: string;
|
|
141
|
+
value: Record<string, unknown>;
|
|
142
|
+
}[];
|
|
143
|
+
};
|
|
144
|
+
scopeSpans: {
|
|
145
|
+
spans: TraceSpan[];
|
|
146
|
+
}[];
|
|
147
|
+
}[];
|
|
148
|
+
};
|
|
149
|
+
export type TraceSearchResult = {
|
|
150
|
+
traces: {
|
|
151
|
+
traceID: string;
|
|
152
|
+
rootServiceName: string;
|
|
153
|
+
rootTraceName: string;
|
|
154
|
+
startTimeUnixNano: string;
|
|
155
|
+
durationMs: number;
|
|
156
|
+
}[];
|
|
157
|
+
metrics?: Record<string, unknown>;
|
|
158
|
+
};
|
|
159
|
+
/** Hidden until Logs launches. */
|
|
160
|
+
export declare function logs(t: Transport): {
|
|
161
|
+
search: (body: LogSearch, options?: RequestOptions) => Promise<LogSearchResult>;
|
|
162
|
+
pages(input: LogSearch, options?: RequestOptions): AsyncGenerator<LogSearchResult>;
|
|
163
|
+
/** The record's cursor, not timestamp or text, identifies a line. The
|
|
164
|
+
* server replays strictly after it. An echoed boundary is ignored; two
|
|
165
|
+
* identical lines at different record positions are both delivered. */
|
|
166
|
+
tail(query: string, options?: RequestOptions & {
|
|
167
|
+
cursor?: string;
|
|
168
|
+
start?: string;
|
|
169
|
+
reconnectMs?: number;
|
|
170
|
+
}): AsyncGenerator<TailEvent>;
|
|
171
|
+
query: (query: LogQuery, options?: RequestOptions) => Promise<LokiResult>;
|
|
172
|
+
queryRange: (query: LogRange, options?: RequestOptions) => Promise<LokiResult>;
|
|
173
|
+
labels: (query?: {
|
|
174
|
+
start?: string;
|
|
175
|
+
end?: string;
|
|
176
|
+
}, options?: RequestOptions) => Promise<LabelResult>;
|
|
177
|
+
labelValues: (name: string, query?: {
|
|
178
|
+
start?: string;
|
|
179
|
+
end?: string;
|
|
180
|
+
}, options?: RequestOptions) => Promise<LabelResult>;
|
|
181
|
+
views: {
|
|
182
|
+
create: (body: ViewInput, options?: RequestOptions) => Promise<SavedView>;
|
|
183
|
+
get: (id: string, options?: RequestOptions) => Promise<SavedView>;
|
|
184
|
+
list: (query?: {
|
|
185
|
+
limit?: number;
|
|
186
|
+
cursor?: string;
|
|
187
|
+
}, options?: RequestOptions) => Promise<Page<SavedView>>;
|
|
188
|
+
update: (id: string, body: Partial<ViewInput>, options?: RequestOptions) => Promise<SavedView>;
|
|
189
|
+
delete: (id: string, options?: RequestOptions) => Promise<void>;
|
|
190
|
+
};
|
|
191
|
+
ingest: (body: OtlpLogs, options?: RequestOptions) => Promise<{
|
|
192
|
+
partialSuccess?: {
|
|
193
|
+
rejectedLogRecords?: string;
|
|
194
|
+
rejectedSpans?: string;
|
|
195
|
+
errorMessage?: string;
|
|
196
|
+
};
|
|
197
|
+
}>;
|
|
198
|
+
};
|
|
199
|
+
export declare function traces(t: Transport): {
|
|
200
|
+
get: (id: string, options?: RequestOptions) => Promise<Trace>;
|
|
201
|
+
search: (q: string, options?: RequestOptions) => Promise<TraceSearchResult>;
|
|
202
|
+
tags: (options?: RequestOptions) => Promise<{
|
|
203
|
+
tagNames: string[];
|
|
204
|
+
}>;
|
|
205
|
+
views: {
|
|
206
|
+
create: (body: ViewInput, options?: RequestOptions) => Promise<SavedView>;
|
|
207
|
+
get: (id: string, options?: RequestOptions) => Promise<SavedView>;
|
|
208
|
+
list: (query?: {
|
|
209
|
+
limit?: number;
|
|
210
|
+
cursor?: string;
|
|
211
|
+
}, options?: RequestOptions) => Promise<Page<SavedView>>;
|
|
212
|
+
update: (id: string, body: Partial<ViewInput>, options?: RequestOptions) => Promise<SavedView>;
|
|
213
|
+
delete: (id: string, options?: RequestOptions) => Promise<void>;
|
|
214
|
+
};
|
|
215
|
+
ingest: (body: OtlpTraces, options?: RequestOptions) => Promise<{
|
|
216
|
+
partialSuccess?: {
|
|
217
|
+
rejectedLogRecords?: string;
|
|
218
|
+
rejectedSpans?: string;
|
|
219
|
+
errorMessage?: string;
|
|
220
|
+
};
|
|
221
|
+
}>;
|
|
222
|
+
};
|
|
223
|
+
export declare function alerts(t: Transport): {
|
|
224
|
+
test: (id: string, options?: RequestOptions) => Promise<{
|
|
225
|
+
firing: boolean;
|
|
226
|
+
matches: number;
|
|
227
|
+
}>;
|
|
228
|
+
silence: (id: string, input: {
|
|
229
|
+
until: string;
|
|
230
|
+
reason?: string;
|
|
231
|
+
}, options?: RequestOptions) => Promise<Alert>;
|
|
232
|
+
create: (body: AlertInput, options?: RequestOptions) => Promise<Alert>;
|
|
233
|
+
get: (id: string, options?: RequestOptions) => Promise<Alert>;
|
|
234
|
+
list: (query?: {
|
|
235
|
+
limit?: number;
|
|
236
|
+
cursor?: string;
|
|
237
|
+
}, options?: RequestOptions) => Promise<Page<Alert>>;
|
|
238
|
+
update: (id: string, body: Partial<AlertInput>, options?: RequestOptions) => Promise<Alert>;
|
|
239
|
+
delete: (id: string, options?: RequestOptions) => Promise<void>;
|
|
240
|
+
};
|
|
241
|
+
export type ObservabilityClient = {
|
|
242
|
+
logs: ReturnType<typeof logs>;
|
|
243
|
+
traces: ReturnType<typeof traces>;
|
|
244
|
+
alerts: ReturnType<typeof alerts>;
|
|
245
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { RuntimeError, InvalidRequestError } from "../errors.js";
|
|
2
|
+
import { Page } from "../page.js";
|
|
3
|
+
const enc = encodeURIComponent;
|
|
4
|
+
const protocol = (message) => new RuntimeError({
|
|
5
|
+
code: "invalid_stream",
|
|
6
|
+
status: 422,
|
|
7
|
+
message,
|
|
8
|
+
hint: "Reconnect the log tail. If this repeats, report the request to support.",
|
|
9
|
+
});
|
|
10
|
+
/** Shared CRUD keeps the saved log and trace views on identical contracts. */
|
|
11
|
+
function resources(t, path) {
|
|
12
|
+
const list = async (query = {}, options) => {
|
|
13
|
+
const page = await t.json({
|
|
14
|
+
method: "GET",
|
|
15
|
+
path,
|
|
16
|
+
query,
|
|
17
|
+
...options,
|
|
18
|
+
});
|
|
19
|
+
return new Page(page.data, page.nextCursor, (cursor) => list({ ...query, cursor }, options));
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
create: (body, options) => t.json({ method: "POST", path, body, ...options }),
|
|
23
|
+
get: (id, options) => t.json({ method: "GET", path: `${path}/${enc(id)}`, ...options }),
|
|
24
|
+
list,
|
|
25
|
+
update: (id, body, options) => t.json({ method: "PUT", path: `${path}/${enc(id)}`, body, ...options }),
|
|
26
|
+
delete: (id, options) => t.json({ method: "DELETE", path: `${path}/${enc(id)}`, ...options }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function ingest(t, path, body, options) {
|
|
30
|
+
if (new TextEncoder().encode(JSON.stringify(body)).length > 1_048_576)
|
|
31
|
+
throw new InvalidRequestError({
|
|
32
|
+
code: "payload_too_large",
|
|
33
|
+
status: 413,
|
|
34
|
+
message: "OTLP JSON exceeds 1 MiB.",
|
|
35
|
+
hint: "Split the write into smaller batches, each with its own idempotency key.",
|
|
36
|
+
});
|
|
37
|
+
return t.json({ method: "POST", path, body, ...options });
|
|
38
|
+
}
|
|
39
|
+
/** Parse SSE incrementally, including split UTF-8, CRLF and multi-line data.
|
|
40
|
+
* Incomplete events at disconnect are discarded and replayed from the cursor.
|
|
41
|
+
* A finite event bound prevents a broken peer from growing memory forever. */
|
|
42
|
+
async function* events(stream, signal) {
|
|
43
|
+
const reader = stream.getReader();
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
let buffer = "", id = "", event = "", data = [], size = 0;
|
|
46
|
+
const cancel = () => {
|
|
47
|
+
void reader.cancel(signal?.reason).catch(() => undefined);
|
|
48
|
+
};
|
|
49
|
+
if (signal?.aborted)
|
|
50
|
+
cancel();
|
|
51
|
+
else
|
|
52
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
53
|
+
try {
|
|
54
|
+
for (;;) {
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
const chunk = await reader.read().catch((cause) => {
|
|
57
|
+
signal?.throwIfAborted();
|
|
58
|
+
throw new RuntimeError({
|
|
59
|
+
code: "tail_connection_lost",
|
|
60
|
+
status: 503,
|
|
61
|
+
message: "The log stream disconnected.",
|
|
62
|
+
hint: "The SDK resumes from the last delivered cursor.",
|
|
63
|
+
cause,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
signal?.throwIfAborted();
|
|
67
|
+
if (chunk.done)
|
|
68
|
+
break;
|
|
69
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
70
|
+
for (;;) {
|
|
71
|
+
const match = /\r\n|\r|\n/.exec(buffer);
|
|
72
|
+
if (!match || (match[0] === "\r" && match.index === buffer.length - 1))
|
|
73
|
+
break;
|
|
74
|
+
const line = buffer.slice(0, match.index);
|
|
75
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
76
|
+
size += line.length + 1;
|
|
77
|
+
if (size > 1_048_576)
|
|
78
|
+
throw protocol("A log event exceeds 1 MiB of text.");
|
|
79
|
+
if (!line) {
|
|
80
|
+
if (data.length)
|
|
81
|
+
yield { id, event, data: data.join("\n") };
|
|
82
|
+
id = "";
|
|
83
|
+
event = "";
|
|
84
|
+
data = [];
|
|
85
|
+
size = 0;
|
|
86
|
+
}
|
|
87
|
+
else if (!line.startsWith(":")) {
|
|
88
|
+
const colon = line.indexOf(":");
|
|
89
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
90
|
+
const value = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
91
|
+
if (field === "id" && !value.includes("\0"))
|
|
92
|
+
id = value;
|
|
93
|
+
if (field === "event")
|
|
94
|
+
event = value;
|
|
95
|
+
if (field === "data")
|
|
96
|
+
data.push(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (buffer.length + size > 1_048_576)
|
|
100
|
+
throw protocol("A log event exceeds 1 MiB of text.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
signal?.removeEventListener("abort", cancel);
|
|
105
|
+
await reader.cancel().catch(() => undefined);
|
|
106
|
+
reader.releaseLock();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function pause(ms, signal) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const done = () => {
|
|
112
|
+
signal?.removeEventListener("abort", abort);
|
|
113
|
+
resolve();
|
|
114
|
+
};
|
|
115
|
+
const timer = setTimeout(done, ms);
|
|
116
|
+
const abort = () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
signal?.removeEventListener("abort", abort);
|
|
119
|
+
reject(signal?.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError"));
|
|
120
|
+
};
|
|
121
|
+
if (signal?.aborted)
|
|
122
|
+
abort();
|
|
123
|
+
else
|
|
124
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Hidden until Logs launches. */
|
|
128
|
+
export function logs(t) {
|
|
129
|
+
const search = (body, options) => t.json({ method: "POST", path: "/v1/logs:search", body, ...options });
|
|
130
|
+
return {
|
|
131
|
+
search,
|
|
132
|
+
async *pages(input, options) {
|
|
133
|
+
let cursor = input.cursor;
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
if (cursor)
|
|
136
|
+
seen.add(cursor);
|
|
137
|
+
do {
|
|
138
|
+
const page = await search({ ...input, ...(cursor ? { cursor } : {}) }, options);
|
|
139
|
+
yield page;
|
|
140
|
+
cursor = page.next ?? undefined;
|
|
141
|
+
if (cursor && seen.has(cursor))
|
|
142
|
+
throw protocol("Search repeated a paging cursor.");
|
|
143
|
+
if (cursor)
|
|
144
|
+
seen.add(cursor);
|
|
145
|
+
} while (cursor);
|
|
146
|
+
},
|
|
147
|
+
/** The record's cursor, not timestamp or text, identifies a line. The
|
|
148
|
+
* server replays strictly after it. An echoed boundary is ignored; two
|
|
149
|
+
* identical lines at different record positions are both delivered. */
|
|
150
|
+
async *tail(query, options = {}) {
|
|
151
|
+
const { cursor: initial, start, reconnectMs = 250, ...request } = options;
|
|
152
|
+
let cursor = initial, failures = 0;
|
|
153
|
+
if (!Number.isFinite(reconnectMs) || reconnectMs < 0)
|
|
154
|
+
throw protocol("Reconnect delay must be a finite, nonnegative number.");
|
|
155
|
+
for (;;) {
|
|
156
|
+
request.signal?.throwIfAborted();
|
|
157
|
+
try {
|
|
158
|
+
const stream = await t.fileStream({
|
|
159
|
+
method: "GET",
|
|
160
|
+
path: "/v1/logs/tail",
|
|
161
|
+
query: { query, cursor, start },
|
|
162
|
+
accept: "text/event-stream",
|
|
163
|
+
...request,
|
|
164
|
+
timeoutMs: request.timeoutMs ?? 0,
|
|
165
|
+
onResponse(response) {
|
|
166
|
+
if (!response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream"))
|
|
167
|
+
throw protocol("Log tail did not return text/event-stream.");
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
for await (const event of events(stream, request.signal)) {
|
|
171
|
+
let value;
|
|
172
|
+
try {
|
|
173
|
+
value = JSON.parse(event.data);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw protocol("Log tail returned invalid JSON.");
|
|
177
|
+
}
|
|
178
|
+
if (event.event === "error") {
|
|
179
|
+
// A reader failure is retryable, but its resume id has not been
|
|
180
|
+
// delivered as a line. Keep the last acknowledged line cursor.
|
|
181
|
+
throw new RuntimeError({
|
|
182
|
+
code: "tail_interrupted",
|
|
183
|
+
status: 503,
|
|
184
|
+
message: "The log reader stopped.",
|
|
185
|
+
hint: "The SDK resumes from the last delivered cursor.",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (!event.id)
|
|
189
|
+
throw protocol("Log tail event has no record cursor.");
|
|
190
|
+
if (event.id === cursor)
|
|
191
|
+
continue;
|
|
192
|
+
if (event.event === "start") {
|
|
193
|
+
// Where the server's tail begins: a reconnect before the first
|
|
194
|
+
// line resumes here, so nothing written meanwhile is missed.
|
|
195
|
+
cursor = event.id;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (event.event === "gap") {
|
|
199
|
+
const gap = value;
|
|
200
|
+
const from = gap?.from ?? cursor;
|
|
201
|
+
const to = gap?.to ?? gap?.cursor;
|
|
202
|
+
if (typeof from !== "string" || typeof to !== "string" || to !== event.id)
|
|
203
|
+
throw protocol("Log tail gap has invalid cursors.");
|
|
204
|
+
cursor = event.id;
|
|
205
|
+
yield { gap: true, from, to };
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const line = value;
|
|
209
|
+
if (!line ||
|
|
210
|
+
typeof line.timeUnixNano !== "string" ||
|
|
211
|
+
typeof line.time !== "string" ||
|
|
212
|
+
!line.labels ||
|
|
213
|
+
!("body" in line))
|
|
214
|
+
throw protocol("Log tail returned an invalid line.");
|
|
215
|
+
cursor = event.id;
|
|
216
|
+
yield { ...line, cursor };
|
|
217
|
+
}
|
|
218
|
+
failures = 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
request.signal?.throwIfAborted();
|
|
223
|
+
if (error instanceof RuntimeError && !error.retryable)
|
|
224
|
+
throw error;
|
|
225
|
+
if (!(error instanceof RuntimeError) && !(error instanceof TypeError))
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
await pause(Math.min(10_000, Math.max(0, reconnectMs) * 2 ** Math.min(failures++, 6)), request.signal);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
query: (query, options) => t.json({ method: "GET", path: "/v1/logs/query", query, ...options }),
|
|
232
|
+
queryRange: (query, options) => t.json({ method: "GET", path: "/v1/logs/query_range", query, ...options }),
|
|
233
|
+
labels: (query = {}, options) => t.json({ method: "GET", path: "/v1/logs/labels", query, ...options }),
|
|
234
|
+
labelValues: (name, query = {}, options) => t.json({
|
|
235
|
+
method: "GET",
|
|
236
|
+
path: `/v1/logs/labels/${enc(name)}/values`,
|
|
237
|
+
query,
|
|
238
|
+
...options,
|
|
239
|
+
}),
|
|
240
|
+
views: resources(t, "/v1/logs/views"),
|
|
241
|
+
ingest: (body, options) => ingest(t, "/v1/logs", body, options),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export function traces(t) {
|
|
245
|
+
return {
|
|
246
|
+
get: (id, options) => t.json({ method: "GET", path: `/v1/traces/${enc(id)}`, ...options }),
|
|
247
|
+
search: (q, options) => t.json({
|
|
248
|
+
method: "GET",
|
|
249
|
+
path: "/v1/traces:search",
|
|
250
|
+
query: { q },
|
|
251
|
+
...options,
|
|
252
|
+
}),
|
|
253
|
+
tags: (options) => t.json({ method: "GET", path: "/v1/traces/tags", ...options }),
|
|
254
|
+
views: resources(t, "/v1/traces/views"),
|
|
255
|
+
ingest: (body, options) => ingest(t, "/v1/traces", body, options),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export function alerts(t) {
|
|
259
|
+
return {
|
|
260
|
+
...resources(t, "/v1/alerts"),
|
|
261
|
+
test: (id, options) => t.json({
|
|
262
|
+
method: "POST",
|
|
263
|
+
path: `/v1/alerts/${enc(id)}/test`,
|
|
264
|
+
body: {},
|
|
265
|
+
...options,
|
|
266
|
+
}),
|
|
267
|
+
silence: (id, input, options) => t.json({
|
|
268
|
+
method: "POST",
|
|
269
|
+
path: `/v1/alerts/${enc(id)}/silence`,
|
|
270
|
+
body: input,
|
|
271
|
+
...options,
|
|
272
|
+
}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|