endia 0.0.0-stage → 0.0.2

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 (101) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +34 -2
  3. package/dist/archives/archive-file.d.ts +36 -0
  4. package/dist/archives/archive-file.js +71 -0
  5. package/dist/archives/build-context.d.ts +63 -0
  6. package/dist/archives/build-context.js +132 -0
  7. package/dist/archives/docker-ignore.d.ts +57 -0
  8. package/dist/archives/docker-ignore.js +250 -0
  9. package/dist/archives/fleet-archive.d.ts +33 -0
  10. package/dist/archives/fleet-archive.js +134 -0
  11. package/dist/compose-model.d.ts +32 -0
  12. package/dist/compose-model.js +39 -0
  13. package/dist/contract/archives.d.ts +14 -0
  14. package/dist/contract/archives.js +1 -0
  15. package/dist/contract/bases.d.ts +48 -0
  16. package/dist/contract/bases.js +1 -0
  17. package/dist/contract/clients.d.ts +29 -0
  18. package/dist/contract/clients.js +1 -0
  19. package/dist/contract/common.d.ts +30 -0
  20. package/dist/contract/common.js +1 -0
  21. package/dist/contract/compose.d.ts +27 -0
  22. package/dist/contract/compose.js +1 -0
  23. package/dist/contract/desktop.d.ts +269 -0
  24. package/dist/contract/desktop.js +1 -0
  25. package/dist/contract/execs.d.ts +61 -0
  26. package/dist/contract/execs.js +1 -0
  27. package/dist/contract/fleets.d.ts +136 -0
  28. package/dist/contract/fleets.js +1 -0
  29. package/dist/contract/images.d.ts +130 -0
  30. package/dist/contract/images.js +1 -0
  31. package/dist/contract/machines.d.ts +199 -0
  32. package/dist/contract/machines.js +1 -0
  33. package/dist/contract/operations.d.ts +28 -0
  34. package/dist/contract/operations.js +1 -0
  35. package/dist/contract/processes.d.ts +75 -0
  36. package/dist/contract/processes.js +1 -0
  37. package/dist/contract/purposes.d.ts +62 -0
  38. package/dist/contract/purposes.js +1 -0
  39. package/dist/contract/space.d.ts +114 -0
  40. package/dist/contract/space.js +1 -0
  41. package/dist/contract/stats.d.ts +58 -0
  42. package/dist/contract/stats.js +1 -0
  43. package/dist/contract/status.d.ts +151 -0
  44. package/dist/contract/status.js +1 -0
  45. package/dist/contract/volumes.d.ts +22 -0
  46. package/dist/contract/volumes.js +1 -0
  47. package/dist/eastindia-client.d.ts +118 -0
  48. package/dist/eastindia-client.js +142 -0
  49. package/dist/environment.d.ts +52 -0
  50. package/dist/environment.js +133 -0
  51. package/dist/errors.d.ts +81 -0
  52. package/dist/errors.js +108 -0
  53. package/dist/exec/exec-frame.d.ts +38 -0
  54. package/dist/exec/exec-frame.js +50 -0
  55. package/dist/exec/exec-session.d.ts +64 -0
  56. package/dist/exec/exec-session.js +117 -0
  57. package/dist/groups/archives.d.ts +78 -0
  58. package/dist/groups/archives.js +117 -0
  59. package/dist/groups/bases.d.ts +28 -0
  60. package/dist/groups/bases.js +39 -0
  61. package/dist/groups/certificates.d.ts +32 -0
  62. package/dist/groups/certificates.js +43 -0
  63. package/dist/groups/desktop.d.ts +143 -0
  64. package/dist/groups/desktop.js +182 -0
  65. package/dist/groups/execs.d.ts +110 -0
  66. package/dist/groups/execs.js +119 -0
  67. package/dist/groups/fleets.d.ts +206 -0
  68. package/dist/groups/fleets.js +255 -0
  69. package/dist/groups/images.d.ts +130 -0
  70. package/dist/groups/images.js +109 -0
  71. package/dist/groups/machines.d.ts +124 -0
  72. package/dist/groups/machines.js +127 -0
  73. package/dist/groups/operations.d.ts +127 -0
  74. package/dist/groups/operations.js +174 -0
  75. package/dist/groups/processes.d.ts +77 -0
  76. package/dist/groups/processes.js +75 -0
  77. package/dist/groups/stats.d.ts +29 -0
  78. package/dist/groups/stats.js +33 -0
  79. package/dist/groups/system.d.ts +33 -0
  80. package/dist/groups/system.js +40 -0
  81. package/dist/groups/volumes.d.ts +36 -0
  82. package/dist/groups/volumes.js +40 -0
  83. package/dist/index.d.ts +37 -0
  84. package/dist/index.js +20 -0
  85. package/dist/purposes.d.ts +17 -0
  86. package/dist/purposes.js +21 -0
  87. package/dist/routes.d.ts +71 -0
  88. package/dist/routes.js +73 -0
  89. package/dist/transport/api-connection.d.ts +185 -0
  90. package/dist/transport/api-connection.js +396 -0
  91. package/dist/transport/connection-failures.d.ts +14 -0
  92. package/dist/transport/connection-failures.js +57 -0
  93. package/dist/transport/json-lines.d.ts +10 -0
  94. package/dist/transport/json-lines.js +23 -0
  95. package/dist/transport/query-text.d.ts +49 -0
  96. package/dist/transport/query-text.js +62 -0
  97. package/dist/transport/service-address.d.ts +30 -0
  98. package/dist/transport/service-address.js +37 -0
  99. package/dist/transport/tls-files.d.ts +9 -0
  100. package/dist/transport/tls-files.js +1 -0
  101. package/package.json +44 -5
