@alchemy.run/boat 0.0.0 → 2.0.0-beta.86

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 (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +66 -4
  3. package/lib/AiProxy.d.ts +89 -0
  4. package/lib/AiProxy.d.ts.map +1 -0
  5. package/lib/AiProxy.js +125 -0
  6. package/lib/AiProxy.js.map +1 -0
  7. package/lib/ApiKey.d.ts +105 -0
  8. package/lib/ApiKey.d.ts.map +1 -0
  9. package/lib/ApiKey.js +134 -0
  10. package/lib/ApiKey.js.map +1 -0
  11. package/lib/AuthProvider.d.ts +71 -0
  12. package/lib/AuthProvider.d.ts.map +1 -0
  13. package/lib/AuthProvider.js +322 -0
  14. package/lib/AuthProvider.js.map +1 -0
  15. package/lib/BoatBindings.d.ts +52 -0
  16. package/lib/BoatBindings.d.ts.map +1 -0
  17. package/lib/BoatBindings.js +104 -0
  18. package/lib/BoatBindings.js.map +1 -0
  19. package/lib/Bootstrap.d.ts +8 -0
  20. package/lib/Bootstrap.d.ts.map +1 -0
  21. package/lib/Bootstrap.js +28 -0
  22. package/lib/Bootstrap.js.map +1 -0
  23. package/lib/ConnectSandbox.d.ts +56 -0
  24. package/lib/ConnectSandbox.d.ts.map +1 -0
  25. package/lib/ConnectSandbox.js +3 -0
  26. package/lib/ConnectSandbox.js.map +1 -0
  27. package/lib/ConnectSandboxHttp.d.ts +21 -0
  28. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  29. package/lib/ConnectSandboxHttp.js +46 -0
  30. package/lib/ConnectSandboxHttp.js.map +1 -0
  31. package/lib/Contributions.d.ts +7 -0
  32. package/lib/Contributions.d.ts.map +1 -0
  33. package/lib/Contributions.js +6 -0
  34. package/lib/Contributions.js.map +1 -0
  35. package/lib/CreateSandbox.d.ts +134 -0
  36. package/lib/CreateSandbox.d.ts.map +1 -0
  37. package/lib/CreateSandbox.js +3 -0
  38. package/lib/CreateSandbox.js.map +1 -0
  39. package/lib/CreateSandboxHttp.d.ts +21 -0
  40. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  41. package/lib/CreateSandboxHttp.js +93 -0
  42. package/lib/CreateSandboxHttp.js.map +1 -0
  43. package/lib/CredentialProfile.d.ts +157 -0
  44. package/lib/CredentialProfile.d.ts.map +1 -0
  45. package/lib/CredentialProfile.js +245 -0
  46. package/lib/CredentialProfile.js.map +1 -0
  47. package/lib/Credentials.d.ts +15 -0
  48. package/lib/Credentials.d.ts.map +1 -0
  49. package/lib/Credentials.js +23 -0
  50. package/lib/Credentials.js.map +1 -0
  51. package/lib/Environment.d.ts +219 -0
  52. package/lib/Environment.d.ts.map +1 -0
  53. package/lib/Environment.js +273 -0
  54. package/lib/Environment.js.map +1 -0
  55. package/lib/Errors.d.ts +50 -0
  56. package/lib/Errors.d.ts.map +1 -0
  57. package/lib/Errors.js +17 -0
  58. package/lib/Errors.js.map +1 -0
  59. package/lib/Providers.d.ts +33 -0
  60. package/lib/Providers.d.ts.map +1 -0
  61. package/lib/Providers.js +47 -0
  62. package/lib/Providers.js.map +1 -0
  63. package/lib/Sandbox.d.ts +205 -0
  64. package/lib/Sandbox.d.ts.map +1 -0
  65. package/lib/Sandbox.js +368 -0
  66. package/lib/Sandbox.js.map +1 -0
  67. package/lib/SandboxClient.d.ts +159 -0
  68. package/lib/SandboxClient.d.ts.map +1 -0
  69. package/lib/SandboxClient.js +1 -0
  70. package/lib/SandboxClient.js.map +1 -0
  71. package/lib/SandboxEvents.d.ts +111 -0
  72. package/lib/SandboxEvents.d.ts.map +1 -0
  73. package/lib/SandboxEvents.js +60 -0
  74. package/lib/SandboxEvents.js.map +1 -0
  75. package/lib/SandboxEventsLive.d.ts +13 -0
  76. package/lib/SandboxEventsLive.d.ts.map +1 -0
  77. package/lib/SandboxEventsLive.js +63 -0
  78. package/lib/SandboxEventsLive.js.map +1 -0
  79. package/lib/Snapshot.d.ts +123 -0
  80. package/lib/Snapshot.d.ts.map +1 -0
  81. package/lib/Snapshot.js +186 -0
  82. package/lib/Snapshot.js.map +1 -0
  83. package/lib/Webhook.d.ts +56 -0
  84. package/lib/Webhook.d.ts.map +1 -0
  85. package/lib/Webhook.js +98 -0
  86. package/lib/Webhook.js.map +1 -0
  87. package/lib/index.d.ts +19 -0
  88. package/lib/index.d.ts.map +1 -0
  89. package/lib/index.js +19 -0
  90. package/lib/index.js.map +1 -0
  91. package/lib/internal/Declared.d.ts +34 -0
  92. package/lib/internal/Declared.d.ts.map +1 -0
  93. package/lib/internal/Declared.js +38 -0
  94. package/lib/internal/Declared.js.map +1 -0
  95. package/lib/internal/Org.d.ts +22 -0
  96. package/lib/internal/Org.d.ts.map +1 -0
  97. package/lib/internal/Org.js +23 -0
  98. package/lib/internal/Org.js.map +1 -0
  99. package/lib/internal/Ownership.d.ts +29 -0
  100. package/lib/internal/Ownership.d.ts.map +1 -0
  101. package/lib/internal/Ownership.js +50 -0
  102. package/lib/internal/Ownership.js.map +1 -0
  103. package/lib/internal/Program.d.ts +69 -0
  104. package/lib/internal/Program.d.ts.map +1 -0
  105. package/lib/internal/Program.js +178 -0
  106. package/lib/internal/Program.js.map +1 -0
  107. package/lib/internal/Ref.d.ts +14 -0
  108. package/lib/internal/Ref.d.ts.map +1 -0
  109. package/lib/internal/Ref.js +7 -0
  110. package/lib/internal/Ref.js.map +1 -0
  111. package/lib/internal/SandboxHandle.d.ts +17 -0
  112. package/lib/internal/SandboxHandle.d.ts.map +1 -0
  113. package/lib/internal/SandboxHandle.js +249 -0
  114. package/lib/internal/SandboxHandle.js.map +1 -0
  115. package/lib/internal/WebhookDelivery.d.ts +16 -0
  116. package/lib/internal/WebhookDelivery.d.ts.map +1 -0
  117. package/lib/internal/WebhookDelivery.js +109 -0
  118. package/lib/internal/WebhookDelivery.js.map +1 -0
  119. package/package.json +67 -3
  120. package/src/AiProxy.ts +171 -0
  121. package/src/ApiKey.ts +250 -0
  122. package/src/AuthProvider.ts +472 -0
  123. package/src/BoatBindings.ts +136 -0
  124. package/src/Bootstrap.ts +55 -0
  125. package/src/ConnectSandbox.ts +60 -0
  126. package/src/ConnectSandboxHttp.ts +59 -0
  127. package/src/Contributions.ts +9 -0
  128. package/src/CreateSandbox.ts +143 -0
  129. package/src/CreateSandboxHttp.ts +115 -0
  130. package/src/CredentialProfile.ts +350 -0
  131. package/src/Credentials.ts +33 -0
  132. package/src/Environment.ts +416 -0
  133. package/src/Errors.ts +34 -0
  134. package/src/Providers.ts +71 -0
  135. package/src/Sandbox.ts +647 -0
  136. package/src/SandboxClient.ts +199 -0
  137. package/src/SandboxEvents.ts +125 -0
  138. package/src/SandboxEventsLive.ts +88 -0
  139. package/src/Snapshot.ts +284 -0
  140. package/src/Webhook.ts +163 -0
  141. package/src/globals.d.ts +2 -0
  142. package/src/index.ts +18 -0
  143. package/src/internal/Declared.ts +87 -0
  144. package/src/internal/Org.ts +31 -0
  145. package/src/internal/Ownership.ts +75 -0
  146. package/src/internal/Program.ts +250 -0
  147. package/src/internal/Ref.ts +15 -0
  148. package/src/internal/SandboxHandle.ts +425 -0
  149. package/src/internal/WebhookDelivery.ts +179 -0
@@ -0,0 +1,60 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type * as Effect from "effect/Effect";
3
+ import type { Sandbox } from "./Sandbox.ts";
4
+ import type { ConnectedSandbox } from "./SandboxClient.ts";
5
+
6
+ /**
7
+ * Work with one {@link Sandbox} declared in the stack, from a Worker,
8
+ * Function or any other host: run commands, read and write files, call its
9
+ * ports, stop, resume or snapshot it. The handle has the same operations
10
+ * as the sandboxes `CreateSandbox` returns, except `fork` and `delete`; its
11
+ * `sandboxId` is an Effect, read at runtime.
12
+ *
13
+ * A declared sandbox stops after its `autoStop`. Calls into it then fail
14
+ * with `SandboxNotRunning` until you `resume()` it.
15
+ *
16
+ * The host gets its own Boat API key, minted at deploy and revoked when the
17
+ * host is destroyed. The key reaches only this sandbox.
18
+ *
19
+ * ### Running commands in a dev box
20
+ * **Example:** Pull and test on request
21
+ * ```typescript
22
+ * const devBox = yield* Boat.ConnectSandbox(DevBox);
23
+ * return {
24
+ * fetch: Effect.gen(function* () {
25
+ * yield* devBox.resume({ autoStop: "10 minutes" });
26
+ * const test = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });
27
+ * return yield* HttpServerResponse.json(test);
28
+ * }),
29
+ * };
30
+ * ```
31
+ *
32
+ * ### Calling a program in the sandbox
33
+ * **Example:** Call port 3000
34
+ * ```typescript
35
+ * const port = yield* devBox.getTcpPort(3000);
36
+ * const response = yield* port.fetch(HttpClientRequest.get("/health"));
37
+ * ```
38
+ *
39
+ * **Example:** Proxy the Worker's request to port 3000
40
+ * ```typescript
41
+ * const port = yield* devBox.getTcpPort(3000);
42
+ * return yield* port.fetch(yield* HttpServerRequest);
43
+ * ```
44
+ *
45
+ * ### Stopping it early
46
+ * **Example:** Stop when the work is done
47
+ * ```typescript
48
+ * yield* devBox.stop();
49
+ * ```
50
+ *
51
+ * @binding
52
+ * @product Sandbox
53
+ */
54
+ export interface ConnectSandbox extends Binding.Service<
55
+ ConnectSandbox,
56
+ "Boat.ConnectSandbox",
57
+ (sandbox: Sandbox) => Effect.Effect<ConnectedSandbox>
58
+ > {}
59
+
60
+ export const ConnectSandbox = Binding.Service<ConnectSandbox>("Boat.ConnectSandbox");
@@ -0,0 +1,59 @@
1
+ import type * as Output from "alchemy/Output";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import type { ApiKeyAction } from "./ApiKey.ts";
5
+ import { bindBoat } from "./BoatBindings.ts";
6
+ import { ConnectSandbox } from "./ConnectSandbox.ts";
7
+ import { makeConnectedSandbox } from "./internal/SandboxHandle.ts";
8
+ import type { Sandbox } from "./Sandbox.ts";
9
+
10
+ /** What a `ConnectSandbox` host may do, on its one sandbox. */
11
+ const CONNECT_SANDBOX_ACTIONS: ApiKeyAction[] = [
12
+ "sandbox.read",
13
+ "sandbox.update",
14
+ "sandbox.stop",
15
+ "sandbox.resume",
16
+ "exec",
17
+ "file.read",
18
+ "file.write",
19
+ "host",
20
+ "snapshot.read",
21
+ "snapshot.write",
22
+ ];
23
+
24
+ /** `DevBox` → `DEV_BOX`, for the env var that carries its key. */
25
+ const envSuffix = (logicalId: string) =>
26
+ logicalId
27
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
28
+ .replace(/[^A-Za-z0-9]+/g, "_")
29
+ .toUpperCase();
30
+
31
+ /**
32
+ * HTTP implementation of {@link ConnectSandbox}: Boat's REST API with a key
33
+ * that reaches only the bound sandbox. Works on any host.
34
+ *
35
+ * ### Provide the layer
36
+ * **Example:** On a Cloudflare Worker
37
+ * ```typescript
38
+ * Effect.gen(function* () {
39
+ * const devBox = yield* Boat.ConnectSandbox(DevBox);
40
+ * // ...
41
+ * }).pipe(Effect.provide(Boat.ConnectSandboxHttp))
42
+ * ```
43
+ *
44
+ * @layer
45
+ * @product Sandbox
46
+ * @provides Boat.ConnectSandbox
47
+ */
48
+ export const ConnectSandboxHttp = Layer.succeed(
49
+ ConnectSandbox,
50
+ Effect.fn("Boat.ConnectSandbox")(function* (sandbox: Sandbox) {
51
+ const auth = yield* bindBoat(
52
+ `${sandbox.LogicalId}Key`,
53
+ `BOAT_${envSuffix(sandbox.LogicalId)}_KEY`,
54
+ { actions: CONNECT_SANDBOX_ACTIONS, sandboxIds: [sandbox.sandboxId] },
55
+ );
56
+ const sandboxId = yield* (sandbox.sandboxId as Output.Output<string, never>).asEffect();
57
+ return makeConnectedSandbox(auth, sandboxId);
58
+ }),
59
+ );
@@ -0,0 +1,9 @@
1
+ import * as Contributions from "alchemy/Contributions";
2
+
3
+ export default Contributions.make({
4
+ name: "boat",
5
+ auth: Contributions.lazy(
6
+ () => import("./AuthProvider.ts"),
7
+ (module) => module.BoatAuth,
8
+ ),
9
+ });
@@ -0,0 +1,143 @@
1
+ import type * as boat from "@distilled.cloud/boat-dev/boat";
2
+ import * as Binding from "alchemy/Binding";
3
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
4
+ import type * as Duration from "effect/Duration";
5
+ import type * as Effect from "effect/Effect";
6
+ import type { BoatError, BoatSandbox } from "./SandboxClient.ts";
7
+ import type { Snapshot } from "./Snapshot.ts";
8
+
9
+ /**
10
+ * Boat's `createSandbox` parameters, with `autoStop` in place of
11
+ * `ttlSeconds`. `from` defaults to the bound snapshot; `labels` are merged
12
+ * under the host's ownership labels.
13
+ */
14
+ export type CreateSandboxOptions = Omit<boat.CreateSandboxRequest, "count" | "ttlSeconds"> & {
15
+ /**
16
+ * How long the sandbox runs before Boat stops it, saving its disk;
17
+ * `false` never stops it.
18
+ * @default "1 hour"
19
+ */
20
+ readonly autoStop?: Duration.Input | false;
21
+ };
22
+
23
+ /** Boat's `listSandboxes` filters, with `labels` as a record. */
24
+ export interface ListSandboxesOptions {
25
+ /** Only sandboxes with all of these labels. */
26
+ readonly labels?: Readonly<Record<string, string>>;
27
+ /** Only sandboxes in these states, e.g. `["ready", "idle", "running"]`. */
28
+ readonly state?: ReadonlyArray<boat.SandboxState>;
29
+ }
30
+
31
+ /** Boat sandboxes: create, connect to, list and delete them. */
32
+ export interface SandboxesClient {
33
+ /** Creates a sandbox, from the bound snapshot unless `from` says otherwise. */
34
+ create(options?: CreateSandboxOptions): Effect.Effect<BoatSandbox, BoatError, RuntimeContext>;
35
+ /** A handle to an existing sandbox. */
36
+ connect(sandboxId: string): Effect.Effect<BoatSandbox, BoatError, RuntimeContext>;
37
+ /** A sandbox's details. */
38
+ get(sandboxId: string): Effect.Effect<boat.Sandbox, BoatError, RuntimeContext>;
39
+ /** Sandboxes, filtered by labels or state. */
40
+ list(
41
+ options?: ListSandboxesOptions,
42
+ ): Effect.Effect<ReadonlyArray<boat.Sandbox>, BoatError, RuntimeContext>;
43
+ /**
44
+ * Deletes a sandbox this host created, with its disk. Deleting one that
45
+ * is gone succeeds.
46
+ */
47
+ delete(sandboxId: string): Effect.Effect<void, BoatError, RuntimeContext>;
48
+ }
49
+
50
+ /**
51
+ * Create and manage Boat sandboxes from a Worker, Function or any other
52
+ * host.
53
+ *
54
+ * `Boat.CreateSandbox(snapshot)` creates sandboxes from a snapshot declared
55
+ * in the stack; `Boat.CreateSandbox()` creates them from scratch, or from
56
+ * the `from` each call passes. Both return the same client, whose calls take
57
+ * Boat's own parameters.
58
+ *
59
+ * Sandboxes created at run time are labelled with the stack, stage and
60
+ * host (`alchemy.stack`, `alchemy.stage`, `alchemy.id`) and are not deleted
61
+ * when the stack is destroyed. List them by those labels to clean them up:
62
+ * `sandboxes.list({ labels: { "alchemy.stack": "my-app", "alchemy.stage": "prod" } })`.
63
+ * Labels you pass to `create` merge underneath; they never replace the
64
+ * `alchemy.*` ones.
65
+ *
66
+ * The host gets its own Boat API key, minted at deploy and revoked when the
67
+ * host is destroyed. It may create sandboxes and work in them, and delete
68
+ * only the sandboxes it created; it can't touch the account's other
69
+ * sandboxes, environments, keys or billing.
70
+ *
71
+ * ### Creating sandboxes
72
+ * **Example:** One sandbox per request, deleted when the request ends
73
+ * ```typescript
74
+ * const sandboxes = yield* Boat.CreateSandbox();
75
+ * // in fetch:
76
+ * const sandbox = yield* Effect.acquireRelease(
77
+ * sandboxes.create({ type: "small", noEnv: true, autoStop: "10 minutes" }),
78
+ * (sandbox) => Effect.ignore(sandbox.delete()),
79
+ * );
80
+ * const result = yield* sandbox.exec("node --version");
81
+ * ```
82
+ *
83
+ * **Example:** From a snapshot declared in the stack
84
+ * ```typescript
85
+ * const sandboxes = yield* Boat.CreateSandbox(Golden);
86
+ * const sandbox = yield* sandboxes.create();
87
+ * ```
88
+ *
89
+ * **Example:** A long-lived sandbox per task, found by label
90
+ * ```typescript
91
+ * const [existing] = yield* sandboxes.list({ labels: { task } });
92
+ * const sandbox = existing
93
+ * ? yield* sandboxes.connect(existing.id)
94
+ * : yield* sandboxes.create({ labels: { task }, autoStop: "30 minutes" });
95
+ * ```
96
+ *
97
+ * ### Running commands
98
+ * `exec` runs to completion and returns `{ exitCode, stdout, stderr }`; a
99
+ * non-zero exit is returned, not failed. A string runs in a shell, an
100
+ * array runs its arguments quoted.
101
+ *
102
+ * **Example:** A shell line, an argument vector, stdin and a timeout
103
+ * ```typescript
104
+ * const test = yield* sandbox.exec("npm test", { cwd: "/home/user/app" });
105
+ * const version = yield* sandbox.exec(["node", "--version"]);
106
+ * const sorted = yield* sandbox.exec("sort", { stdin: "b\na\n" });
107
+ * const slow = yield* sandbox.exec("make", { timeout: "2 minutes" }); // Boat.ExecTimedOut
108
+ * ```
109
+ *
110
+ * **Example:** Start a server in the background and stream its output
111
+ * ```typescript
112
+ * const server = yield* sandbox.start("npm run dev", { cwd: "/home/user/app" });
113
+ * yield* server.output.pipe(
114
+ * Stream.take(5),
115
+ * Stream.runForEach((chunk) => Effect.log(new TextDecoder().decode(chunk.data))),
116
+ * );
117
+ * ```
118
+ *
119
+ * ### Calling a port
120
+ * **Example:** Proxy the Worker's request to port 3000
121
+ * ```typescript
122
+ * const port = yield* sandbox.getTcpPort(3000);
123
+ * return yield* port.fetch(yield* HttpServerRequest);
124
+ * ```
125
+ *
126
+ * ### Forking and snapshots
127
+ * **Example:** Save a sandbox and fork it
128
+ * ```typescript
129
+ * yield* sandbox.stop(); // stopping snapshots it
130
+ * const copy = yield* sandbox.fork({ autoStop: "10 minutes" });
131
+ * yield* sandbox.resume();
132
+ * ```
133
+ *
134
+ * @binding
135
+ * @product Sandbox
136
+ */
137
+ export interface CreateSandbox extends Binding.Service<
138
+ CreateSandbox,
139
+ "Boat.CreateSandbox",
140
+ (base?: Snapshot) => Effect.Effect<SandboxesClient>
141
+ > {}
142
+
143
+ export const CreateSandbox = Binding.Service<CreateSandbox>("Boat.CreateSandbox");
@@ -0,0 +1,115 @@
1
+ import * as boat from "@distilled.cloud/boat-dev/boat";
2
+ import type * as Output from "alchemy/Output";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
6
+ import type { ApiKeyAction } from "./ApiKey.ts";
7
+ import { bindBoat, bindOwnerLabels } from "./BoatBindings.ts";
8
+ import { CreateSandbox, type CreateSandboxOptions, type SandboxesClient } from "./CreateSandbox.ts";
9
+ import { BoatUnexpectedResponse } from "./Errors.ts";
10
+ import { labelFilter } from "./internal/Ownership.ts";
11
+ import { makeSandboxHandle, ttlOf } from "./internal/SandboxHandle.ts";
12
+ import type { Snapshot } from "./Snapshot.ts";
13
+
14
+ /** What a `CreateSandbox` host may do: work in sandboxes, and delete only its own. */
15
+ const CREATE_SANDBOX_ACTIONS: ApiKeyAction[] = [
16
+ "sandbox.create",
17
+ "sandbox.read",
18
+ "sandbox.update",
19
+ "sandbox.stop",
20
+ "sandbox.resume",
21
+ "sandbox.fork",
22
+ "sandbox.delete-own",
23
+ "exec",
24
+ "file.read",
25
+ "file.write",
26
+ "host",
27
+ "snapshot.read",
28
+ "snapshot.write",
29
+ ];
30
+
31
+ /**
32
+ * HTTP implementation of {@link CreateSandbox}: Boat's REST API with the
33
+ * host's own key. Works on any host.
34
+ *
35
+ * ### Provide the layer
36
+ * **Example:** On a Cloudflare Worker
37
+ * ```typescript
38
+ * Effect.gen(function* () {
39
+ * const sandboxes = yield* Boat.CreateSandbox();
40
+ * // ...
41
+ * }).pipe(Effect.provide(Boat.CreateSandboxHttp))
42
+ * ```
43
+ *
44
+ * @layer
45
+ * @product Sandbox
46
+ * @provides Boat.CreateSandbox
47
+ */
48
+ export const CreateSandboxHttp = Layer.succeed(
49
+ CreateSandbox,
50
+ Effect.fn("Boat.CreateSandbox")(function* (base?: Snapshot) {
51
+ const auth = yield* bindBoat("BoatSandboxesKey", "BOAT_SANDBOXES_KEY", {
52
+ actions: CREATE_SANDBOX_ACTIONS,
53
+ });
54
+ const owner = yield* bindOwnerLabels;
55
+ const baseName: Effect.Effect<string> | undefined =
56
+ base === undefined
57
+ ? undefined
58
+ : yield* (base.name as Output.Output<string, never>).asEffect();
59
+
60
+ const handleOf = (sandbox: boat.Sandbox) => makeSandboxHandle(auth, sandbox, owner);
61
+
62
+ const client: SandboxesClient = {
63
+ create: Effect.fn("Boat.CreateSandbox.create")(function* (
64
+ createOptions: CreateSandboxOptions = {},
65
+ ) {
66
+ const { autoStop, ...options } = createOptions;
67
+ const from = options.from ?? (baseName === undefined ? undefined : yield* baseName);
68
+ const created = yield* auth.authorize(
69
+ boat.createSandbox({
70
+ ...options,
71
+ ...(autoStop === undefined ? {} : { ttlSeconds: ttlOf(autoStop) }),
72
+ ...(from === undefined ? {} : { from }),
73
+ labels: { ...options.labels, ...(yield* owner) },
74
+ }),
75
+ );
76
+ return handleOf(created.sandbox);
77
+ }),
78
+ connect: Effect.fn("Boat.CreateSandbox.connect")(function* (sandboxId) {
79
+ const got = yield* auth.authorize(boat.getSandbox({ sandboxId }));
80
+ if (got.sandbox === undefined) {
81
+ return yield* new BoatUnexpectedResponse({
82
+ message: `Boat returned no sandbox for ${sandboxId}`,
83
+ });
84
+ }
85
+ return handleOf(got.sandbox);
86
+ }),
87
+ get: Effect.fn("Boat.CreateSandbox.get")(function* (sandboxId) {
88
+ const got = yield* auth.authorize(boat.getSandbox({ sandboxId }));
89
+ if (got.sandbox === undefined) {
90
+ return yield* new BoatUnexpectedResponse({
91
+ message: `Boat returned no sandbox for ${sandboxId}`,
92
+ });
93
+ }
94
+ return got.sandbox;
95
+ }),
96
+ list: Effect.fn("Boat.CreateSandbox.list")(function* (options) {
97
+ const all = yield* auth.authorize(
98
+ Stream.runCollect(
99
+ boat.listSandboxes.items({
100
+ ...(options?.labels === undefined ? {} : { label: labelFilter(options.labels) }),
101
+ ...(options?.state === undefined ? {} : { state: options.state.join(",") }),
102
+ }),
103
+ ),
104
+ );
105
+ return Array.from(all);
106
+ }),
107
+ delete: (sandboxId) =>
108
+ auth.authorize(boat.deleteSandbox({ sandboxId, xAsciiConfirmDelete: sandboxId })).pipe(
109
+ Effect.asVoid,
110
+ Effect.catchTag("NotFound", () => Effect.void),
111
+ ),
112
+ };
113
+ return client;
114
+ }),
115
+ );