@prized/sdk 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +666 -2
  3. package/dist/cjs/commands.d.ts +199 -0
  4. package/dist/cjs/commands.js +597 -0
  5. package/dist/cjs/errors.d.ts +113 -0
  6. package/dist/cjs/errors.js +306 -0
  7. package/dist/cjs/files.d.ts +101 -0
  8. package/dist/cjs/files.js +387 -0
  9. package/dist/cjs/http.d.ts +226 -0
  10. package/dist/cjs/http.js +618 -0
  11. package/dist/cjs/index.d.ts +12 -0
  12. package/dist/cjs/index.js +23 -0
  13. package/dist/cjs/outcome.d.ts +76 -0
  14. package/dist/cjs/outcome.js +170 -0
  15. package/dist/cjs/output.d.ts +70 -0
  16. package/dist/cjs/output.js +383 -0
  17. package/dist/cjs/package.json +1 -0
  18. package/dist/cjs/sandbox.d.ts +218 -0
  19. package/dist/cjs/sandbox.js +836 -0
  20. package/dist/cjs/shell.d.ts +152 -0
  21. package/dist/cjs/shell.js +393 -0
  22. package/dist/cjs/toolbox.d.ts +48 -0
  23. package/dist/cjs/toolbox.js +88 -0
  24. package/dist/cjs/types.d.ts +111 -0
  25. package/dist/cjs/types.js +102 -0
  26. package/dist/cjs/version.d.ts +2 -0
  27. package/dist/cjs/version.js +5 -0
  28. package/dist/esm/commands.d.ts +199 -0
  29. package/dist/esm/commands.js +590 -0
  30. package/dist/esm/errors.d.ts +113 -0
  31. package/dist/esm/errors.js +297 -0
  32. package/dist/esm/files.d.ts +101 -0
  33. package/dist/esm/files.js +381 -0
  34. package/dist/esm/http.d.ts +226 -0
  35. package/dist/esm/http.js +601 -0
  36. package/dist/esm/index.d.ts +12 -0
  37. package/dist/esm/index.js +7 -0
  38. package/dist/esm/outcome.d.ts +76 -0
  39. package/dist/esm/outcome.js +155 -0
  40. package/dist/esm/output.d.ts +70 -0
  41. package/dist/esm/output.js +374 -0
  42. package/dist/esm/sandbox.d.ts +218 -0
  43. package/dist/esm/sandbox.js +832 -0
  44. package/dist/esm/shell.d.ts +152 -0
  45. package/dist/esm/shell.js +379 -0
  46. package/dist/esm/toolbox.d.ts +48 -0
  47. package/dist/esm/toolbox.js +83 -0
  48. package/dist/esm/types.d.ts +111 -0
  49. package/dist/esm/types.js +95 -0
  50. package/dist/esm/version.d.ts +2 -0
  51. package/dist/esm/version.js +2 -0
  52. package/package.json +53 -4
