@alchemy.run/daytona 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 (274) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +76 -4
  3. package/lib/ApiKey.d.ts +99 -0
  4. package/lib/ApiKey.d.ts.map +1 -0
  5. package/lib/ApiKey.js +152 -0
  6. package/lib/ApiKey.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +55 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +280 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Bootstrap.d.ts +8 -0
  12. package/lib/Bootstrap.d.ts.map +1 -0
  13. package/lib/Bootstrap.js +28 -0
  14. package/lib/Bootstrap.js.map +1 -0
  15. package/lib/ConnectSandbox.d.ts +72 -0
  16. package/lib/ConnectSandbox.d.ts.map +1 -0
  17. package/lib/ConnectSandbox.js +3 -0
  18. package/lib/ConnectSandbox.js.map +1 -0
  19. package/lib/ConnectSandboxHttp.d.ts +5 -0
  20. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  21. package/lib/ConnectSandboxHttp.js +22 -0
  22. package/lib/ConnectSandboxHttp.js.map +1 -0
  23. package/lib/Contributions.d.ts +7 -0
  24. package/lib/Contributions.d.ts.map +1 -0
  25. package/lib/Contributions.js +6 -0
  26. package/lib/Contributions.js.map +1 -0
  27. package/lib/CreateSandbox.d.ts +86 -0
  28. package/lib/CreateSandbox.d.ts.map +1 -0
  29. package/lib/CreateSandbox.js +3 -0
  30. package/lib/CreateSandbox.js.map +1 -0
  31. package/lib/CreateSandboxHttp.d.ts +5 -0
  32. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  33. package/lib/CreateSandboxHttp.js +65 -0
  34. package/lib/CreateSandboxHttp.js.map +1 -0
  35. package/lib/CreateSecret.d.ts +56 -0
  36. package/lib/CreateSecret.d.ts.map +1 -0
  37. package/lib/CreateSecret.js +3 -0
  38. package/lib/CreateSecret.js.map +1 -0
  39. package/lib/CreateSecretHttp.d.ts +5 -0
  40. package/lib/CreateSecretHttp.d.ts.map +1 -0
  41. package/lib/CreateSecretHttp.js +39 -0
  42. package/lib/CreateSecretHttp.js.map +1 -0
  43. package/lib/CreateSnapshot.d.ts +80 -0
  44. package/lib/CreateSnapshot.d.ts.map +1 -0
  45. package/lib/CreateSnapshot.js +3 -0
  46. package/lib/CreateSnapshot.js.map +1 -0
  47. package/lib/CreateSnapshotHttp.d.ts +5 -0
  48. package/lib/CreateSnapshotHttp.d.ts.map +1 -0
  49. package/lib/CreateSnapshotHttp.js +47 -0
  50. package/lib/CreateSnapshotHttp.js.map +1 -0
  51. package/lib/CreateVolume.d.ts +81 -0
  52. package/lib/CreateVolume.d.ts.map +1 -0
  53. package/lib/CreateVolume.js +10 -0
  54. package/lib/CreateVolume.js.map +1 -0
  55. package/lib/CreateVolumeHttp.d.ts +5 -0
  56. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  57. package/lib/CreateVolumeHttp.js +31 -0
  58. package/lib/CreateVolumeHttp.js.map +1 -0
  59. package/lib/Credentials.d.ts +9 -0
  60. package/lib/Credentials.d.ts.map +1 -0
  61. package/lib/Credentials.js +18 -0
  62. package/lib/Credentials.js.map +1 -0
  63. package/lib/DaytonaBindings.d.ts +51 -0
  64. package/lib/DaytonaBindings.d.ts.map +1 -0
  65. package/lib/DaytonaBindings.js +61 -0
  66. package/lib/DaytonaBindings.js.map +1 -0
  67. package/lib/IdentityProvider.d.ts +80 -0
  68. package/lib/IdentityProvider.d.ts.map +1 -0
  69. package/lib/IdentityProvider.js +111 -0
  70. package/lib/IdentityProvider.js.map +1 -0
  71. package/lib/MountVolume.d.ts +86 -0
  72. package/lib/MountVolume.d.ts.map +1 -0
  73. package/lib/MountVolume.js +27 -0
  74. package/lib/MountVolume.js.map +1 -0
  75. package/lib/OAuthClient.d.ts +14 -0
  76. package/lib/OAuthClient.d.ts.map +1 -0
  77. package/lib/OAuthClient.js +23 -0
  78. package/lib/OAuthClient.js.map +1 -0
  79. package/lib/Organization.d.ts +40 -0
  80. package/lib/Organization.d.ts.map +1 -0
  81. package/lib/Organization.js +80 -0
  82. package/lib/Organization.js.map +1 -0
  83. package/lib/OrganizationInvitation.d.ts +70 -0
  84. package/lib/OrganizationInvitation.d.ts.map +1 -0
  85. package/lib/OrganizationInvitation.js +109 -0
  86. package/lib/OrganizationInvitation.js.map +1 -0
  87. package/lib/OrganizationMember.d.ts +86 -0
  88. package/lib/OrganizationMember.d.ts.map +1 -0
  89. package/lib/OrganizationMember.js +109 -0
  90. package/lib/OrganizationMember.js.map +1 -0
  91. package/lib/OrganizationOtelConfig.d.ts +59 -0
  92. package/lib/OrganizationOtelConfig.d.ts.map +1 -0
  93. package/lib/OrganizationOtelConfig.js +87 -0
  94. package/lib/OrganizationOtelConfig.js.map +1 -0
  95. package/lib/OrganizationRole.d.ts +66 -0
  96. package/lib/OrganizationRole.d.ts.map +1 -0
  97. package/lib/OrganizationRole.js +106 -0
  98. package/lib/OrganizationRole.js.map +1 -0
  99. package/lib/OrganizationSettings.d.ts +76 -0
  100. package/lib/OrganizationSettings.d.ts.map +1 -0
  101. package/lib/OrganizationSettings.js +107 -0
  102. package/lib/OrganizationSettings.js.map +1 -0
  103. package/lib/Providers.d.ts +14 -0
  104. package/lib/Providers.d.ts.map +1 -0
  105. package/lib/Providers.js +53 -0
  106. package/lib/Providers.js.map +1 -0
  107. package/lib/ReadAuditLogs.d.ts +30 -0
  108. package/lib/ReadAuditLogs.d.ts.map +1 -0
  109. package/lib/ReadAuditLogs.js +17 -0
  110. package/lib/ReadAuditLogs.js.map +1 -0
  111. package/lib/ReadOrganization.d.ts +39 -0
  112. package/lib/ReadOrganization.d.ts.map +1 -0
  113. package/lib/ReadOrganization.js +39 -0
  114. package/lib/ReadOrganization.js.map +1 -0
  115. package/lib/ReadUsage.d.ts +31 -0
  116. package/lib/ReadUsage.d.ts.map +1 -0
  117. package/lib/ReadUsage.js +17 -0
  118. package/lib/ReadUsage.js.map +1 -0
  119. package/lib/Region.d.ts +78 -0
  120. package/lib/Region.d.ts.map +1 -0
  121. package/lib/Region.js +169 -0
  122. package/lib/Region.js.map +1 -0
  123. package/lib/Registry.d.ts +84 -0
  124. package/lib/Registry.d.ts.map +1 -0
  125. package/lib/Registry.js +97 -0
  126. package/lib/Registry.js.map +1 -0
  127. package/lib/Runner.d.ts +66 -0
  128. package/lib/Runner.d.ts.map +1 -0
  129. package/lib/Runner.js +116 -0
  130. package/lib/Runner.js.map +1 -0
  131. package/lib/Sandbox.d.ts +376 -0
  132. package/lib/Sandbox.d.ts.map +1 -0
  133. package/lib/Sandbox.js +565 -0
  134. package/lib/Sandbox.js.map +1 -0
  135. package/lib/SandboxClient.d.ts +258 -0
  136. package/lib/SandboxClient.d.ts.map +1 -0
  137. package/lib/SandboxClient.js +233 -0
  138. package/lib/SandboxClient.js.map +1 -0
  139. package/lib/Secret.d.ts +93 -0
  140. package/lib/Secret.d.ts.map +1 -0
  141. package/lib/Secret.js +130 -0
  142. package/lib/Secret.js.map +1 -0
  143. package/lib/Snapshot.d.ts +327 -0
  144. package/lib/Snapshot.d.ts.map +1 -0
  145. package/lib/Snapshot.js +447 -0
  146. package/lib/Snapshot.js.map +1 -0
  147. package/lib/Volume.d.ts +99 -0
  148. package/lib/Volume.d.ts.map +1 -0
  149. package/lib/Volume.js +152 -0
  150. package/lib/Volume.js.map +1 -0
  151. package/lib/WarmPool.d.ts +61 -0
  152. package/lib/WarmPool.d.ts.map +1 -0
  153. package/lib/WarmPool.js +81 -0
  154. package/lib/WarmPool.js.map +1 -0
  155. package/lib/Webhooks.d.ts +58 -0
  156. package/lib/Webhooks.d.ts.map +1 -0
  157. package/lib/Webhooks.js +75 -0
  158. package/lib/Webhooks.js.map +1 -0
  159. package/lib/index.d.ts +37 -0
  160. package/lib/index.d.ts.map +1 -0
  161. package/lib/index.js +36 -0
  162. package/lib/index.js.map +1 -0
  163. package/lib/internal/BuildContext.d.ts +16 -0
  164. package/lib/internal/BuildContext.d.ts.map +1 -0
  165. package/lib/internal/BuildContext.js +50 -0
  166. package/lib/internal/BuildContext.js.map +1 -0
  167. package/lib/internal/Declared.d.ts +29 -0
  168. package/lib/internal/Declared.d.ts.map +1 -0
  169. package/lib/internal/Declared.js +31 -0
  170. package/lib/internal/Declared.js.map +1 -0
  171. package/lib/internal/Derived.d.ts +40 -0
  172. package/lib/internal/Derived.d.ts.map +1 -0
  173. package/lib/internal/Derived.js +64 -0
  174. package/lib/internal/Derived.js.map +1 -0
  175. package/lib/internal/Errors.d.ts +4 -0
  176. package/lib/internal/Errors.d.ts.map +1 -0
  177. package/lib/internal/Errors.js +1 -0
  178. package/lib/internal/Errors.js.map +1 -0
  179. package/lib/internal/Labels.d.ts +20 -0
  180. package/lib/internal/Labels.d.ts.map +1 -0
  181. package/lib/internal/Labels.js +22 -0
  182. package/lib/internal/Labels.js.map +1 -0
  183. package/lib/internal/Names.d.ts +18 -0
  184. package/lib/internal/Names.d.ts.map +1 -0
  185. package/lib/internal/Names.js +25 -0
  186. package/lib/internal/Names.js.map +1 -0
  187. package/lib/internal/Organization.d.ts +27 -0
  188. package/lib/internal/Organization.d.ts.map +1 -0
  189. package/lib/internal/Organization.js +32 -0
  190. package/lib/internal/Organization.js.map +1 -0
  191. package/lib/internal/Process.d.ts +14 -0
  192. package/lib/internal/Process.d.ts.map +1 -0
  193. package/lib/internal/Process.js +15 -0
  194. package/lib/internal/Process.js.map +1 -0
  195. package/lib/internal/Program.d.ts +83 -0
  196. package/lib/internal/Program.d.ts.map +1 -0
  197. package/lib/internal/Program.js +169 -0
  198. package/lib/internal/Program.js.map +1 -0
  199. package/lib/internal/Ref.d.ts +14 -0
  200. package/lib/internal/Ref.d.ts.map +1 -0
  201. package/lib/internal/Ref.js +7 -0
  202. package/lib/internal/Ref.js.map +1 -0
  203. package/lib/internal/Scoped.d.ts +31 -0
  204. package/lib/internal/Scoped.d.ts.map +1 -0
  205. package/lib/internal/Scoped.js +48 -0
  206. package/lib/internal/Scoped.js.map +1 -0
  207. package/lib/internal/Tar.d.ts +13 -0
  208. package/lib/internal/Tar.d.ts.map +1 -0
  209. package/lib/internal/Tar.js +62 -0
  210. package/lib/internal/Tar.js.map +1 -0
  211. package/lib/internal/Toolbox.d.ts +11 -0
  212. package/lib/internal/Toolbox.d.ts.map +1 -0
  213. package/lib/internal/Toolbox.js +20 -0
  214. package/lib/internal/Toolbox.js.map +1 -0
  215. package/lib/internal/Wait.d.ts +49 -0
  216. package/lib/internal/Wait.d.ts.map +1 -0
  217. package/lib/internal/Wait.js +57 -0
  218. package/lib/internal/Wait.js.map +1 -0
  219. package/package.json +64 -3
  220. package/src/ApiKey.ts +221 -0
  221. package/src/AuthProvider.ts +406 -0
  222. package/src/Bootstrap.ts +55 -0
  223. package/src/ConnectSandbox.ts +76 -0
  224. package/src/ConnectSandboxHttp.ts +26 -0
  225. package/src/Contributions.ts +9 -0
  226. package/src/CreateSandbox.ts +107 -0
  227. package/src/CreateSandboxHttp.ts +97 -0
  228. package/src/CreateSecret.ts +73 -0
  229. package/src/CreateSecretHttp.ts +58 -0
  230. package/src/CreateSnapshot.ts +133 -0
  231. package/src/CreateSnapshotHttp.ts +77 -0
  232. package/src/CreateVolume.ts +104 -0
  233. package/src/CreateVolumeHttp.ts +50 -0
  234. package/src/Credentials.ts +37 -0
  235. package/src/DaytonaBindings.ts +111 -0
  236. package/src/IdentityProvider.ts +181 -0
  237. package/src/MountVolume.ts +122 -0
  238. package/src/OAuthClient.ts +27 -0
  239. package/src/Organization.ts +121 -0
  240. package/src/OrganizationInvitation.ts +174 -0
  241. package/src/OrganizationMember.ts +174 -0
  242. package/src/OrganizationOtelConfig.ts +138 -0
  243. package/src/OrganizationRole.ts +167 -0
  244. package/src/OrganizationSettings.ts +187 -0
  245. package/src/Providers.ts +93 -0
  246. package/src/ReadAuditLogs.ts +50 -0
  247. package/src/ReadOrganization.ts +83 -0
  248. package/src/ReadUsage.ts +51 -0
  249. package/src/Region.ts +226 -0
  250. package/src/Registry.ts +164 -0
  251. package/src/Runner.ts +166 -0
  252. package/src/Sandbox.ts +927 -0
  253. package/src/SandboxClient.ts +625 -0
  254. package/src/Secret.ts +203 -0
  255. package/src/Snapshot.ts +685 -0
  256. package/src/Volume.ts +229 -0
  257. package/src/WarmPool.ts +128 -0
  258. package/src/Webhooks.ts +121 -0
  259. package/src/globals.d.ts +2 -0
  260. package/src/index.ts +46 -0
  261. package/src/internal/BuildContext.ts +62 -0
  262. package/src/internal/Declared.ts +80 -0
  263. package/src/internal/Derived.ts +97 -0
  264. package/src/internal/Errors.ts +4 -0
  265. package/src/internal/Labels.ts +39 -0
  266. package/src/internal/Names.ts +37 -0
  267. package/src/internal/Organization.ts +40 -0
  268. package/src/internal/Process.ts +25 -0
  269. package/src/internal/Program.ts +239 -0
  270. package/src/internal/Ref.ts +15 -0
  271. package/src/internal/Scoped.ts +102 -0
  272. package/src/internal/Tar.ts +72 -0
  273. package/src/internal/Toolbox.ts +27 -0
  274. package/src/internal/Wait.ts +105 -0
