@alchemy.run/boat 0.0.0-stage → 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.
- package/LICENSE +201 -0
- package/README.md +70 -2
- package/lib/AiProxy.d.ts +89 -0
- package/lib/AiProxy.d.ts.map +1 -0
- package/lib/AiProxy.js +125 -0
- package/lib/AiProxy.js.map +1 -0
- package/lib/ApiKey.d.ts +105 -0
- package/lib/ApiKey.d.ts.map +1 -0
- package/lib/ApiKey.js +134 -0
- package/lib/ApiKey.js.map +1 -0
- package/lib/AuthProvider.d.ts +71 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +322 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/BoatBindings.d.ts +52 -0
- package/lib/BoatBindings.d.ts.map +1 -0
- package/lib/BoatBindings.js +104 -0
- package/lib/BoatBindings.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +28 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +56 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +3 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/ConnectSandboxHttp.d.ts +21 -0
- package/lib/ConnectSandboxHttp.d.ts.map +1 -0
- package/lib/ConnectSandboxHttp.js +46 -0
- package/lib/ConnectSandboxHttp.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateSandbox.d.ts +134 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +3 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/CreateSandboxHttp.d.ts +21 -0
- package/lib/CreateSandboxHttp.d.ts.map +1 -0
- package/lib/CreateSandboxHttp.js +93 -0
- package/lib/CreateSandboxHttp.js.map +1 -0
- package/lib/CredentialProfile.d.ts +157 -0
- package/lib/CredentialProfile.d.ts.map +1 -0
- package/lib/CredentialProfile.js +245 -0
- package/lib/CredentialProfile.js.map +1 -0
- package/lib/Credentials.d.ts +15 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +23 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Environment.d.ts +219 -0
- package/lib/Environment.d.ts.map +1 -0
- package/lib/Environment.js +273 -0
- package/lib/Environment.js.map +1 -0
- package/lib/Errors.d.ts +50 -0
- package/lib/Errors.d.ts.map +1 -0
- package/lib/Errors.js +17 -0
- package/lib/Errors.js.map +1 -0
- package/lib/Providers.d.ts +33 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +47 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Sandbox.d.ts +205 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +368 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxClient.d.ts +159 -0
- package/lib/SandboxClient.d.ts.map +1 -0
- package/lib/SandboxClient.js +1 -0
- package/lib/SandboxClient.js.map +1 -0
- package/lib/SandboxEvents.d.ts +111 -0
- package/lib/SandboxEvents.d.ts.map +1 -0
- package/lib/SandboxEvents.js +60 -0
- package/lib/SandboxEvents.js.map +1 -0
- package/lib/SandboxEventsLive.d.ts +13 -0
- package/lib/SandboxEventsLive.d.ts.map +1 -0
- package/lib/SandboxEventsLive.js +63 -0
- package/lib/SandboxEventsLive.js.map +1 -0
- package/lib/Snapshot.d.ts +123 -0
- package/lib/Snapshot.d.ts.map +1 -0
- package/lib/Snapshot.js +186 -0
- package/lib/Snapshot.js.map +1 -0
- package/lib/Webhook.d.ts +56 -0
- package/lib/Webhook.d.ts.map +1 -0
- package/lib/Webhook.js +98 -0
- package/lib/Webhook.js.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +19 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Declared.d.ts +34 -0
- package/lib/internal/Declared.d.ts.map +1 -0
- package/lib/internal/Declared.js +38 -0
- package/lib/internal/Declared.js.map +1 -0
- package/lib/internal/Org.d.ts +22 -0
- package/lib/internal/Org.d.ts.map +1 -0
- package/lib/internal/Org.js +23 -0
- package/lib/internal/Org.js.map +1 -0
- package/lib/internal/Ownership.d.ts +29 -0
- package/lib/internal/Ownership.d.ts.map +1 -0
- package/lib/internal/Ownership.js +50 -0
- package/lib/internal/Ownership.js.map +1 -0
- package/lib/internal/Program.d.ts +69 -0
- package/lib/internal/Program.d.ts.map +1 -0
- package/lib/internal/Program.js +178 -0
- package/lib/internal/Program.js.map +1 -0
- package/lib/internal/Ref.d.ts +14 -0
- package/lib/internal/Ref.d.ts.map +1 -0
- package/lib/internal/Ref.js +7 -0
- package/lib/internal/Ref.js.map +1 -0
- package/lib/internal/SandboxHandle.d.ts +17 -0
- package/lib/internal/SandboxHandle.d.ts.map +1 -0
- package/lib/internal/SandboxHandle.js +249 -0
- package/lib/internal/SandboxHandle.js.map +1 -0
- package/lib/internal/WebhookDelivery.d.ts +16 -0
- package/lib/internal/WebhookDelivery.d.ts.map +1 -0
- package/lib/internal/WebhookDelivery.js +109 -0
- package/lib/internal/WebhookDelivery.js.map +1 -0
- package/package.json +81 -3
- package/src/AiProxy.ts +171 -0
- package/src/ApiKey.ts +250 -0
- package/src/AuthProvider.ts +472 -0
- package/src/BoatBindings.ts +136 -0
- package/src/Bootstrap.ts +55 -0
- package/src/ConnectSandbox.ts +60 -0
- package/src/ConnectSandboxHttp.ts +59 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateSandbox.ts +143 -0
- package/src/CreateSandboxHttp.ts +115 -0
- package/src/CredentialProfile.ts +350 -0
- package/src/Credentials.ts +33 -0
- package/src/Environment.ts +416 -0
- package/src/Errors.ts +34 -0
- package/src/Providers.ts +71 -0
- package/src/Sandbox.ts +647 -0
- package/src/SandboxClient.ts +199 -0
- package/src/SandboxEvents.ts +125 -0
- package/src/SandboxEventsLive.ts +88 -0
- package/src/Snapshot.ts +284 -0
- package/src/Webhook.ts +163 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +18 -0
- package/src/internal/Declared.ts +87 -0
- package/src/internal/Org.ts +31 -0
- package/src/internal/Ownership.ts +75 -0
- package/src/internal/Program.ts +250 -0
- package/src/internal/Ref.ts +15 -0
- package/src/internal/SandboxHandle.ts +425 -0
- package/src/internal/WebhookDelivery.ts +179 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How Boat bindings reach Boat from any host.
|
|
3
|
+
*
|
|
4
|
+
* Boat has scoped API keys and no identity federation, so every binding
|
|
5
|
+
* mints its own key at deploy, a `Boat.ApiKey` in the host's namespace,
|
|
6
|
+
* with only the actions it needs and, for a declared sandbox, only that
|
|
7
|
+
* sandbox. The key is deleted with the host. The deployer's credentials
|
|
8
|
+
* never reach a host. The binding captures the key as a named runtime
|
|
9
|
+
* Output; inside the deployed host it is read back from the host's env,
|
|
10
|
+
* still `Redacted`. Every runtime call provides `Credentials` from it, so
|
|
11
|
+
* nothing reads `process.env`. The deployer's org and API URL are bound
|
|
12
|
+
* alongside it (`BOAT_ORG`, `BOAT_API_BASE_URL`), so the host's calls bill
|
|
13
|
+
* the same wallet the deploy did.
|
|
14
|
+
*
|
|
15
|
+
* Bindings that create sandboxes at run time label them with the host's
|
|
16
|
+
* `alchemy.stack` / `alchemy.stage` / `alchemy.id`, computed at deploy and
|
|
17
|
+
* bound into the host (`bindOwnerLabels`). Destroying the stack does not
|
|
18
|
+
* delete them.
|
|
19
|
+
*/
|
|
20
|
+
import { Credentials, DEFAULT_API_BASE_URL } from "@distilled.cloud/boat-dev/Credentials";
|
|
21
|
+
import { orDieCredentialsUnavailable } from "alchemy/Auth/Resolve";
|
|
22
|
+
import * as Binding from "alchemy/Binding";
|
|
23
|
+
import * as Namespace from "alchemy/Namespace";
|
|
24
|
+
import * as Output from "alchemy/Output";
|
|
25
|
+
import { Stack } from "alchemy/Stack";
|
|
26
|
+
import { Stage } from "alchemy/Stage";
|
|
27
|
+
import * as Effect from "effect/Effect";
|
|
28
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
29
|
+
import * as Layer from "effect/Layer";
|
|
30
|
+
import * as Option from "effect/Option";
|
|
31
|
+
import * as Redacted from "effect/Redacted";
|
|
32
|
+
import { ApiKey } from "./ApiKey.js";
|
|
33
|
+
import { ALCHEMY_LABEL_PREFIX } from "./internal/Ownership.js";
|
|
34
|
+
/** A plain value as an Output with no requirements (`asOutput` leaves them `any`). */
|
|
35
|
+
const output = (value) => Output.asOutput(value);
|
|
36
|
+
/**
|
|
37
|
+
* Mints the binding's key (`<keyId>` in the host's namespace, with
|
|
38
|
+
* `scope`) and binds it onto the host as `<envName>`, and returns a runtime
|
|
39
|
+
* accessor for it. Without a host (a script) nothing is minted, and calls
|
|
40
|
+
* use the ambient credentials.
|
|
41
|
+
*/
|
|
42
|
+
export const bindBoat = Effect.fn("Boat.bind")(function* (keyId, envName, scope) {
|
|
43
|
+
// Yielding the resource class erases its `Providers` requirement; the
|
|
44
|
+
// stack satisfies it at plan time.
|
|
45
|
+
const makeKey = yield* ApiKey;
|
|
46
|
+
let apiKey = output(Redacted.make(""));
|
|
47
|
+
let deployer = { apiBaseUrl: DEFAULT_API_BASE_URL, org: "" };
|
|
48
|
+
if (!globalThis.__ALCHEMY_RUNTIME__) {
|
|
49
|
+
const host = yield* Binding.Host;
|
|
50
|
+
if (host !== undefined) {
|
|
51
|
+
const key = yield* Namespace.push(host.LogicalId, makeKey(keyId, scope));
|
|
52
|
+
apiKey = key.key;
|
|
53
|
+
const credentials = yield* Effect.serviceOption(Credentials);
|
|
54
|
+
if (Option.isSome(credentials)) {
|
|
55
|
+
const { apiBaseUrl, org } = yield* credentials.value.pipe(orDieCredentialsUnavailable("Boat"));
|
|
56
|
+
deployer = { apiBaseUrl, org: org ?? "" };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const key = yield* Output.named(apiKey, envName).asEffect();
|
|
61
|
+
const apiBaseUrl = yield* Output.named(output(deployer.apiBaseUrl), "BOAT_API_BASE_URL").asEffect();
|
|
62
|
+
const org = yield* Output.named(output(deployer.org), "BOAT_ORG").asEffect();
|
|
63
|
+
const services = Layer.mergeAll(Layer.succeed(Credentials, Effect.all([key, apiBaseUrl, org]).pipe(Effect.map(([apiKey, apiBaseUrl, org]) => ({
|
|
64
|
+
apiKey,
|
|
65
|
+
apiBaseUrl: apiBaseUrl || DEFAULT_API_BASE_URL,
|
|
66
|
+
...(org ? { org } : {}),
|
|
67
|
+
})))), FetchHttpClient.layer);
|
|
68
|
+
return {
|
|
69
|
+
authorize: (effect) => effect.pipe(Effect.provide(services)),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const parseOwnerLabels = (json) => {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(json || "{}");
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Binds the host's `alchemy.stack` / `alchemy.stage` / `alchemy.id` into
|
|
82
|
+
* it, and returns them at run time for `create` to label what it makes.
|
|
83
|
+
* Without a host (a script) there are none.
|
|
84
|
+
*/
|
|
85
|
+
export const bindOwnerLabels = Effect.gen(function* () {
|
|
86
|
+
let labels = "{}";
|
|
87
|
+
if (!globalThis.__ALCHEMY_RUNTIME__) {
|
|
88
|
+
const host = yield* Binding.Host;
|
|
89
|
+
// The values `createInternalTags(host.LogicalId)` gives, read without
|
|
90
|
+
// adding `Stack` / `Stage` to the binding's requirements.
|
|
91
|
+
const stack = yield* Effect.serviceOption(Stack);
|
|
92
|
+
const stage = yield* Effect.serviceOption(Stage);
|
|
93
|
+
if (host !== undefined && Option.isSome(stack) && Option.isSome(stage)) {
|
|
94
|
+
labels = JSON.stringify({
|
|
95
|
+
[`${ALCHEMY_LABEL_PREFIX}stack`]: stack.value.name,
|
|
96
|
+
[`${ALCHEMY_LABEL_PREFIX}stage`]: stage.value,
|
|
97
|
+
[`${ALCHEMY_LABEL_PREFIX}id`]: host.LogicalId,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const bound = yield* Output.named(output(labels), "BOAT_OWNER_LABELS").asEffect();
|
|
102
|
+
return Effect.map(bound, parseOwnerLabels);
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=BoatBindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BoatBindings.js","sources":["../src/BoatBindings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the bundled program (the runners registered via `host.run` /
|
|
3
|
+
* serve) and run it with a Node HTTP server bound to `PORT` on every
|
|
4
|
+
* interface, so the sandbox's port URL reaches the `{ fetch }`
|
|
5
|
+
* handler and `host.run` loops stay alive.
|
|
6
|
+
*/
|
|
7
|
+
export declare const bootstrap: (entrypoint: unknown) => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=Bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bootstrap.d.ts","sources":["../src/Bootstrap.ts"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,eAAO,MAAM,SAAS,eAAgB,OAAO,KAAG,OAAO,CAAC,IAAI,CA0B3D,CAAC"}
|
package/lib/Bootstrap.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process bootstrap for a `Boat.Sandbox` with `main` (a Node process in
|
|
3
|
+
* the sandbox serving the bundled program). The generated entry imports this module
|
|
4
|
+
* and the user's `main`, nothing else — see `alchemy/Runtime/Bootstrap`
|
|
5
|
+
* for why.
|
|
6
|
+
*/
|
|
7
|
+
import { NodeServices } from "@effect/platform-node";
|
|
8
|
+
import { NodeHttpServer } from "alchemy/Http";
|
|
9
|
+
import { reifyBoundConfigProvider } from "alchemy/Runtime";
|
|
10
|
+
import { entrypointLayer, resolveProgram, runProcess, stackFromEnv, } from "alchemy/Runtime/Bootstrap";
|
|
11
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
12
|
+
import * as SecureRandom from "effect/crypto/SecureRandom";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import * as Logger from "effect/Logger";
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the bundled program (the runners registered via `host.run` /
|
|
19
|
+
* serve) and run it with a Node HTTP server bound to `PORT` on every
|
|
20
|
+
* interface, so the sandbox's port URL reaches the `{ fetch }`
|
|
21
|
+
* handler and `host.run` loops stay alive.
|
|
22
|
+
*/
|
|
23
|
+
export const bootstrap = (entrypoint) => {
|
|
24
|
+
const platform = Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer, SecureRandom.layerWebCrypto, Logger.layer([Logger.consolePretty()]));
|
|
25
|
+
const program = resolveProgram("program").pipe(Effect.provide(entrypointLayer(entrypoint).pipe(Layer.provideMerge(stackFromEnv), Layer.provideMerge(NodeHttpServer({ hostname: "0.0.0.0" })), Layer.provideMerge(platform), Layer.provideMerge(Layer.succeed(ConfigProvider.ConfigProvider, reifyBoundConfigProvider(ConfigProvider.fromEnv(), process.env))))), Effect.scoped);
|
|
26
|
+
return runProcess("Boat program", program);
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=Bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bootstrap.js","sources":["../src/Bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EACL,eAAe,EACf,cAAc,EACd,UAAU,EACV,YAAY,GACb,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,eAAe,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,UAAmB,EAAiB,EAAE;IAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAC7B,YAAY,CAAC,KAAK,EAClB,eAAe,CAAC,KAAK,EACrB,YAAY,CAAC,cAAc,EAC3B,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CACvC,CAAC;IAEF,MAAM,OAAO,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI,CAC5C,MAAM,CAAC,OAAO,CACZ,eAAe,CAAC,UAAU,CAAC,CAAC,IAAI,CAC9B,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,EAChC,KAAK,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,EAC3D,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,EAC5B,KAAK,CAAC,YAAY,CAChB,KAAK,CAAC,OAAO,CACX,cAAc,CAAC,cAAc,EAC7B,wBAAwB,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,CAChE,CACF,CACF,CACF,EACD,MAAM,CAAC,MAAM,CACd,CAAC;IAEF,OAAO,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
* Work with one {@link Sandbox} declared in the stack, from a Worker,
|
|
7
|
+
* Function or any other host: run commands, read and write files, call its
|
|
8
|
+
* ports, stop, resume or snapshot it. The handle has the same operations
|
|
9
|
+
* as the sandboxes `CreateSandbox` returns, except `fork` and `delete`; its
|
|
10
|
+
* `sandboxId` is an Effect, read at runtime.
|
|
11
|
+
*
|
|
12
|
+
* A declared sandbox stops after its `autoStop`. Calls into it then fail
|
|
13
|
+
* with `SandboxNotRunning` until you `resume()` it.
|
|
14
|
+
*
|
|
15
|
+
* The host gets its own Boat API key, minted at deploy and revoked when the
|
|
16
|
+
* host is destroyed. The key reaches only this sandbox.
|
|
17
|
+
*
|
|
18
|
+
* ### Running commands in a dev box
|
|
19
|
+
* **Example:** Pull and test on request
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const devBox = yield* Boat.ConnectSandbox(DevBox);
|
|
22
|
+
* return {
|
|
23
|
+
* fetch: Effect.gen(function* () {
|
|
24
|
+
* yield* devBox.resume({ autoStop: "10 minutes" });
|
|
25
|
+
* const test = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });
|
|
26
|
+
* return yield* HttpServerResponse.json(test);
|
|
27
|
+
* }),
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* ### Calling a program in the sandbox
|
|
32
|
+
* **Example:** Call port 3000
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const port = yield* devBox.getTcpPort(3000);
|
|
35
|
+
* const response = yield* port.fetch(HttpClientRequest.get("/health"));
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* **Example:** Proxy the Worker's request to port 3000
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const port = yield* devBox.getTcpPort(3000);
|
|
41
|
+
* return yield* port.fetch(yield* HttpServerRequest);
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ### Stopping it early
|
|
45
|
+
* **Example:** Stop when the work is done
|
|
46
|
+
* ```typescript
|
|
47
|
+
* yield* devBox.stop();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @binding
|
|
51
|
+
* @product Sandbox
|
|
52
|
+
*/
|
|
53
|
+
export interface ConnectSandbox extends Binding.Service<ConnectSandbox, "Boat.ConnectSandbox", (sandbox: Sandbox) => Effect.Effect<ConnectedSandbox>> {
|
|
54
|
+
}
|
|
55
|
+
export declare const ConnectSandbox: ConnectSandbox;
|
|
56
|
+
//# sourceMappingURL=ConnectSandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectSandbox.d.ts","sources":["../src/ConnectSandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,WAAW,cAAe,SAAQ,OAAO,CAAC,OAAO,CACrD,cAAc,EACd,qBAAqB,EACrB,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CACtD;CAAG;AAEJ,eAAO,MAAM,cAAc,gBAAyD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectSandbox.js","sources":["../src/ConnectSandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AA2D3C,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAiB,qBAAqB,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import { ConnectSandbox } from "./ConnectSandbox.ts";
|
|
3
|
+
/**
|
|
4
|
+
* HTTP implementation of {@link ConnectSandbox}: Boat's REST API with a key
|
|
5
|
+
* that reaches only the bound sandbox. Works on any host.
|
|
6
|
+
*
|
|
7
|
+
* ### Provide the layer
|
|
8
|
+
* **Example:** On a Cloudflare Worker
|
|
9
|
+
* ```typescript
|
|
10
|
+
* Effect.gen(function* () {
|
|
11
|
+
* const devBox = yield* Boat.ConnectSandbox(DevBox);
|
|
12
|
+
* // ...
|
|
13
|
+
* }).pipe(Effect.provide(Boat.ConnectSandboxHttp))
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @layer
|
|
17
|
+
* @product Sandbox
|
|
18
|
+
* @provides Boat.ConnectSandbox
|
|
19
|
+
*/
|
|
20
|
+
export declare const ConnectSandboxHttp: Layer.Layer<ConnectSandbox, never, never>;
|
|
21
|
+
//# sourceMappingURL=ConnectSandboxHttp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectSandboxHttp.d.ts","sources":["../src/ConnectSandboxHttp.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAyBrD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,2CAW9B,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import { bindBoat } from "./BoatBindings.js";
|
|
4
|
+
import { ConnectSandbox } from "./ConnectSandbox.js";
|
|
5
|
+
import { makeConnectedSandbox } from "./internal/SandboxHandle.js";
|
|
6
|
+
/** What a `ConnectSandbox` host may do, on its one sandbox. */
|
|
7
|
+
const CONNECT_SANDBOX_ACTIONS = [
|
|
8
|
+
"sandbox.read",
|
|
9
|
+
"sandbox.update",
|
|
10
|
+
"sandbox.stop",
|
|
11
|
+
"sandbox.resume",
|
|
12
|
+
"exec",
|
|
13
|
+
"file.read",
|
|
14
|
+
"file.write",
|
|
15
|
+
"host",
|
|
16
|
+
"snapshot.read",
|
|
17
|
+
"snapshot.write",
|
|
18
|
+
];
|
|
19
|
+
/** `DevBox` → `DEV_BOX`, for the env var that carries its key. */
|
|
20
|
+
const envSuffix = (logicalId) => logicalId
|
|
21
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
22
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
23
|
+
.toUpperCase();
|
|
24
|
+
/**
|
|
25
|
+
* HTTP implementation of {@link ConnectSandbox}: Boat's REST API with a key
|
|
26
|
+
* that reaches only the bound sandbox. Works on any host.
|
|
27
|
+
*
|
|
28
|
+
* ### Provide the layer
|
|
29
|
+
* **Example:** On a Cloudflare Worker
|
|
30
|
+
* ```typescript
|
|
31
|
+
* Effect.gen(function* () {
|
|
32
|
+
* const devBox = yield* Boat.ConnectSandbox(DevBox);
|
|
33
|
+
* // ...
|
|
34
|
+
* }).pipe(Effect.provide(Boat.ConnectSandboxHttp))
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @layer
|
|
38
|
+
* @product Sandbox
|
|
39
|
+
* @provides Boat.ConnectSandbox
|
|
40
|
+
*/
|
|
41
|
+
export const ConnectSandboxHttp = Layer.succeed(ConnectSandbox, Effect.fn("Boat.ConnectSandbox")(function* (sandbox) {
|
|
42
|
+
const auth = yield* bindBoat(`${sandbox.LogicalId}Key`, `BOAT_${envSuffix(sandbox.LogicalId)}_KEY`, { actions: CONNECT_SANDBOX_ACTIONS, sandboxIds: [sandbox.sandboxId] });
|
|
43
|
+
const sandboxId = yield* sandbox.sandboxId.asEffect();
|
|
44
|
+
return makeConnectedSandbox(auth, sandboxId);
|
|
45
|
+
}));
|
|
46
|
+
//# sourceMappingURL=ConnectSandboxHttp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectSandboxHttp.js","sources":["../src/ConnectSandboxHttp.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,+DAA+D;AAC/D,MAAM,uBAAuB,GAAmB;IAC9C,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,gBAAgB;IAChB,MAAM;IACN,WAAW;IACX,YAAY;IACZ,MAAM;IACN,eAAe;IACf,gBAAgB;CACjB,CAAC;AAEF,kEAAkE;AAClE,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAE,EAAE,CACtC,SAAS;KACN,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;KACtC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;KAC9B,WAAW,EAAE,CAAC;AAEnB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC,OAAO,CAC7C,cAAc,EACd,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAgB;IAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAC1B,GAAG,OAAO,CAAC,SAAS,KAAK,EACzB,QAAQ,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,EAC1C,EAAE,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CACtE,CAAC;IACF,MAAM,SAAS,GAAG,KAAK,CAAC,CAAE,OAAO,CAAC,SAA0C,CAAC,QAAQ,EAAE,CAAC;IACxF,OAAO,oBAAoB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC/C,CAAC,CAAC,CACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as Contributions from "alchemy/Contributions";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
readonly name: "boat";
|
|
4
|
+
readonly auth: Contributions.Lazy<import("effect/Layer").Layer<never, never, import("alchemy/Auth/Registry").AuthProviders | import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/crypto/SecureRandom").SecureRandom>>;
|
|
5
|
+
};
|
|
6
|
+
export default _default;
|
|
7
|
+
//# sourceMappingURL=Contributions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Contributions.d.ts","sources":["../src/Contributions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,uBAAuB,CAAC;;;;;AAEvD,wBAMG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Contributions.js","sources":["../src/Contributions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,uBAAuB,CAAC;AAEvD,eAAe,aAAa,CAAC,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,aAAa,CAAC,IAAI,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,EACjC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAC5B;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
* Boat's `createSandbox` parameters, with `autoStop` in place of
|
|
10
|
+
* `ttlSeconds`. `from` defaults to the bound snapshot; `labels` are merged
|
|
11
|
+
* under the host's ownership labels.
|
|
12
|
+
*/
|
|
13
|
+
export type CreateSandboxOptions = Omit<boat.CreateSandboxRequest, "count" | "ttlSeconds"> & {
|
|
14
|
+
/**
|
|
15
|
+
* How long the sandbox runs before Boat stops it, saving its disk;
|
|
16
|
+
* `false` never stops it.
|
|
17
|
+
* @default "1 hour"
|
|
18
|
+
*/
|
|
19
|
+
readonly autoStop?: Duration.Input | false;
|
|
20
|
+
};
|
|
21
|
+
/** Boat's `listSandboxes` filters, with `labels` as a record. */
|
|
22
|
+
export interface ListSandboxesOptions {
|
|
23
|
+
/** Only sandboxes with all of these labels. */
|
|
24
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
25
|
+
/** Only sandboxes in these states, e.g. `["ready", "idle", "running"]`. */
|
|
26
|
+
readonly state?: ReadonlyArray<boat.SandboxState>;
|
|
27
|
+
}
|
|
28
|
+
/** Boat sandboxes: create, connect to, list and delete them. */
|
|
29
|
+
export interface SandboxesClient {
|
|
30
|
+
/** Creates a sandbox, from the bound snapshot unless `from` says otherwise. */
|
|
31
|
+
create(options?: CreateSandboxOptions): Effect.Effect<BoatSandbox, BoatError, RuntimeContext>;
|
|
32
|
+
/** A handle to an existing sandbox. */
|
|
33
|
+
connect(sandboxId: string): Effect.Effect<BoatSandbox, BoatError, RuntimeContext>;
|
|
34
|
+
/** A sandbox's details. */
|
|
35
|
+
get(sandboxId: string): Effect.Effect<boat.Sandbox, BoatError, RuntimeContext>;
|
|
36
|
+
/** Sandboxes, filtered by labels or state. */
|
|
37
|
+
list(options?: ListSandboxesOptions): Effect.Effect<ReadonlyArray<boat.Sandbox>, BoatError, RuntimeContext>;
|
|
38
|
+
/**
|
|
39
|
+
* Deletes a sandbox this host created, with its disk. Deleting one that
|
|
40
|
+
* is gone succeeds.
|
|
41
|
+
*/
|
|
42
|
+
delete(sandboxId: string): Effect.Effect<void, BoatError, RuntimeContext>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create and manage Boat sandboxes from a Worker, Function or any other
|
|
46
|
+
* host.
|
|
47
|
+
*
|
|
48
|
+
* `Boat.CreateSandbox(snapshot)` creates sandboxes from a snapshot declared
|
|
49
|
+
* in the stack; `Boat.CreateSandbox()` creates them from scratch, or from
|
|
50
|
+
* the `from` each call passes. Both return the same client, whose calls take
|
|
51
|
+
* Boat's own parameters.
|
|
52
|
+
*
|
|
53
|
+
* Sandboxes created at run time are labelled with the stack, stage and
|
|
54
|
+
* host (`alchemy.stack`, `alchemy.stage`, `alchemy.id`) and are not deleted
|
|
55
|
+
* when the stack is destroyed. List them by those labels to clean them up:
|
|
56
|
+
* `sandboxes.list({ labels: { "alchemy.stack": "my-app", "alchemy.stage": "prod" } })`.
|
|
57
|
+
* Labels you pass to `create` merge underneath; they never replace the
|
|
58
|
+
* `alchemy.*` ones.
|
|
59
|
+
*
|
|
60
|
+
* The host gets its own Boat API key, minted at deploy and revoked when the
|
|
61
|
+
* host is destroyed. It may create sandboxes and work in them, and delete
|
|
62
|
+
* only the sandboxes it created; it can't touch the account's other
|
|
63
|
+
* sandboxes, environments, keys or billing.
|
|
64
|
+
*
|
|
65
|
+
* ### Creating sandboxes
|
|
66
|
+
* **Example:** One sandbox per request, deleted when the request ends
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const sandboxes = yield* Boat.CreateSandbox();
|
|
69
|
+
* // in fetch:
|
|
70
|
+
* const sandbox = yield* Effect.acquireRelease(
|
|
71
|
+
* sandboxes.create({ type: "small", noEnv: true, autoStop: "10 minutes" }),
|
|
72
|
+
* (sandbox) => Effect.ignore(sandbox.delete()),
|
|
73
|
+
* );
|
|
74
|
+
* const result = yield* sandbox.exec("node --version");
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* **Example:** From a snapshot declared in the stack
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const sandboxes = yield* Boat.CreateSandbox(Golden);
|
|
80
|
+
* const sandbox = yield* sandboxes.create();
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* **Example:** A long-lived sandbox per task, found by label
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const [existing] = yield* sandboxes.list({ labels: { task } });
|
|
86
|
+
* const sandbox = existing
|
|
87
|
+
* ? yield* sandboxes.connect(existing.id)
|
|
88
|
+
* : yield* sandboxes.create({ labels: { task }, autoStop: "30 minutes" });
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* ### Running commands
|
|
92
|
+
* `exec` runs to completion and returns `{ exitCode, stdout, stderr }`; a
|
|
93
|
+
* non-zero exit is returned, not failed. A string runs in a shell, an
|
|
94
|
+
* array runs its arguments quoted.
|
|
95
|
+
*
|
|
96
|
+
* **Example:** A shell line, an argument vector, stdin and a timeout
|
|
97
|
+
* ```typescript
|
|
98
|
+
* const test = yield* sandbox.exec("npm test", { cwd: "/home/user/app" });
|
|
99
|
+
* const version = yield* sandbox.exec(["node", "--version"]);
|
|
100
|
+
* const sorted = yield* sandbox.exec("sort", { stdin: "b\na\n" });
|
|
101
|
+
* const slow = yield* sandbox.exec("make", { timeout: "2 minutes" }); // Boat.ExecTimedOut
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* **Example:** Start a server in the background and stream its output
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const server = yield* sandbox.start("npm run dev", { cwd: "/home/user/app" });
|
|
107
|
+
* yield* server.output.pipe(
|
|
108
|
+
* Stream.take(5),
|
|
109
|
+
* Stream.runForEach((chunk) => Effect.log(new TextDecoder().decode(chunk.data))),
|
|
110
|
+
* );
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* ### Calling a port
|
|
114
|
+
* **Example:** Proxy the Worker's request to port 3000
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const port = yield* sandbox.getTcpPort(3000);
|
|
117
|
+
* return yield* port.fetch(yield* HttpServerRequest);
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* ### Forking and snapshots
|
|
121
|
+
* **Example:** Save a sandbox and fork it
|
|
122
|
+
* ```typescript
|
|
123
|
+
* yield* sandbox.stop(); // stopping snapshots it
|
|
124
|
+
* const copy = yield* sandbox.fork({ autoStop: "10 minutes" });
|
|
125
|
+
* yield* sandbox.resume();
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @binding
|
|
129
|
+
* @product Sandbox
|
|
130
|
+
*/
|
|
131
|
+
export interface CreateSandbox extends Binding.Service<CreateSandbox, "Boat.CreateSandbox", (base?: Snapshot) => Effect.Effect<SandboxesClient>> {
|
|
132
|
+
}
|
|
133
|
+
export declare const CreateSandbox: CreateSandbox;
|
|
134
|
+
//# sourceMappingURL=CreateSandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateSandbox.d.ts","sources":["../src/CreateSandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,IAAI,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,YAAY,CAAC,GAAG;IAC3F;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;CAC5C,CAAC;AAEF,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;CACnD;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,MAAM,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC9F,uCAAuC;IACvC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAClF,2BAA2B;IAC3B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC/E,8CAA8C;IAC9C,IAAI,CACF,OAAO,CAAC,EAAE,oBAAoB,GAC7B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IACzE;;;OAGG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,MAAM,WAAW,aAAc,SAAQ,OAAO,CAAC,OAAO,CACpD,aAAa,EACb,oBAAoB,EACpB,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CACpD;CAAG;AAEJ,eAAO,MAAM,aAAa,eAAuD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateSandbox.js","sources":["../src/CreateSandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AA6I3C,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAgB,oBAAoB,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import { CreateSandbox } from "./CreateSandbox.ts";
|
|
3
|
+
/**
|
|
4
|
+
* HTTP implementation of {@link CreateSandbox}: Boat's REST API with the
|
|
5
|
+
* host's own key. Works on any host.
|
|
6
|
+
*
|
|
7
|
+
* ### Provide the layer
|
|
8
|
+
* **Example:** On a Cloudflare Worker
|
|
9
|
+
* ```typescript
|
|
10
|
+
* Effect.gen(function* () {
|
|
11
|
+
* const sandboxes = yield* Boat.CreateSandbox();
|
|
12
|
+
* // ...
|
|
13
|
+
* }).pipe(Effect.provide(Boat.CreateSandboxHttp))
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @layer
|
|
17
|
+
* @product Sandbox
|
|
18
|
+
* @provides Boat.CreateSandbox
|
|
19
|
+
*/
|
|
20
|
+
export declare const CreateSandboxHttp: Layer.Layer<CreateSandbox, never, never>;
|
|
21
|
+
//# sourceMappingURL=CreateSandboxHttp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateSandboxHttp.d.ts","sources":["../src/CreateSandboxHttp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,OAAO,EAAE,aAAa,EAAmD,MAAM,oBAAoB,CAAC;AAuBpG;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,0CAmE7B,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as boat from "@distilled.cloud/boat-dev/boat";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Stream from "effect/Stream";
|
|
5
|
+
import { bindBoat, bindOwnerLabels } from "./BoatBindings.js";
|
|
6
|
+
import { CreateSandbox } from "./CreateSandbox.js";
|
|
7
|
+
import { BoatUnexpectedResponse } from "./Errors.js";
|
|
8
|
+
import { labelFilter } from "./internal/Ownership.js";
|
|
9
|
+
import { makeSandboxHandle, ttlOf } from "./internal/SandboxHandle.js";
|
|
10
|
+
/** What a `CreateSandbox` host may do: work in sandboxes, and delete only its own. */
|
|
11
|
+
const CREATE_SANDBOX_ACTIONS = [
|
|
12
|
+
"sandbox.create",
|
|
13
|
+
"sandbox.read",
|
|
14
|
+
"sandbox.update",
|
|
15
|
+
"sandbox.stop",
|
|
16
|
+
"sandbox.resume",
|
|
17
|
+
"sandbox.fork",
|
|
18
|
+
"sandbox.delete-own",
|
|
19
|
+
"exec",
|
|
20
|
+
"file.read",
|
|
21
|
+
"file.write",
|
|
22
|
+
"host",
|
|
23
|
+
"snapshot.read",
|
|
24
|
+
"snapshot.write",
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* HTTP implementation of {@link CreateSandbox}: Boat's REST API with the
|
|
28
|
+
* host's own key. Works on any host.
|
|
29
|
+
*
|
|
30
|
+
* ### Provide the layer
|
|
31
|
+
* **Example:** On a Cloudflare Worker
|
|
32
|
+
* ```typescript
|
|
33
|
+
* Effect.gen(function* () {
|
|
34
|
+
* const sandboxes = yield* Boat.CreateSandbox();
|
|
35
|
+
* // ...
|
|
36
|
+
* }).pipe(Effect.provide(Boat.CreateSandboxHttp))
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @layer
|
|
40
|
+
* @product Sandbox
|
|
41
|
+
* @provides Boat.CreateSandbox
|
|
42
|
+
*/
|
|
43
|
+
export const CreateSandboxHttp = Layer.succeed(CreateSandbox, Effect.fn("Boat.CreateSandbox")(function* (base) {
|
|
44
|
+
const auth = yield* bindBoat("BoatSandboxesKey", "BOAT_SANDBOXES_KEY", {
|
|
45
|
+
actions: CREATE_SANDBOX_ACTIONS,
|
|
46
|
+
});
|
|
47
|
+
const owner = yield* bindOwnerLabels;
|
|
48
|
+
const baseName = base === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: yield* base.name.asEffect();
|
|
51
|
+
const handleOf = (sandbox) => makeSandboxHandle(auth, sandbox, owner);
|
|
52
|
+
const client = {
|
|
53
|
+
create: Effect.fn("Boat.CreateSandbox.create")(function* (createOptions = {}) {
|
|
54
|
+
const { autoStop, ...options } = createOptions;
|
|
55
|
+
const from = options.from ?? (baseName === undefined ? undefined : yield* baseName);
|
|
56
|
+
const created = yield* auth.authorize(boat.createSandbox({
|
|
57
|
+
...options,
|
|
58
|
+
...(autoStop === undefined ? {} : { ttlSeconds: ttlOf(autoStop) }),
|
|
59
|
+
...(from === undefined ? {} : { from }),
|
|
60
|
+
labels: { ...options.labels, ...(yield* owner) },
|
|
61
|
+
}));
|
|
62
|
+
return handleOf(created.sandbox);
|
|
63
|
+
}),
|
|
64
|
+
connect: Effect.fn("Boat.CreateSandbox.connect")(function* (sandboxId) {
|
|
65
|
+
const got = yield* auth.authorize(boat.getSandbox({ sandboxId }));
|
|
66
|
+
if (got.sandbox === undefined) {
|
|
67
|
+
return yield* new BoatUnexpectedResponse({
|
|
68
|
+
message: `Boat returned no sandbox for ${sandboxId}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return handleOf(got.sandbox);
|
|
72
|
+
}),
|
|
73
|
+
get: Effect.fn("Boat.CreateSandbox.get")(function* (sandboxId) {
|
|
74
|
+
const got = yield* auth.authorize(boat.getSandbox({ sandboxId }));
|
|
75
|
+
if (got.sandbox === undefined) {
|
|
76
|
+
return yield* new BoatUnexpectedResponse({
|
|
77
|
+
message: `Boat returned no sandbox for ${sandboxId}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return got.sandbox;
|
|
81
|
+
}),
|
|
82
|
+
list: Effect.fn("Boat.CreateSandbox.list")(function* (options) {
|
|
83
|
+
const all = yield* auth.authorize(Stream.runCollect(boat.listSandboxes.items({
|
|
84
|
+
...(options?.labels === undefined ? {} : { label: labelFilter(options.labels) }),
|
|
85
|
+
...(options?.state === undefined ? {} : { state: options.state.join(",") }),
|
|
86
|
+
})));
|
|
87
|
+
return Array.from(all);
|
|
88
|
+
}),
|
|
89
|
+
delete: (sandboxId) => auth.authorize(boat.deleteSandbox({ sandboxId, xAsciiConfirmDelete: sandboxId })).pipe(Effect.asVoid, Effect.catchTag("NotFound", () => Effect.void)),
|
|
90
|
+
};
|
|
91
|
+
return client;
|
|
92
|
+
}));
|
|
93
|
+
//# sourceMappingURL=CreateSandboxHttp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateSandboxHttp.js","sources":["../src/CreateSandboxHttp.ts"],"names":[],"mappings":"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"}
|