@@ -0,0 +1,81 @@
1
+ import type { ApiError } from "./contract/common.js";
2
+ import type { MachineSummary } from "./contract/machines.js";
3
+ import type { Operation } from "./contract/operations.js";
4
+ /**
5
+ * The service answered and refused: a request it would not carry out, or an operation it started that ended without succeeding.
6
+ * Without this type a caller would match message text; with it, the caller reads `code`, the API's error code, such as `NOT_FOUND`
7
+ * or `BUILD_FAILED`.
8
+ */
9
+ export declare class EastIndiaApiError extends Error {
10
+ /** The HTTP status of the refusal, such as 404; undefined when an operation ended without succeeding. */
11
+ readonly status: number | undefined;
12
+ /** The service's error, or null when a refusal came without a readable body. */
13
+ readonly error: ApiError | null;
14
+ /** The operation that ended without succeeding; undefined for a refused request. */
15
+ readonly operation: Operation | undefined;
16
+ /**
17
+ * Describes a refusal or an unsuccessful operation.
18
+ *
19
+ * @param status - The HTTP status of a refusal; undefined for an operation.
20
+ * @param error - The service's error, or null when none could be read.
21
+ * @param operation - The operation that ended without succeeding, for an operation.
22
+ */
23
+ constructor(status: number | undefined, error: ApiError | null, operation?: Operation);
24
+ /** The API's error code, such as `NOT_FOUND`, or undefined when the refusal carried no readable error. */
25
+ get code(): string | undefined;
26
+ /** The field at fault, such as `reference` or `services.web.ports[0]`, or undefined when no one field is at fault. */
27
+ get fieldPath(): string | undefined;
28
+ /**
29
+ * Describes an operation that ended without succeeding, from the error the operation records.
30
+ *
31
+ * @param operation - The finished operation, in the state `failed`, `cancelled` or `interrupted`.
32
+ * @returns The error, which carries the operation.
33
+ */
34
+ static fromOperation(operation: Operation): EastIndiaApiError;
35
+ private static describe;
36
+ }
37
+ /**
38
+ * Why a request got no answer from the service.
39
+ *
40
+ * - `unreachable`: nothing answered at the address, or the connection broke before an answer arrived.
41
+ * - `certificateRefused`: the service refused the client certificate, because another authority issued it, it has expired or it
42
+ * was revoked.
43
+ * - `serverNotTrusted`: the server's certificate does not chain to EastIndia's authority in `ca.pem`, or does not name the address.
44
+ * - `pipeRefused`: Windows refused to open the service's named pipe for this account.
45
+ * - `timedOut`: the request had no answer within the client's request time limit.
46
+ */
47
+ export type ConnectionFailure = "unreachable" | "certificateRefused" | "serverNotTrusted" | "pipeRefused" | "timedOut";
48
+ /**
49
+ * No answer came from the service. Without this type a caller could not tell a stopped service from a refused certificate, because
50
+ * Node reports each of them as a socket or TLS error; `failure` says which it was, and `cause` holds Node's own error.
51
+ */
52
+ export declare class EastIndiaConnectionError extends Error {
53
+ /** Why no answer came. */
54
+ readonly failure: ConnectionFailure;
55
+ /** The address as messages name it: the host and port, or the named pipe's path. */
56
+ readonly address: string;
57
+ /**
58
+ * Describes a request that got no answer.
59
+ *
60
+ * @param address - The host and port, or the named pipe's path.
61
+ * @param failure - Why no answer came.
62
+ * @param cause - The error Node reported.
63
+ */
64
+ constructor(address: string, failure: ConnectionFailure, cause: unknown);
65
+ private static describe;
66
+ }
67
+ /**
68
+ * A machine exited or became unhealthy while `FleetsClient.wait` waited for its fleet, as `endia compose up --wait` then exits
69
+ * with 1. Without this type a caller could not tell a failed machine from a refused request; `machine` is the machine as the wait
70
+ * last read it.
71
+ */
72
+ export declare class MachineFailedError extends Error {
73
+ /** The machine as the wait last read it. */
74
+ readonly machine: MachineSummary;
75
+ /**
76
+ * Describes a machine that ended a wait.
77
+ *
78
+ * @param machine - The machine, exited or unhealthy.
79
+ */
80
+ constructor(machine: MachineSummary);
81
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The service answered and refused: a request it would not carry out, or an operation it started that ended without succeeding.
3
+ * Without this type a caller would match message text; with it, the caller reads `code`, the API's error code, such as `NOT_FOUND`
4
+ * or `BUILD_FAILED`.
5
+ */
6
+ export class EastIndiaApiError extends Error {
7
+ /** The HTTP status of the refusal, such as 404; undefined when an operation ended without succeeding. */
8
+ status;
9
+ /** The service's error, or null when a refusal came without a readable body. */
10
+ error;
11
+ /** The operation that ended without succeeding; undefined for a refused request. */
12
+ operation;
13
+ /**
14
+ * Describes a refusal or an unsuccessful operation.
15
+ *
16
+ * @param status - The HTTP status of a refusal; undefined for an operation.
17
+ * @param error - The service's error, or null when none could be read.
18
+ * @param operation - The operation that ended without succeeding, for an operation.
19
+ */
20
+ constructor(status, error, operation) {
21
+ super(EastIndiaApiError.describe(status, error, operation));
22
+ this.name = "EastIndiaApiError";
23
+ this.status = status;
24
+ this.error = error;
25
+ this.operation = operation;
26
+ }
27
+ /** The API's error code, such as `NOT_FOUND`, or undefined when the refusal carried no readable error. */
28
+ get code() {
29
+ return this.error?.code;
30
+ }
31
+ /** The field at fault, such as `reference` or `services.web.ports[0]`, or undefined when no one field is at fault. */
32
+ get fieldPath() {
33
+ return this.error?.fieldPath ?? undefined;
34
+ }
35
+ /**
36
+ * Describes an operation that ended without succeeding, from the error the operation records.
37
+ *
38
+ * @param operation - The finished operation, in the state `failed`, `cancelled` or `interrupted`.
39
+ * @returns The error, which carries the operation.
40
+ */
41
+ static fromOperation(operation) {
42
+ return new EastIndiaApiError(undefined, operation.error ?? null, operation);
43
+ }
44
+ static describe(status, error, operation) {
45
+ const subject = operation === undefined ? `The service answered ${String(status)}` : `Operation ${operation.id} (${operation.kind}) ended ${operation.state}`;
46
+ if (error === null) {
47
+ return `${subject}.`;
48
+ }
49
+ const field = error.fieldPath ? ` [${error.fieldPath}]` : "";
50
+ return `${subject} (${error.code}): ${error.message}${field}`;
51
+ }
52
+ }
53
+ /**
54
+ * No answer came from the service. Without this type a caller could not tell a stopped service from a refused certificate, because
55
+ * Node reports each of them as a socket or TLS error; `failure` says which it was, and `cause` holds Node's own error.
56
+ */
57
+ export class EastIndiaConnectionError extends Error {
58
+ /** Why no answer came. */
59
+ failure;
60
+ /** The address as messages name it: the host and port, or the named pipe's path. */
61
+ address;
62
+ /**
63
+ * Describes a request that got no answer.
64
+ *
65
+ * @param address - The host and port, or the named pipe's path.
66
+ * @param failure - Why no answer came.
67
+ * @param cause - The error Node reported.
68
+ */
69
+ constructor(address, failure, cause) {
70
+ super(EastIndiaConnectionError.describe(address, failure), { cause });
71
+ this.name = "EastIndiaConnectionError";
72
+ this.failure = failure;
73
+ this.address = address;
74
+ }
75
+ static describe(address, failure) {
76
+ switch (failure) {
77
+ case "certificateRefused":
78
+ return `The EastIndia service at ${address} refused the client certificate.`;
79
+ case "serverNotTrusted":
80
+ return `The server at ${address} did not present a certificate from the EastIndia authority in ca.pem.`;
81
+ case "pipeRefused":
82
+ return `Windows refused access to the EastIndia service at ${address}: add your account to the eastindia-users group, then sign out and in again.`;
83
+ case "timedOut":
84
+ return `The EastIndia service at ${address} did not answer within the client's request time limit.`;
85
+ case "unreachable":
86
+ return `Cannot connect to the EastIndia service at ${address}. Is the service running?`;
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * A machine exited or became unhealthy while `FleetsClient.wait` waited for its fleet, as `endia compose up --wait` then exits
92
+ * with 1. Without this type a caller could not tell a failed machine from a refused request; `machine` is the machine as the wait
93
+ * last read it.
94
+ */
95
+ export class MachineFailedError extends Error {
96
+ /** The machine as the wait last read it. */
97
+ machine;
98
+ /**
99
+ * Describes a machine that ended a wait.
100
+ *
101
+ * @param machine - The machine, exited or unhealthy.
102
+ */
103
+ constructor(machine) {
104
+ super(`machine ${machine.name} is ${machine.state === "exited" ? `exited (${String(machine.exitCode)})` : "unhealthy"}`);
105
+ this.name = "MachineFailedError";
106
+ this.machine = machine;
107
+ }
108
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The stream a frame carries bytes for; the value is the frame's first byte.
3
+ */
4
+ export declare const FrameStream: {
5
+ readonly stdin: 0;
6
+ readonly stdout: 1;
7
+ readonly stderr: 2;
8
+ };
9
+ /** One of the three streams a frame can carry. */
10
+ export type FrameStream = (typeof FrameStream)[keyof typeof FrameStream];
11
+ /** One message of an attached exec without a console: which stream the bytes belong to, and the bytes. */
12
+ export interface ExecFrame {
13
+ readonly stream: FrameStream;
14
+ readonly payload: Buffer;
15
+ }
16
+ /**
17
+ * Writes one frame in Docker's multiplexed format, the form in which an attached exec without a console carries its streams over
18
+ * one WebSocket. The frame is an eight-byte header followed by the payload:
19
+ *
20
+ * - byte 0 is the stream: 0 standard input, 1 standard output, 2 standard error;
21
+ * - bytes 1 to 3 are zero;
22
+ * - bytes 4 to 7 are the payload's length as a big-endian unsigned integer.
23
+ *
24
+ * When the client sends a standard-input frame with an empty payload, the service closes the program's standard input.
25
+ *
26
+ * @param stream - The stream the payload belongs to.
27
+ * @param payload - The bytes.
28
+ * @returns The header followed by the payload.
29
+ */
30
+ export declare function encodeFrame(stream: FrameStream, payload: Uint8Array): Buffer;
31
+ /**
32
+ * Reads one whole WebSocket message as one frame, the reverse of `encodeFrame`.
33
+ *
34
+ * @param message - One whole binary message.
35
+ * @returns The frame; undefined when the message is not exactly one frame with a header naming a known stream, bytes 1 to 3 zero,
36
+ * and as many payload bytes as the header gives.
37
+ */
38
+ export declare function decodeFrame(message: Buffer): ExecFrame | undefined;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The stream a frame carries bytes for; the value is the frame's first byte.
3
+ */
4
+ export const FrameStream = {
5
+ stdin: 0,
6
+ stdout: 1,
7
+ stderr: 2,
8
+ };
9
+ /** The length of a frame's header, in bytes. */
10
+ const HeaderLength = 8;
11
+ /** Where the payload's length starts in the header. */
12
+ const LengthOffset = 4;
13
+ /**
14
+ * Writes one frame in Docker's multiplexed format, the form in which an attached exec without a console carries its streams over
15
+ * one WebSocket. The frame is an eight-byte header followed by the payload:
16
+ *
17
+ * - byte 0 is the stream: 0 standard input, 1 standard output, 2 standard error;
18
+ * - bytes 1 to 3 are zero;
19
+ * - bytes 4 to 7 are the payload's length as a big-endian unsigned integer.
20
+ *
21
+ * When the client sends a standard-input frame with an empty payload, the service closes the program's standard input.
22
+ *
23
+ * @param stream - The stream the payload belongs to.
24
+ * @param payload - The bytes.
25
+ * @returns The header followed by the payload.
26
+ */
27
+ export function encodeFrame(stream, payload) {
28
+ const frame = Buffer.alloc(HeaderLength + payload.byteLength);
29
+ frame[0] = stream;
30
+ frame.writeUInt32BE(payload.byteLength, LengthOffset);
31
+ frame.set(payload, HeaderLength);
32
+ return frame;
33
+ }
34
+ /**
35
+ * Reads one whole WebSocket message as one frame, the reverse of `encodeFrame`.
36
+ *
37
+ * @param message - One whole binary message.
38
+ * @returns The frame; undefined when the message is not exactly one frame with a header naming a known stream, bytes 1 to 3 zero,
39
+ * and as many payload bytes as the header gives.
40
+ */
41
+ export function decodeFrame(message) {
42
+ if (message.length < HeaderLength || message[1] !== 0 || message[2] !== 0 || message[3] !== 0) {
43
+ return undefined;
44
+ }
45
+ const stream = message[0];
46
+ if (stream !== FrameStream.stdin && stream !== FrameStream.stdout && stream !== FrameStream.stderr) {
47
+ return undefined;
48
+ }
49
+ return message.readUInt32BE(LengthOffset) === message.length - HeaderLength ? { stream, payload: message.subarray(HeaderLength) } : undefined;
50
+ }
@@ -0,0 +1,64 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import type { ExecDetail } from "../contract/execs.js";
3
+ /** What a session needs from the exec routes once its WebSocket is open: the exec's state at the end, and its console's size. */
4
+ export interface ExecRoutes {
5
+ inspect(id: string, signal?: AbortSignal): Promise<ExecDetail>;
6
+ resize(id: string, rows: number, columns: number, signal?: AbortSignal): Promise<void>;
7
+ }
8
+ /** How an attached exec was created, which decides how its WebSocket messages are read and written. */
9
+ export interface ExecSessionShape {
10
+ /**
11
+ * Whether the program has a console. With a console, each WebSocket message carries raw bytes in both directions; without one,
12
+ * each WebSocket message is one frame.
13
+ */
14
+ readonly tty: boolean;
15
+ /** Whether the program's standard input stays open for what the caller sends. */
16
+ readonly attachStdin: boolean;
17
+ }
18
+ /**
19
+ * A program running in a machine with the caller attached: its standard output and standard error as readable streams, its standard
20
+ * input as a writable stream, and its exit code once it has ended. `ExecsClient.spawn` starts one; `ExecsClient.run` starts one and
21
+ * collects its output.
22
+ *
23
+ * The output streams are fed from the exec's WebSocket. While a stream's reader falls behind, the session stops reading the WebSocket.
24
+ * The caller must therefore read both streams: an unread stream stops the program's output, and `wait` never resolves. When the
25
+ * program has ended and its output has been sent, the service closes the WebSocket, both streams end, and `wait` reads the exec's
26
+ * exit code.
27
+ */
28
+ export declare class ExecSession {
29
+ private readonly socket;
30
+ private readonly routes;
31
+ private readonly closed;
32
+ /** The exec ID: 32 lower-case hexadecimal digits. */
33
+ readonly id: string;
34
+ /** The program's standard output; with a console, all of its output. */
35
+ readonly stdout: Readable;
36
+ /** The program's standard error; with a console it stays empty, because the console carries both. */
37
+ readonly stderr: Readable;
38
+ /** The program's standard input when the exec attaches it, otherwise null. Ending it closes the program's standard input. */
39
+ readonly stdin: Writable | null;
40
+ /**
41
+ * Waits until the program has ended and its output has been sent, then reads the exec.
42
+ *
43
+ * @param signal - Cancels the read of the exec; the program keeps running.
44
+ * @returns The exec, with the program's exit code in full, such as 3010; null when Windows restarted before the program ended.
45
+ * A program that was not found ends with 127, and one that was found but could not start ends with 126 and a `startError`.
46
+ */
47
+ wait(signal?: AbortSignal): Promise<ExecDetail>;
48
+ /**
49
+ * Changes the size of the program's console.
50
+ *
51
+ * @param rows - The number of rows.
52
+ * @param columns - The number of columns.
53
+ * @param signal - Cancels the request.
54
+ * @returns When the service has resized the console.
55
+ */
56
+ resize(rows: number, columns: number, signal?: AbortSignal): Promise<void>;
57
+ /**
58
+ * Leaves the program running and stops relaying its streams: the WebSocket is closed and both output streams end. As in Docker,
59
+ * a program keeps running when its caller disconnects.
60
+ */
61
+ detach(): void;
62
+ private relayOutput;
63
+ private createInput;
64
+ }
@@ -0,0 +1,117 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import { decodeFrame, encodeFrame, FrameStream } from "./exec-frame.js";
3
+ /**
4
+ * A program running in a machine with the caller attached: its standard output and standard error as readable streams, its standard
5
+ * input as a writable stream, and its exit code once it has ended. `ExecsClient.spawn` starts one; `ExecsClient.run` starts one and
6
+ * collects its output.
7
+ *
8
+ * The output streams are fed from the exec's WebSocket. While a stream's reader falls behind, the session stops reading the WebSocket.
9
+ * The caller must therefore read both streams: an unread stream stops the program's output, and `wait` never resolves. When the
10
+ * program has ended and its output has been sent, the service closes the WebSocket, both streams end, and `wait` reads the exec's
11
+ * exit code.
12
+ */
13
+ export class ExecSession {
14
+ socket;
15
+ routes;
16
+ closed;
17
+ /** The exec ID: 32 lower-case hexadecimal digits. */
18
+ id;
19
+ /** The program's standard output; with a console, all of its output. */
20
+ stdout;
21
+ /** The program's standard error; with a console it stays empty, because the console carries both. */
22
+ stderr;
23
+ /** The program's standard input when the exec attaches it, otherwise null. Ending it closes the program's standard input. */
24
+ stdin;
25
+ /** @internal */
26
+ constructor(id, socket, shape, routes) {
27
+ this.id = id;
28
+ this.socket = socket;
29
+ this.routes = routes;
30
+ const resume = () => {
31
+ socket.resume();
32
+ };
33
+ this.stdout = new Readable({ read: resume });
34
+ this.stderr = new Readable({ read: resume });
35
+ this.stdin = shape.attachStdin ? this.createInput(shape.tty) : null;
36
+ this.closed = this.relayOutput(shape.tty);
37
+ }
38
+ /**
39
+ * Waits until the program has ended and its output has been sent, then reads the exec.
40
+ *
41
+ * @param signal - Cancels the read of the exec; the program keeps running.
42
+ * @returns The exec, with the program's exit code in full, such as 3010; null when Windows restarted before the program ended.
43
+ * A program that was not found ends with 127, and one that was found but could not start ends with 126 and a `startError`.
44
+ */
45
+ async wait(signal) {
46
+ await this.closed;
47
+ return this.routes.inspect(this.id, signal);
48
+ }
49
+ /**
50
+ * Changes the size of the program's console.
51
+ *
52
+ * @param rows - The number of rows.
53
+ * @param columns - The number of columns.
54
+ * @param signal - Cancels the request.
55
+ * @returns When the service has resized the console.
56
+ */
57
+ resize(rows, columns, signal) {
58
+ return this.routes.resize(this.id, rows, columns, signal);
59
+ }
60
+ /**
61
+ * Leaves the program running and stops relaying its streams: the WebSocket is closed and both output streams end. As in Docker,
62
+ * a program keeps running when its caller disconnects.
63
+ */
64
+ detach() {
65
+ this.socket.close();
66
+ }
67
+ // Feeds each message to its stream until the service closes the WebSocket. A message that is not a well-formed frame carries no
68
+ // stream to feed, so it is skipped, as the eastindia CLI skips it. The promise rejects when the WebSocket fails before the service
69
+ // closes it.
70
+ relayOutput(tty) {
71
+ return new Promise((resolve, reject) => {
72
+ let failure;
73
+ this.socket.on("message", (data) => {
74
+ const frame = tty ? { stream: FrameStream.stdout, payload: data } : decodeFrame(data);
75
+ if (frame === undefined || frame.stream === FrameStream.stdin) {
76
+ return;
77
+ }
78
+ const target = frame.stream === FrameStream.stderr ? this.stderr : this.stdout;
79
+ if (!target.push(frame.payload)) {
80
+ this.socket.pause();
81
+ }
82
+ });
83
+ this.socket.once("error", (error) => {
84
+ failure = error;
85
+ });
86
+ this.socket.once("close", () => {
87
+ this.stdout.push(null);
88
+ this.stderr.push(null);
89
+ if (failure === undefined) {
90
+ resolve();
91
+ }
92
+ else {
93
+ this.stdout.destroy(failure);
94
+ this.stderr.destroy(failure);
95
+ reject(failure);
96
+ }
97
+ });
98
+ });
99
+ }
100
+ // Without a console each write goes as one standard-input frame, and the end of the stream as an empty one, which closes the
101
+ // program's standard input. With a console the bytes go as they are, and the end of the stream sends nothing.
102
+ createInput(tty) {
103
+ return new Writable({
104
+ write: (chunk, _encoding, done) => {
105
+ this.socket.send(tty ? chunk : encodeFrame(FrameStream.stdin, chunk), { binary: true }, done);
106
+ },
107
+ final: (done) => {
108
+ if (tty) {
109
+ done();
110
+ }
111
+ else {
112
+ this.socket.send(encodeFrame(FrameStream.stdin, Buffer.alloc(0)), { binary: true }, done);
113
+ }
114
+ },
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,78 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { Readable } from "node:stream";
3
+ import type { PathStat } from "../contract/archives.js";
4
+ /** A path in a machine read as a tar archive: its description, and the archive as it arrives. */
5
+ export interface MachineArchive {
6
+ readonly stat: PathStat;
7
+ /** The tar archive; the caller reads it to its end or destroys it. */
8
+ readonly archive: IncomingMessage;
9
+ }
10
+ /**
11
+ * The archive routes of `endia cp`, which copy files into and out of a running machine as tar archives. Paths in a machine are
12
+ * absolute Windows paths, such as `C:\data` or `C:/data`; a relative path is relative to `C:\`.
13
+ *
14
+ * `copyIn` and `copyOut` copy between a local path and a machine folder. They place the copy inside an existing folder under the
15
+ * source's own name, which is one of the placements `docker cp` makes. `stat`, `read`, `write` and `delete` are the single routes,
16
+ * for a caller that applies the other placements or packs its own archives.
17
+ */
18
+ export declare class ArchivesClient {
19
+ private readonly connection;
20
+ /**
21
+ * Copies a local file or folder into a folder in the machine, under its own name. A link is copied as a link.
22
+ *
23
+ * @param machine - The machine name or a unique prefix of its ID.
24
+ * @param source - The local file or folder.
25
+ * @param folder - The machine folder, which must exist.
26
+ * @param signal - Cancels the request.
27
+ * @returns When the service has written every file. When a file to be replaced is in use in the machine, the service stops the
28
+ * copy at that file and refuses it with `FILES_LOCKED`; the files written before it stay written.
29
+ */
30
+ copyIn(machine: string, source: string, folder: string, signal?: AbortSignal): Promise<void>;
31
+ /**
32
+ * Copies a file or folder out of the machine into a local folder, under its own name.
33
+ *
34
+ * @param machine - The machine name or a unique prefix of its ID.
35
+ * @param source - The path in the machine.
36
+ * @param folder - The local folder, created when it does not exist.
37
+ * @param signal - Cancels the request.
38
+ * @returns The source's description. An archive entry whose path would lead outside the local folder is refused.
39
+ */
40
+ copyOut(machine: string, source: string, folder: string, signal?: AbortSignal): Promise<PathStat>;
41
+ /**
42
+ * Describes a path in the machine: `HEAD /v1/machines/{name}/archive`.
43
+ *
44
+ * @param machine - The machine name or a unique prefix of its ID.
45
+ * @param target - The path in the machine.
46
+ * @param signal - Cancels the request.
47
+ * @returns The path's name, size, kind and modification time; null when nothing is at the path.
48
+ */
49
+ stat(machine: string, target: string, signal?: AbortSignal): Promise<PathStat | null>;
50
+ /**
51
+ * Reads a path in the machine as a tar archive: `GET /v1/machines/{name}/archive`.
52
+ *
53
+ * @param machine - The machine name or a unique prefix of its ID.
54
+ * @param source - The path in the machine; a folder path ending in `\.` asks for the folder's contents without the folder.
55
+ * @param signal - Ends the request.
56
+ * @returns The path's description and the archive.
57
+ */
58
+ read(machine: string, source: string, signal?: AbortSignal): Promise<MachineArchive>;
59
+ /**
60
+ * Extracts a tar archive into a folder in the machine: `PUT /v1/machines/{name}/archive`.
61
+ *
62
+ * @param machine - The machine name or a unique prefix of its ID.
63
+ * @param folder - The machine folder, which must exist.
64
+ * @param archive - The archive: its bytes, or a function that opens it afresh, so that a retried request sends the same bytes.
65
+ * @param signal - Cancels the request.
66
+ * @returns When the service has written every file.
67
+ */
68
+ write(machine: string, folder: string, archive: Uint8Array | (() => Readable), signal?: AbortSignal): Promise<void>;
69
+ /**
70
+ * Deletes a file or folder in the machine: `DELETE /v1/machines/{name}/archive`.
71
+ *
72
+ * @param machine - The machine name or a unique prefix of its ID.
73
+ * @param target - The path in the machine.
74
+ * @param signal - Cancels the request.
75
+ * @returns When the service has deleted it.
76
+ */
77
+ delete(machine: string, target: string, signal?: AbortSignal): Promise<void>;
78
+ }
@@ -0,0 +1,117 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Readable } from "node:stream";
4
+ import { pipeline } from "node:stream/promises";
5
+ import { create, extract } from "tar";
6
+ import { EastIndiaApiError } from "../errors.js";
7
+ import { Headers, Routes } from "../routes.js";
8
+ import { readHeaderJson, tarBody } from "../transport/api-connection.js";
9
+ /** The query parameter that carries the path on the archive routes. A refusal for a missing path names it as the field at fault. */
10
+ const PathParameter = "path";
11
+ /**
12
+ * The archive routes of `endia cp`, which copy files into and out of a running machine as tar archives. Paths in a machine are
13
+ * absolute Windows paths, such as `C:\data` or `C:/data`; a relative path is relative to `C:\`.
14
+ *
15
+ * `copyIn` and `copyOut` copy between a local path and a machine folder. They place the copy inside an existing folder under the
16
+ * source's own name, which is one of the placements `docker cp` makes. `stat`, `read`, `write` and `delete` are the single routes,
17
+ * for a caller that applies the other placements or packs its own archives.
18
+ */
19
+ export class ArchivesClient {
20
+ connection;
21
+ /** @internal */
22
+ constructor(connection) {
23
+ this.connection = connection;
24
+ }
25
+ /**
26
+ * Copies a local file or folder into a folder in the machine, under its own name. A link is copied as a link.
27
+ *
28
+ * @param machine - The machine name or a unique prefix of its ID.
29
+ * @param source - The local file or folder.
30
+ * @param folder - The machine folder, which must exist.
31
+ * @param signal - Cancels the request.
32
+ * @returns When the service has written every file. When a file to be replaced is in use in the machine, the service stops the
33
+ * copy at that file and refuses it with `FILES_LOCKED`; the files written before it stay written.
34
+ */
35
+ copyIn(machine, source, folder, signal) {
36
+ const resolved = path.resolve(source);
37
+ const open = () => Readable.from(create({ cwd: path.dirname(resolved), follow: false }, [path.basename(resolved)]));
38
+ return this.write(machine, folder, open, signal);
39
+ }
40
+ /**
41
+ * Copies a file or folder out of the machine into a local folder, under its own name.
42
+ *
43
+ * @param machine - The machine name or a unique prefix of its ID.
44
+ * @param source - The path in the machine.
45
+ * @param folder - The local folder, created when it does not exist.
46
+ * @param signal - Cancels the request.
47
+ * @returns The source's description. An archive entry whose path would lead outside the local folder is refused.
48
+ */
49
+ async copyOut(machine, source, folder, signal) {
50
+ await mkdir(folder, { recursive: true });
51
+ const read = await this.read(machine, source, signal);
52
+ await pipeline(read.archive, extract({ cwd: folder }), signal === undefined ? {} : { signal });
53
+ return read.stat;
54
+ }
55
+ /**
56
+ * Describes a path in the machine: `HEAD /v1/machines/{name}/archive`.
57
+ *
58
+ * @param machine - The machine name or a unique prefix of its ID.
59
+ * @param target - The path in the machine.
60
+ * @param signal - Cancels the request.
61
+ * @returns The path's name, size, kind and modification time; null when nothing is at the path.
62
+ */
63
+ async stat(machine, target, signal) {
64
+ try {
65
+ return statOf(await this.connection.head(Routes.machineArchive(machine, target), signal));
66
+ }
67
+ catch (error) {
68
+ if (error instanceof EastIndiaApiError && error.status === 404 && error.fieldPath === PathParameter) {
69
+ return null;
70
+ }
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * Reads a path in the machine as a tar archive: `GET /v1/machines/{name}/archive`.
76
+ *
77
+ * @param machine - The machine name or a unique prefix of its ID.
78
+ * @param source - The path in the machine; a folder path ending in `\.` asks for the folder's contents without the folder.
79
+ * @param signal - Ends the request.
80
+ * @returns The path's description and the archive.
81
+ */
82
+ async read(machine, source, signal) {
83
+ const archive = await this.connection.openStream({ method: "GET", path: Routes.machineArchive(machine, source), signal });
84
+ return { stat: statOf(archive.headers), archive };
85
+ }
86
+ /**
87
+ * Extracts a tar archive into a folder in the machine: `PUT /v1/machines/{name}/archive`.
88
+ *
89
+ * @param machine - The machine name or a unique prefix of its ID.
90
+ * @param folder - The machine folder, which must exist.
91
+ * @param archive - The archive: its bytes, or a function that opens it afresh, so that a retried request sends the same bytes.
92
+ * @param signal - Cancels the request.
93
+ * @returns When the service has written every file.
94
+ */
95
+ write(machine, folder, archive, signal) {
96
+ const body = tarBody(archive);
97
+ return this.connection.changeWithoutAnswer({ method: "PUT", path: Routes.machineArchive(machine, folder), body, signal });
98
+ }
99
+ /**
100
+ * Deletes a file or folder in the machine: `DELETE /v1/machines/{name}/archive`.
101
+ *
102
+ * @param machine - The machine name or a unique prefix of its ID.
103
+ * @param target - The path in the machine.
104
+ * @param signal - Cancels the request.
105
+ * @returns When the service has deleted it.
106
+ */
107
+ delete(machine, target, signal) {
108
+ return this.connection.changeWithoutAnswer({ method: "DELETE", path: Routes.machineArchive(machine, target), signal });
109
+ }
110
+ }
111
+ function statOf(headers) {
112
+ const stat = readHeaderJson(headers, Headers.pathStat);
113
+ if (stat === undefined) {
114
+ throw new Error(`The service answered without a readable ${Headers.pathStat} header.`);
115
+ }
116
+ return stat;
117
+ }