@@ -0,0 +1,97 @@
1
+ import * as sandboxes from "@distilled.cloud/daytona/sandbox";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import { CreateSandbox, type CreateSandboxClient } from "./CreateSandbox.ts";
5
+ import { makeAccess, makeOwnerLabels, withOwnerLabels } from "./DaytonaBindings.ts";
6
+ import { definedLabels } from "./internal/Labels.ts";
7
+ import { waitForState } from "./internal/Wait.ts";
8
+ import { volumeIdOf } from "./MountVolume.ts";
9
+ import { makeSandboxClient } from "./SandboxClient.ts";
10
+ import type { Snapshot } from "./Snapshot.ts";
11
+
12
+ /** {@link CreateSandbox} over Daytona's HTTP API; works on any host. */
13
+ export const CreateSandboxHttp = Layer.effect(
14
+ CreateSandbox,
15
+ Effect.gen(function* () {
16
+ const access = yield* makeAccess;
17
+ return Effect.fn(function* (base?: Snapshot) {
18
+ const purpose = `${base?.LogicalId ?? ""}Sandboxes`;
19
+ const { authorize, organizationId } = yield* access(purpose, [
20
+ "write:sandboxes",
21
+ "delete:sandboxes",
22
+ ]);
23
+ const ownerLabels = yield* makeOwnerLabels;
24
+ const baseName = base ? yield* base.name : undefined;
25
+
26
+ const handle = (s: sandboxes.Sandbox2) =>
27
+ Effect.succeed(makeSandboxClient(s, organizationId, authorize));
28
+
29
+ const client: CreateSandboxClient = {
30
+ create: (input) =>
31
+ authorize(
32
+ Effect.gen(function* () {
33
+ const labels = withOwnerLabels(
34
+ input?.labels ? definedLabels(input.labels) : undefined,
35
+ yield* ownerLabels,
36
+ );
37
+ const snapshot = input?.snapshot ?? (baseName ? yield* baseName : undefined);
38
+ const { mounts, ...request } = input ?? {};
39
+ const volumes = mounts
40
+ ? yield* Effect.forEach(mounts, (m) =>
41
+ Effect.map(volumeIdOf(m.volume), (volumeId) => ({
42
+ volumeId,
43
+ mountPath: m.path,
44
+ subpath: m.subpath,
45
+ })),
46
+ )
47
+ : undefined;
48
+ const created = yield* sandboxes.createSandbox({
49
+ ...request,
50
+ snapshot,
51
+ labels,
52
+ volumes,
53
+ });
54
+ return yield* waitForState({
55
+ kind: "sandbox",
56
+ id: created.id,
57
+ observe: sandboxes.getSandbox({ sandboxIdOrName: created.id }),
58
+ want: ["started"],
59
+ fail: ["error", "build_failed", "destroyed"],
60
+ reason: (s) => s.errorReason,
61
+ budget: "5 minutes",
62
+ });
63
+ }),
64
+ ).pipe(Effect.flatMap(handle)),
65
+ get: (idOrName) =>
66
+ authorize(
67
+ sandboxes.getSandbox({ sandboxIdOrName: idOrName }).pipe(
68
+ Effect.map((s) =>
69
+ s.state === "destroyed" || s.state === "destroying" ? undefined : s,
70
+ ),
71
+ Effect.catchTag("SandboxNotFound", () => Effect.undefined),
72
+ ),
73
+ ).pipe(Effect.flatMap((s) => (s ? handle(s) : Effect.undefined))),
74
+ list: (input) =>
75
+ authorize(
76
+ Effect.gen(function* () {
77
+ const snapshots = input?.snapshots ?? (baseName ? [yield* baseName] : undefined);
78
+ const page = yield* sandboxes.listSandboxes({
79
+ ...input,
80
+ snapshots,
81
+ labels: input?.labels ? JSON.stringify(input.labels) : undefined,
82
+ });
83
+ return page.items;
84
+ }),
85
+ ),
86
+ delete: (idOrName) =>
87
+ authorize(
88
+ sandboxes.deleteSandbox({ sandboxIdOrName: idOrName }).pipe(
89
+ Effect.asVoid,
90
+ Effect.catchTag("SandboxNotFound", () => Effect.void),
91
+ ),
92
+ ),
93
+ };
94
+ return client;
95
+ });
96
+ }),
97
+ );
@@ -0,0 +1,73 @@
1
+ import type * as secrets from "@distilled.cloud/daytona/secret";
2
+ import * as Binding from "alchemy/Binding";
3
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
4
+ import type * as Effect from "effect/Effect";
5
+ import type * as Redacted from "effect/Redacted";
6
+ import type { SecretAttributes } from "./Secret.ts";
7
+
8
+ export interface CreateSecretClient {
9
+ /**
10
+ * Create the secret, or write a new value (and description and hosts)
11
+ * to the one with this name. Returns the same attributes as
12
+ * `Daytona.Secret`; pass `placeholder` into a sandbox's env or the
13
+ * secret's `name` in `secrets`.
14
+ */
15
+ put(input: {
16
+ readonly name: string;
17
+ readonly value: string | Redacted.Redacted<string>;
18
+ readonly description?: string;
19
+ readonly hosts?: string[];
20
+ }): Effect.Effect<
21
+ SecretAttributes,
22
+ secrets.CreateSecretError | secrets.UpdateSecretError | secrets.ListSecretsPaginatedError,
23
+ RuntimeContext
24
+ >;
25
+ /** The secret with this name, or `undefined`. Values are never returned. */
26
+ get(
27
+ name: string,
28
+ ): Effect.Effect<SecretAttributes | undefined, secrets.ListSecretsPaginatedError, RuntimeContext>;
29
+ /** Every secret in the organization. */
30
+ list(): Effect.Effect<SecretAttributes[], secrets.ListSecretsPaginatedError, RuntimeContext>;
31
+ /** Delete the secret with this name. Deleting a missing one succeeds. */
32
+ delete(
33
+ name: string,
34
+ ): Effect.Effect<
35
+ void,
36
+ secrets.DeleteSecretError | secrets.ListSecretsPaginatedError,
37
+ RuntimeContext
38
+ >;
39
+ }
40
+
41
+ /**
42
+ * Store secrets in Daytona at runtime: a credential per user, injected
43
+ * into that user's sandboxes without the sandbox (or an agent in it)
44
+ * ever seeing the value. Daytona swaps the placeholder for the value only
45
+ * on requests to the secret's `hosts`.
46
+ *
47
+ * The host gets its own Daytona API key with `manage:secrets`. Secrets
48
+ * have no labels, so each one it creates carries `stack/stage/id` in its
49
+ * description. They are not deleted when the stack is destroyed: list them
50
+ * (`list()`) and match the description to clean them up.
51
+ *
52
+ * ### A Credential per User
53
+ * **Example:** The user's GitHub token, only sent to GitHub's API
54
+ * ```typescript
55
+ * const secrets = yield* Daytona.CreateSecret();
56
+ * const github = yield* secrets.put({
57
+ * name: `github-${user}`,
58
+ * value: token,
59
+ * hosts: ["api.github.com"],
60
+ * });
61
+ * const sandbox = yield* sandboxes.create({ secrets: [{ GITHUB_TOKEN: github.name }] });
62
+ * ```
63
+ *
64
+ * @binding
65
+ * @product Secret
66
+ */
67
+ export interface CreateSecret extends Binding.Service<
68
+ CreateSecret,
69
+ "Daytona.CreateSecret",
70
+ () => Effect.Effect<CreateSecretClient>
71
+ > {}
72
+
73
+ export const CreateSecret = Binding.Service<CreateSecret>("Daytona.CreateSecret");
@@ -0,0 +1,58 @@
1
+ import * as secrets from "@distilled.cloud/daytona/secret";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Stream from "effect/Stream";
5
+ import { CreateSecret, type CreateSecretClient } from "./CreateSecret.ts";
6
+ import { makeAccess, makeOwnerLabels, ownerDescription } from "./DaytonaBindings.ts";
7
+ import { ensureSecret, findSecretByName, toSecretAttributes } from "./Secret.ts";
8
+
9
+ /** {@link CreateSecret} over Daytona's HTTP API; works on any host. */
10
+ export const CreateSecretHttp = Layer.effect(
11
+ CreateSecret,
12
+ Effect.gen(function* () {
13
+ const access = yield* makeAccess;
14
+ return Effect.fn(function* () {
15
+ const { authorize } = yield* access("Secrets", ["manage:secrets"]);
16
+ // Secrets have no labels, so the owner goes in the description.
17
+ const marker = Effect.map(yield* makeOwnerLabels, ownerDescription);
18
+ const client: CreateSecretClient = {
19
+ put: (input) =>
20
+ authorize(
21
+ Effect.gen(function* () {
22
+ const mark = yield* marker;
23
+ return yield* ensureSecret({
24
+ name: input.name,
25
+ value: input.value,
26
+ description: input.description ? `${input.description} (${mark})` : mark,
27
+ hosts: input.hosts,
28
+ });
29
+ }),
30
+ ),
31
+ get: (name) =>
32
+ authorize(
33
+ findSecretByName(name).pipe(
34
+ Effect.map((s) => (s ? toSecretAttributes(s, "") : undefined)),
35
+ ),
36
+ ),
37
+ list: () =>
38
+ authorize(
39
+ secrets.listSecretsPaginated.items({}).pipe(
40
+ Stream.runCollect,
41
+ Effect.map((all) => Array.from(all, (s) => toSecretAttributes(s, ""))),
42
+ ),
43
+ ),
44
+ delete: (name) =>
45
+ authorize(
46
+ Effect.gen(function* () {
47
+ const found = yield* findSecretByName(name);
48
+ if (found === undefined) return;
49
+ yield* secrets
50
+ .deleteSecret({ secretId: found.id })
51
+ .pipe(Effect.catchTag("SecretNotFound", () => Effect.void));
52
+ }),
53
+ ),
54
+ };
55
+ return client;
56
+ });
57
+ }),
58
+ );
@@ -0,0 +1,133 @@
1
+ import type { UnsupportedDockerfile } from "@alchemy.run/docker/BuildPlan";
2
+ import type { DockerfileSyntaxError } from "@alchemy.run/docker/DockerfileParser";
3
+ import type * as sandboxes from "@distilled.cloud/daytona/sandbox";
4
+ import type * as snapshots from "@distilled.cloud/daytona/snapshots";
5
+ import * as Binding from "alchemy/Binding";
6
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
7
+ import type * as Effect from "effect/Effect";
8
+ import type { NameOutsidePrefix } from "./CreateVolume.ts";
9
+ import type { ProcessError } from "./internal/Errors.ts";
10
+ import type { StateFailed, StateTimeout } from "./internal/Wait.ts";
11
+ import type {
12
+ ResolvedSnapshotSource,
13
+ SnapshotAttributes,
14
+ SnapshotContextUnsupported,
15
+ SnapshotImageTagInvalid,
16
+ SnapshotRunFailed,
17
+ } from "./Snapshot.ts";
18
+
19
+ /** `create` input: a name and one source, as `Daytona.Snapshot` takes them (Dockerfile as content). */
20
+ export interface CreateSnapshotInput extends Omit<ResolvedSnapshotSource, "contextHashes"> {
21
+ readonly name: string;
22
+ }
23
+
24
+ export interface CreateSnapshotClient {
25
+ /**
26
+ * Build a snapshot and wait until it is active: pull `image`, build
27
+ * `dockerfile` on Daytona, or start a sandbox from `from`, `run` the
28
+ * commands and capture it. Returns the same attributes as
29
+ * `Daytona.Snapshot`.
30
+ */
31
+ create(
32
+ input: CreateSnapshotInput,
33
+ ): Effect.Effect<
34
+ SnapshotAttributes,
35
+ | snapshots.CreateSnapshotError
36
+ | snapshots.GetSnapshotError
37
+ | snapshots.ActivateSnapshotError
38
+ | sandboxes.CreateSandboxError
39
+ | sandboxes.GetSandboxError
40
+ | sandboxes.CreateSandboxSnapshotError
41
+ | ProcessError
42
+ | SnapshotRunFailed
43
+ | SnapshotImageTagInvalid
44
+ | SnapshotContextUnsupported
45
+ | UnsupportedDockerfile
46
+ | DockerfileSyntaxError
47
+ | StateTimeout
48
+ | StateFailed
49
+ | NameOutsidePrefix,
50
+ RuntimeContext
51
+ >;
52
+ /** The snapshot with this id or name, or `undefined`. */
53
+ get(
54
+ idOrName: string,
55
+ ): Effect.Effect<SnapshotAttributes | undefined, snapshots.GetSnapshotError, RuntimeContext>;
56
+ /** Snapshots matching Daytona's list filters. */
57
+ list(
58
+ input?: Omit<snapshots.GetAllSnapshotsRequest, "xDaytonaOrganizationID" | "page">,
59
+ ): Effect.Effect<SnapshotAttributes[], snapshots.GetAllSnapshotsError, RuntimeContext>;
60
+ /** Make a deactivated snapshot usable again and wait until it is active. */
61
+ activate(
62
+ idOrName: string,
63
+ ): Effect.Effect<
64
+ SnapshotAttributes,
65
+ snapshots.GetSnapshotError | snapshots.ActivateSnapshotError | StateTimeout | StateFailed,
66
+ RuntimeContext
67
+ >;
68
+ /** Deactivate a snapshot; sandboxes can't be created from it until it is activated. */
69
+ deactivate(
70
+ idOrName: string,
71
+ ): Effect.Effect<void, snapshots.DeactivateSnapshotError, RuntimeContext>;
72
+ /** A URL streaming the snapshot's build logs. */
73
+ buildLogsUrl(
74
+ idOrName: string,
75
+ ): Effect.Effect<snapshots.Url, snapshots.GetSnapshotBuildLogsUrlError, RuntimeContext>;
76
+ /** Delete a snapshot and wait until it is gone. Deleting a missing one succeeds. */
77
+ delete(
78
+ idOrName: string,
79
+ ): Effect.Effect<
80
+ void,
81
+ snapshots.RemoveSnapshotError | snapshots.GetSnapshotError | StateTimeout,
82
+ RuntimeContext
83
+ >;
84
+ }
85
+
86
+ /**
87
+ * Options for {@link CreateSnapshot}. Snapshots have only a name, no labels
88
+ * or description, so a `prefix` is how you find the ones a binding
89
+ * created: the binding refuses names that don't start with it.
90
+ */
91
+ export interface CreateSnapshotOptions {
92
+ /** Every name the binding creates must start with this. */
93
+ readonly prefix?: string;
94
+ }
95
+
96
+ /**
97
+ * Build Daytona snapshots at runtime: a warm base per repository, built
98
+ * when it's first needed, that later sandboxes start from. To capture a
99
+ * sandbox you already have, use `sandbox.createSandboxSnapshot` on its
100
+ * handle.
101
+ *
102
+ * The host gets its own Daytona API key with `write:snapshots`,
103
+ * `delete:snapshots` and, for `from` builds, `write:sandboxes` and
104
+ * `delete:sandboxes`. Snapshots have only a name, so the `prefix` you give
105
+ * is how you find the ones it created. They are not deleted when the stack
106
+ * is destroyed: `list()` and delete the names with your prefix to clean
107
+ * them up.
108
+ *
109
+ * ### A Warm Base per Repository
110
+ * **Example:** Build it on first use, then start sandboxes from it
111
+ * ```typescript
112
+ * const images = yield* Daytona.CreateSnapshot({ prefix: "repo-" });
113
+ * const name = `repo-${owner}-${repo}`;
114
+ * const base =
115
+ * (yield* images.get(name)) ??
116
+ * (yield* images.create({
117
+ * name,
118
+ * from: "daytonaio/sandbox:0.4.3",
119
+ * run: [`git clone https://github.com/${owner}/${repo} /home/daytona/repo`, "cd /home/daytona/repo && npm ci"],
120
+ * }));
121
+ * const sandbox = yield* sandboxes.create({ snapshot: base.name });
122
+ * ```
123
+ *
124
+ * @binding
125
+ * @product Snapshot
126
+ */
127
+ export interface CreateSnapshot extends Binding.Service<
128
+ CreateSnapshot,
129
+ "Daytona.CreateSnapshot",
130
+ (options?: CreateSnapshotOptions) => Effect.Effect<CreateSnapshotClient>
131
+ > {}
132
+
133
+ export const CreateSnapshot = Binding.Service<CreateSnapshot>("Daytona.CreateSnapshot");
@@ -0,0 +1,77 @@
1
+ import * as snapshots from "@distilled.cloud/daytona/snapshots";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Stream from "effect/Stream";
5
+ import {
6
+ CreateSnapshot,
7
+ type CreateSnapshotClient,
8
+ type CreateSnapshotOptions,
9
+ } from "./CreateSnapshot.ts";
10
+ import { NameOutsidePrefix } from "./CreateVolume.ts";
11
+ import { makeAccess, makeOwnerLabels } from "./DaytonaBindings.ts";
12
+ import {
13
+ buildSnapshot,
14
+ checkRuntimeDockerfile,
15
+ deleteSnapshot,
16
+ getSnapshot,
17
+ toSnapshotAttributes,
18
+ waitForSnapshot,
19
+ } from "./Snapshot.ts";
20
+
21
+ /** {@link CreateSnapshot} over Daytona's HTTP API; works on any host. */
22
+ export const CreateSnapshotHttp = Layer.effect(
23
+ CreateSnapshot,
24
+ Effect.gen(function* () {
25
+ const access = yield* makeAccess;
26
+ return Effect.fn(function* (options?: CreateSnapshotOptions) {
27
+ const { authorize } = yield* access("Snapshots", [
28
+ "write:snapshots",
29
+ "delete:snapshots",
30
+ "write:sandboxes",
31
+ "delete:sandboxes",
32
+ ]);
33
+ // A snapshot has only a name: `prefix` scopes it. The owner labels go
34
+ // on the sandbox a warm base is captured from.
35
+ const labels = yield* makeOwnerLabels;
36
+ const prefix = options?.prefix;
37
+ const client: CreateSnapshotClient = {
38
+ create: ({ name, ...source }) =>
39
+ prefix !== undefined && !name.startsWith(prefix)
40
+ ? Effect.fail(new NameOutsidePrefix({ objectName: name, prefix }))
41
+ : authorize(
42
+ Effect.gen(function* () {
43
+ if (source.dockerfile !== undefined)
44
+ yield* checkRuntimeDockerfile(source.dockerfile);
45
+ const built = yield* buildSnapshot({
46
+ name,
47
+ source,
48
+ labels: yield* labels,
49
+ note: () => Effect.void,
50
+ });
51
+ return toSnapshotAttributes(built, "");
52
+ }),
53
+ ),
54
+ get: (idOrName) =>
55
+ authorize(
56
+ getSnapshot(idOrName).pipe(
57
+ Effect.map((s) => (s ? toSnapshotAttributes(s, "") : undefined)),
58
+ ),
59
+ ),
60
+ list: (input) =>
61
+ authorize(
62
+ snapshots.getAllSnapshots.items({ ...input }).pipe(
63
+ Stream.runCollect,
64
+ Effect.map((all) => Array.from(all, (s) => toSnapshotAttributes(s, ""))),
65
+ ),
66
+ ),
67
+ activate: (idOrName) =>
68
+ authorize(waitForSnapshot(idOrName).pipe(Effect.map((s) => toSnapshotAttributes(s, "")))),
69
+ deactivate: (idOrName) =>
70
+ authorize(snapshots.deactivateSnapshot({ id: idOrName }).pipe(Effect.asVoid)),
71
+ buildLogsUrl: (idOrName) => authorize(snapshots.getSnapshotBuildLogsUrl({ id: idOrName })),
72
+ delete: (idOrName) => authorize(deleteSnapshot(idOrName)),
73
+ };
74
+ return client;
75
+ });
76
+ }),
77
+ );
@@ -0,0 +1,104 @@
1
+ import type * as volumes from "@distilled.cloud/daytona/volumes";
2
+ import * as Binding from "alchemy/Binding";
3
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
4
+ import * as Data from "effect/Data";
5
+ import type * as Effect from "effect/Effect";
6
+ import type { StateFailed, StateTimeout } from "./internal/Wait.ts";
7
+ import type { VolumeAttributes } from "./Volume.ts";
8
+
9
+ /** A name outside the binding's `prefix`. */
10
+ export class NameOutsidePrefix extends Data.TaggedError("Daytona.NameOutsidePrefix")<{
11
+ readonly objectName: string;
12
+ readonly prefix: string;
13
+ }> {
14
+ override get message() {
15
+ return `${this.objectName} must start with ${this.prefix}: the binding only manages names with that prefix`;
16
+ }
17
+ }
18
+
19
+ export interface CreateVolumeClient {
20
+ /**
21
+ * The volume named `name`, created (and waited on until ready) if it
22
+ * doesn't exist. Returns the same attributes as `Daytona.Volume`.
23
+ */
24
+ getOrCreate(input: {
25
+ readonly name: string;
26
+ }): Effect.Effect<
27
+ VolumeAttributes,
28
+ | volumes.CreateVolumeError
29
+ | volumes.GetVolumeByNameError
30
+ | volumes.GetVolumeError
31
+ | StateTimeout
32
+ | StateFailed
33
+ | NameOutsidePrefix,
34
+ RuntimeContext
35
+ >;
36
+ /** The volume with this name, or `undefined`. */
37
+ get(
38
+ name: string,
39
+ ): Effect.Effect<VolumeAttributes | undefined, volumes.GetVolumeByNameError, RuntimeContext>;
40
+ /** Every volume in the organization. */
41
+ list(): Effect.Effect<VolumeAttributes[], volumes.ListVolumesError, RuntimeContext>;
42
+ /**
43
+ * Delete a volume. Returns once Daytona has accepted the delete; it
44
+ * removes the volume in the background. Fails with `VolumeInUse` while a
45
+ * sandbox mounts it: Daytona releases a volume about a minute after the
46
+ * sandbox that wrote to it is deleted, so retry later. Deleting a missing
47
+ * one succeeds.
48
+ */
49
+ delete(
50
+ volumeId: string,
51
+ ): Effect.Effect<
52
+ void,
53
+ volumes.DeleteVolumeError | volumes.GetVolumeError | StateTimeout,
54
+ RuntimeContext
55
+ >;
56
+ }
57
+
58
+ /**
59
+ * Options for {@link CreateVolume}. Volumes have only a name, no labels or
60
+ * description, so a `prefix` is how you find the ones a binding created:
61
+ * the binding refuses names that don't start with it.
62
+ */
63
+ export interface CreateVolumeOptions {
64
+ /** Every name the binding creates or gets must start with this. */
65
+ readonly prefix?: string;
66
+ }
67
+
68
+ /**
69
+ * Create, find and delete Daytona volumes at runtime: a volume per user,
70
+ * per project or per session, mounted into the sandboxes you create.
71
+ *
72
+ * The host gets its own Daytona API key with `read:volumes`,
73
+ * `write:volumes` and `delete:volumes`. Volumes have only a name, so the
74
+ * `prefix` you give is how you find the ones it created. They are not
75
+ * deleted when the stack is destroyed: `list()` and delete the names with
76
+ * your prefix to clean them up.
77
+ *
78
+ * ### Volume per User
79
+ * **Example:** Mount the user's volume as their home directory
80
+ * ```typescript
81
+ * const volumes = yield* Daytona.CreateVolume({ prefix: "user-" });
82
+ * const sandboxes = yield* Daytona.CreateSandbox(Base);
83
+ * return {
84
+ * fetch: Effect.gen(function* () {
85
+ * const home = yield* volumes.getOrCreate({ name: `user-${user}` });
86
+ * const sandbox = yield* sandboxes.create({
87
+ * mounts: [{ volume: home, path: "/home/daytona/work" }],
88
+ * });
89
+ * const ls = yield* sandbox.exec("ls /home/daytona/work");
90
+ * return yield* HttpServerResponse.json(ls);
91
+ * }),
92
+ * };
93
+ * ```
94
+ *
95
+ * @binding
96
+ * @product Volume
97
+ */
98
+ export interface CreateVolume extends Binding.Service<
99
+ CreateVolume,
100
+ "Daytona.CreateVolume",
101
+ (options?: CreateVolumeOptions) => Effect.Effect<CreateVolumeClient>
102
+ > {}
103
+
104
+ export const CreateVolume = Binding.Service<CreateVolume>("Daytona.CreateVolume");
@@ -0,0 +1,50 @@
1
+ import * as volumes from "@distilled.cloud/daytona/volumes";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import {
5
+ CreateVolume,
6
+ NameOutsidePrefix,
7
+ type CreateVolumeClient,
8
+ type CreateVolumeOptions,
9
+ } from "./CreateVolume.ts";
10
+ import { makeAccess } from "./DaytonaBindings.ts";
11
+ import { deleteVolumeById, ensureVolume, getVolumeByName, toVolumeAttributes } from "./Volume.ts";
12
+
13
+ /** {@link CreateVolume} over Daytona's HTTP API; works on any host. */
14
+ export const CreateVolumeHttp = Layer.effect(
15
+ CreateVolume,
16
+ Effect.gen(function* () {
17
+ const access = yield* makeAccess;
18
+ return Effect.fn(function* (options?: CreateVolumeOptions) {
19
+ const { authorize } = yield* access("Volumes", [
20
+ "read:volumes",
21
+ "write:volumes",
22
+ "delete:volumes",
23
+ ]);
24
+ // A volume has only a name: `prefix` scopes it.
25
+ const prefix = options?.prefix;
26
+ const client: CreateVolumeClient = {
27
+ getOrCreate: ({ name }) =>
28
+ prefix !== undefined && !name.startsWith(prefix)
29
+ ? Effect.fail(new NameOutsidePrefix({ objectName: name, prefix }))
30
+ : authorize(ensureVolume(name).pipe(Effect.map(toVolumeAttributes))),
31
+ get: (name) =>
32
+ authorize(
33
+ getVolumeByName(name).pipe(Effect.map((v) => (v ? toVolumeAttributes(v) : undefined))),
34
+ ),
35
+ list: () =>
36
+ authorize(
37
+ volumes
38
+ .listVolumes({})
39
+ .pipe(
40
+ Effect.map((all) =>
41
+ all.filter((v) => v.state !== "deleted").map(toVolumeAttributes),
42
+ ),
43
+ ),
44
+ ),
45
+ delete: (volumeId) => authorize(deleteVolumeById(volumeId, { wait: false })),
46
+ };
47
+ return client;
48
+ });
49
+ }),
50
+ );
@@ -0,0 +1,37 @@
1
+ import { ConfigError } from "@distilled.cloud/core/errors";
2
+ import {
3
+ Credentials,
4
+ DEFAULT_ANALYTICS_BASE_URL,
5
+ DEFAULT_TOOLBOX_BASE_URL,
6
+ } from "@distilled.cloud/daytona/Credentials";
7
+ import { lazyCredentials } from "alchemy/Auth/Resolve";
8
+ import * as Layer from "effect/Layer";
9
+ import { DAYTONA_AUTH_PROVIDER_NAME, type DaytonaResolvedCredentials } from "./AuthProvider.ts";
10
+
11
+ export {
12
+ Credentials,
13
+ CredentialsFromEnv,
14
+ DEFAULT_ANALYTICS_BASE_URL,
15
+ DEFAULT_API_BASE_URL,
16
+ DEFAULT_TOOLBOX_BASE_URL,
17
+ } from "@distilled.cloud/daytona/Credentials";
18
+
19
+ /**
20
+ * A `Credentials` layer that resolves Daytona credentials through the
21
+ * Alchemy AuthProvider for the current profile.
22
+ */
23
+ export const fromAuthProvider = () =>
24
+ Layer.effect(
25
+ Credentials,
26
+ lazyCredentials<DaytonaResolvedCredentials, ConfigError>()(
27
+ DAYTONA_AUTH_PROVIDER_NAME,
28
+ (creds) => ({
29
+ apiKey: creds.token,
30
+ apiBaseUrl: creds.apiBaseUrl,
31
+ analyticsBaseUrl: DEFAULT_ANALYTICS_BASE_URL,
32
+ toolboxBaseUrl: DEFAULT_TOOLBOX_BASE_URL,
33
+ organizationId: creds.organizationId,
34
+ }),
35
+ { expiresAt: (creds) => creds.expires },
36
+ ),
37
+ );