@@ -0,0 +1,76 @@
1
+ import { PrizedError } from './errors.js';
2
+ /**
3
+ * What one request does in the sandbox, in the words its errors use: `maybe`
4
+ * when no clear answer came back (a lost connection, a timeout, a 5xx other
5
+ * than the ones that prove it never arrived), `did` when a 2xx came back that
6
+ * could not be read (the sandbox acted on it), and `check`, how to find out
7
+ * which.
8
+ */
9
+ export interface Effect {
10
+ readonly maybe: string;
11
+ readonly did: string;
12
+ readonly check: string;
13
+ /** What the sandbox does once the connection closes, said when the caller aborts after it may have been sent (withAbort). */
14
+ readonly onClose?: string;
15
+ }
16
+ export declare function commandEffect(): Effect;
17
+ export declare function writeEffect(path: string): Effect;
18
+ export declare function removeEffect(path: string): Effect;
19
+ /** For CommandHandle.kill (`signal` without its SIG prefix). */
20
+ export declare function signalEffect(runId: string, signal: string): Effect;
21
+ /** For a background start (or a streamed or long exec, which runs as one): said before what the SDK did about it (startAbandoned). A 2xx proves only that the start ran. */
22
+ export declare function startEffect(): Effect;
23
+ /** For an SDK helper sent in `exec` mode that names no effect of its own. */
24
+ export declare function helperEffect(): Effect;
25
+ /**
26
+ * How a request that is not sent again ended: `never` reached the sandbox,
27
+ * `maybe` acted (no clear answer), `did` act (a 2xx that could not be read),
28
+ * or null for a refusal whose own words say what happened.
29
+ */
30
+ export type Outcome = 'never' | 'maybe' | 'did' | null;
31
+ /** Whether withOutcome said the sandbox may have acted on the request. */
32
+ export declare function mayHaveActed(error: unknown): error is PrizedError;
33
+ /** `error` with another message (and, given, another status, code, sandbox, exit code or cause), every other field kept. */
34
+ export declare function restate(error: PrizedError, message: string, init?: {
35
+ status?: number;
36
+ code?: string;
37
+ sandboxId?: string;
38
+ exitCode?: number;
39
+ cause?: unknown;
40
+ }): PrizedError;
41
+ /** `error` (same code, status and body), its message saying what it means for `effect`. */
42
+ export declare function withOutcome(error: PrizedError, effect: Effect, outcome: Outcome): PrizedError;
43
+ /**
44
+ * `error` (code `aborted`) for a request with an `effect`: `never` when
45
+ * nothing that may have reached the sandbox was sent (the signal was
46
+ * aborted before an attempt, or while the SDK waited to send one again
47
+ * after attempts that never arrived), `did` when a 2xx status came before
48
+ * the abort cut its body, else `maybe`, with what the sandbox does once a
49
+ * connection closes. TypeScript only: the Python SDK takes no signal.
50
+ */
51
+ export declare function withAbort(error: PrizedError, effect: Effect, outcome: 'never' | 'maybe' | 'did'): PrizedError;
52
+ /**
53
+ * A background start that may have started (`error`, from withOutcome or
54
+ * withAbort) and that the SDK then stopped by its run id (`stopped`: the
55
+ * abandon script answered exit 0, so the run can no longer start, was
56
+ * killed with its group, or had already ended), or could not confirm it did
57
+ * (no answer, or exit 3: no pid came, so the command may yet run). The
58
+ * Python SDK's start_abandoned.
59
+ */
60
+ export declare function startAbandoned(error: PrizedError, runId: string, stopped: boolean): PrizedError;
61
+ /**
62
+ * A streamed or long exec the caller aborted after its run started
63
+ * (`error`, code `aborted`, from `sandboxId`), saying whether the SDK then
64
+ * stopped the run by its id (`stopped`: its KILL was sent, or the run had
65
+ * already ended), or could not confirm it did.
66
+ */
67
+ export declare function runAborted(error: PrizedError, runId: string, stopped: boolean, sandboxId: string): PrizedError;
68
+ /**
69
+ * A streamed or long exec whose poll failed after its run started (`error`:
70
+ * a lost connection, a 5xx, an answer that could not be read, a refused
71
+ * key), saying whether the SDK then stopped the run by its id (`stopped`),
72
+ * or could not confirm it did: with the network down, the command may still
73
+ * be running. Same code, status and body (and `sandboxId` when it names
74
+ * none); `error` itself is the cause. The Python SDK's poll_failed.
75
+ */
76
+ export declare function pollFailed(error: PrizedError, runId: string, stopped: boolean, sandboxId: string): PrizedError;
@@ -0,0 +1,155 @@
1
+ // What an error says about a command that may have run. A command, a file
2
+ // write's rename, a remove or a signal (`exec` retry mode) is never sent again
3
+ // once it may have reached the sandbox, so its error has to say what the
4
+ // caller cannot see: whether it happened. A write's staged upload (`staged`)
5
+ // may have been sent again, and its error says whether a copy may have
6
+ // reached the sandbox. The Python SDK words these the same (_core.py).
7
+ import { defaultMessage, PrizedError } from './errors.js';
8
+ const NOT_SENT_AGAIN = 'It was not sent again';
9
+ export function commandEffect() {
10
+ return {
11
+ maybe: 'the command may or may not have run',
12
+ did: 'The command ran',
13
+ check: `${NOT_SENT_AGAIN}: check for its effects (the files it writes, the processes it starts) before you run it again.`,
14
+ // The sandbox runs a command under its request, in a process group of
15
+ // its own that it KILLs when the request ends, and the edge forwards the
16
+ // request under the caller's connection: a closed connection kills it.
17
+ onClose: 'The sandbox stops a command (and its process group) once its connection closes, so it may have run in part.',
18
+ };
19
+ }
20
+ export function writeEffect(path) {
21
+ return {
22
+ maybe: `${path} may or may not have been written`,
23
+ did: `${path} was written`,
24
+ check: `${NOT_SENT_AGAIN}: read it back with files.read() to see which.`,
25
+ };
26
+ }
27
+ export function removeEffect(path) {
28
+ return {
29
+ maybe: `${path} may or may not have been removed`,
30
+ did: `The remove of ${path} ran`,
31
+ check: `${NOT_SENT_AGAIN}: files.exists() tells you which.`,
32
+ };
33
+ }
34
+ /** For CommandHandle.kill (`signal` without its SIG prefix). */
35
+ export function signalEffect(runId, signal) {
36
+ return {
37
+ maybe: `SIG${signal} may or may not have reached ${runId}`,
38
+ did: `The kill of ${runId} ran`,
39
+ check: `${NOT_SENT_AGAIN}: status() tells you whether the command is still running.`,
40
+ };
41
+ }
42
+ /** For a background start (or a streamed or long exec, which runs as one): said before what the SDK did about it (startAbandoned). A 2xx proves only that the start ran. */
43
+ export function startEffect() {
44
+ return { maybe: 'the command may or may not have started', did: 'The sandbox ran the start', check: '' };
45
+ }
46
+ /** For an SDK helper sent in `exec` mode that names no effect of its own. */
47
+ export function helperEffect() {
48
+ return { maybe: 'it may or may not have taken effect', did: 'It ran', check: `${NOT_SENT_AGAIN}.` };
49
+ }
50
+ const acted = new WeakSet();
51
+ /** Whether withOutcome said the sandbox may have acted on the request. */
52
+ export function mayHaveActed(error) {
53
+ return error instanceof PrizedError && acted.has(error);
54
+ }
55
+ /** `error` with another message (and, given, another status, code, sandbox, exit code or cause), every other field kept. */
56
+ export function restate(error, message, init = {}) {
57
+ return new PrizedError(init.code ?? error.code, message, {
58
+ status: init.status ?? error.status,
59
+ reason: error.reason,
60
+ sandboxId: error.sandboxId ?? init.sandboxId ?? null,
61
+ body: error.body,
62
+ retryAfter: error.retryAfter,
63
+ exitCode: init.exitCode ?? error.exitCode,
64
+ cause: 'cause' in init ? init.cause : error.cause,
65
+ });
66
+ }
67
+ /** `error` restated with `message` (and, given, another sandbox or cause), as one after which the sandbox may have acted. */
68
+ function acting(error, message, init = {}) {
69
+ const restated = restate(error, message, init);
70
+ acted.add(restated);
71
+ return restated;
72
+ }
73
+ function trimmed(text) {
74
+ return text.replace(/[.\s]+$/, '');
75
+ }
76
+ /** The failure in a few words, for inside a sentence. */
77
+ function detail(error) {
78
+ const message = trimmed(error.message);
79
+ if (error.status === null || error.code === 'http_error' || error.code === 'bad_response')
80
+ return message;
81
+ const head = `HTTP ${error.status} ${error.code}`;
82
+ return message === trimmed(defaultMessage(error.code)) ? head : `${head}: ${message}`;
83
+ }
84
+ /** What follows "so" when no clear answer came: what may have happened, then (`closed`: the caller aborted) what a closed connection does, then how to check. */
85
+ function maybeTail(effect, closed) {
86
+ let tail = `${effect.maybe}.`;
87
+ if (closed && effect.onClose)
88
+ tail += ` ${effect.onClose}`;
89
+ if (effect.check)
90
+ tail += ` ${effect.check}`;
91
+ return tail;
92
+ }
93
+ /** `error` (same code, status and body), its message saying what it means for `effect`. */
94
+ export function withOutcome(error, effect, outcome) {
95
+ if (outcome === null)
96
+ return error;
97
+ if (outcome === 'never')
98
+ return restate(error, `${trimmed(error.message)}. It never reached the sandbox, so it had no effect.`);
99
+ if (outcome === 'did')
100
+ return acting(error, `${effect.did}, but the answer could not be read (${detail(error)}).`);
101
+ return acting(error, `No clear answer came back (${detail(error)}), so ${maybeTail(effect, false)}`);
102
+ }
103
+ /**
104
+ * `error` (code `aborted`) for a request with an `effect`: `never` when
105
+ * nothing that may have reached the sandbox was sent (the signal was
106
+ * aborted before an attempt, or while the SDK waited to send one again
107
+ * after attempts that never arrived), `did` when a 2xx status came before
108
+ * the abort cut its body, else `maybe`, with what the sandbox does once a
109
+ * connection closes. TypeScript only: the Python SDK takes no signal.
110
+ */
111
+ export function withAbort(error, effect, outcome) {
112
+ if (outcome !== 'maybe')
113
+ return withOutcome(error, effect, outcome);
114
+ return acting(error, `Aborted before a clear answer came, so ${maybeTail(effect, true)}`);
115
+ }
116
+ /** What the SDK did about a run it stopped by its id (`stopped`), or could not confirm it did. */
117
+ function stopTail(runId, stopped) {
118
+ return stopped
119
+ ? ` The SDK stopped ${runId}, or it had already ended.`
120
+ : ` The SDK could not confirm it stopped: it may still be running as ${runId} (exec('ps -ef | grep ${runId}') finds it).`;
121
+ }
122
+ /**
123
+ * A background start that may have started (`error`, from withOutcome or
124
+ * withAbort) and that the SDK then stopped by its run id (`stopped`: the
125
+ * abandon script answered exit 0, so the run can no longer start, was
126
+ * killed with its group, or had already ended), or could not confirm it did
127
+ * (no answer, or exit 3: no pid came, so the command may yet run). The
128
+ * Python SDK's start_abandoned.
129
+ */
130
+ export function startAbandoned(error, runId, stopped) {
131
+ const said = error.message.trimEnd();
132
+ return acting(error, (/[.!?]$/.test(said) ? said : `${said}.`) + stopTail(runId, stopped));
133
+ }
134
+ /**
135
+ * A streamed or long exec the caller aborted after its run started
136
+ * (`error`, code `aborted`, from `sandboxId`), saying whether the SDK then
137
+ * stopped the run by its id (`stopped`: its KILL was sent, or the run had
138
+ * already ended), or could not confirm it did.
139
+ */
140
+ export function runAborted(error, runId, stopped, sandboxId) {
141
+ return acting(error, `${trimmed(error.message)}.${stopTail(runId, stopped)}`, { sandboxId });
142
+ }
143
+ /**
144
+ * A streamed or long exec whose poll failed after its run started (`error`:
145
+ * a lost connection, a 5xx, an answer that could not be read, a refused
146
+ * key), saying whether the SDK then stopped the run by its id (`stopped`),
147
+ * or could not confirm it did: with the network down, the command may still
148
+ * be running. Same code, status and body (and `sandboxId` when it names
149
+ * none); `error` itself is the cause. The Python SDK's poll_failed.
150
+ */
151
+ export function pollFailed(error, runId, stopped, sandboxId) {
152
+ // One sentence, whatever lines the poll's own error carries (a script's stderr).
153
+ const said = detail(error).replace(/\s+/g, ' ');
154
+ return acting(error, `Polling the command failed (${said}).${stopTail(runId, stopped)}`, { sandboxId, cause: error });
155
+ }
@@ -0,0 +1,70 @@
1
+ import { type Toolbox } from './toolbox.js';
2
+ import type { ExecResult, TruncatedReason } from './types.js';
3
+ export declare const OUTPUT_DIR = "/home/prized/.prized/exec";
4
+ export declare const OUTPUT_ID_RE: RegExp;
5
+ /** Each stream of a fullOutput command keeps at most this many bytes: one download. */
6
+ export declare const FULL_OUTPUT_BYTES: number;
7
+ /**
8
+ * The variables that carry the script and the caller's command, so neither
9
+ * is in the argv of any process but the command's own shell: `pkill -f`
10
+ * finds what it finds in a plain exec. The script unsets both first.
11
+ */
12
+ export declare const SCRIPT_VAR = "PRIZED_EXEC_SCRIPT";
13
+ export declare const COMMAND_VAR = "PRIZED_EXEC_COMMAND";
14
+ /** What a fullOutput exec sends as its command: the script, from SCRIPT_VAR. */
15
+ export declare const RUN_SCRIPT = "eval \"${PRIZED_EXEC_SCRIPT:?}\"";
16
+ /**
17
+ * The script a fullOutput exec runs (RUN_SCRIPT, from SCRIPT_VAR). The
18
+ * caller's command, from COMMAND_VAR, runs as a plain exec runs it (`bash
19
+ * -c`, or `sh -c` where there is no bash, stdin from /dev/null), with the
20
+ * request's cwd and env.
21
+ *
22
+ * Nothing is assigned before it runs but the positional parameters, so every
23
+ * variable the caller's env sets reaches it unchanged, PATH included; and
24
+ * every step holds under an errexit the env may switch on (SHELLOPTS). As in
25
+ * a plain exec, the relays stop 5 s after the command ends if a process it
26
+ * left running still holds its output, keeping what came through. A command
27
+ * a signal ends exits 128+n, as a background command does. A reader that
28
+ * could not write its file (failedLines) sends the output to the files API
29
+ * whatever its size, with the marker saying which. Without /bin/sh, setsid,
30
+ * cat or a directory to write to, the command runs as a plain exec.
31
+ */
32
+ export declare function captureScript(id: string): string;
33
+ /**
34
+ * What a fullOutput command left when the script could not print it (its
35
+ * time limit, or a signal, ended the script): the marker line, or `gone`,
36
+ * with the directory removed, when there is nothing to read (it was never
37
+ * made, or its output was already printed back). A failed write is read from
38
+ * the reader's flag, before the size, which is final once the flag is there
39
+ * (the script's wait for the reader's exit status went with the script); a
40
+ * reader killed, or one that could not make its flag, is not seen here.
41
+ */
42
+ export declare function collectScript(id: string): string;
43
+ /**
44
+ * Remove a fullOutput command's directory (keyed by the SDK's own id, safe to
45
+ * send again), and any an earlier one left over an hour ago, when it got no
46
+ * answer to remove its own.
47
+ */
48
+ export declare function removeOutputScript(id: string): string;
49
+ /** What the marker line says: each stream's byte count (one cut at FULL_OUTPUT_BYTES reads more), and whether its reader failed to write it. */
50
+ export interface Marker {
51
+ out: number;
52
+ err: number;
53
+ outFailed: boolean;
54
+ errFailed: boolean;
55
+ }
56
+ /** The marker (one without failure flags, as an earlier SDK's collect printed, has none), `gone`, or null for no marker. */
57
+ export declare function parseMarker(stdout: string, id: string): Marker | 'gone' | null;
58
+ /** Why the read-back output is cut, if it is: a failed write first, since it is the one the caller cannot foresee. */
59
+ export declare function markerTruncation(marker: Marker): TruncatedReason | null;
60
+ /**
61
+ * A blocking exec that keeps the whole output (up to FULL_OUTPUT_BYTES of
62
+ * each stream). The result is a plain exec's, timeouts included: the caller
63
+ * (execCommand) turns a timed-out one into CommandTimeoutError.
64
+ */
65
+ export declare function execFullOutput(toolbox: Toolbox, id: string, req: {
66
+ command: string;
67
+ cwd?: string | undefined;
68
+ env?: Record<string, string> | undefined;
69
+ timeoutMs: number;
70
+ }, signal: AbortSignal | undefined): Promise<ExecResult>;