@alchemy.run/daytona 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.
Files changed (274) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +80 -2
  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 +79 -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,685 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as BuildPlan from "@alchemy.run/docker/BuildPlan";
3
+ import type { InlineDockerfile } from "@alchemy.run/docker/Dockerfile";
4
+ import * as DockerfileParser from "@alchemy.run/docker/DockerfileParser";
5
+ import {
6
+ computeStaticSourceHash,
7
+ makeContainerImageSource,
8
+ type DockerfileImageSource,
9
+ type ImageRegistryTarget,
10
+ } from "@alchemy.run/docker/ImageSource";
11
+ import * as dockerRegistry from "@distilled.cloud/daytona/docker_registry";
12
+ import * as sandboxes from "@distilled.cloud/daytona/sandbox";
13
+ import * as snapshots from "@distilled.cloud/daytona/snapshots";
14
+ import { Unowned } from "alchemy/AdoptPolicy";
15
+ import { isResolved } from "alchemy/Diff";
16
+ import * as Provider from "alchemy/Provider";
17
+ import { Resource } from "alchemy/Resource";
18
+ import * as Data from "effect/Data";
19
+ import * as Effect from "effect/Effect";
20
+ import type * as Redacted from "effect/Redacted";
21
+ import * as Stream from "effect/Stream";
22
+ import { uploadBuildContext } from "./internal/BuildContext.ts";
23
+ import { declarable } from "./internal/Declared.ts";
24
+ import { createInternalLabels } from "./internal/Labels.ts";
25
+ import { isOwnedName, resolveName } from "./internal/Names.ts";
26
+ import { runCommand } from "./internal/Process.ts";
27
+ import { resolved, type Ref } from "./internal/Ref.ts";
28
+ import { waitForState, waitUntilGone } from "./internal/Wait.ts";
29
+ import type { Providers } from "./Providers.ts";
30
+
31
+ /** A snapshot, by name. Accepts a `Daytona.Snapshot` or `{ name }`. */
32
+ export type SnapshotRef = { readonly name: string };
33
+
34
+ /** A private registry. Accepts a `Daytona.Registry` or `{ registryId }`. */
35
+ export type SnapshotRegistry = { readonly registryId: string };
36
+
37
+ export type GpuType = snapshots.GpuType;
38
+
39
+ export interface SnapshotBuild {
40
+ /** Build arguments, read by the Dockerfile's `ARG` instructions. */
41
+ args?: Record<string, string>;
42
+ /**
43
+ * BuildKit secrets, keyed by id; mount one in a `RUN` step with
44
+ * `--mount=type=secret,id=<id>`. They never enter the image or its hash.
45
+ * Daytona's builders can't receive secrets, so setting any builds locally.
46
+ */
47
+ secrets?: Record<string, Redacted.Redacted<string>>;
48
+ /**
49
+ * Build on Daytona's builders (`true`), or with your local Docker
50
+ * (`false`), pushing the image to Daytona's registry. Build locally to use
51
+ * build secrets, base images only your machine can pull, or your local
52
+ * layer cache.
53
+ * @default true, unless `secrets` are set
54
+ */
55
+ remote?: boolean;
56
+ }
57
+
58
+ export interface SnapshotProps {
59
+ /**
60
+ * Snapshot name, unique in the organization. Sandboxes are created from
61
+ * it by name. If omitted, a unique name is generated from the stack,
62
+ * stage and logical ID. Changing it replaces the snapshot.
63
+ */
64
+ name?: string;
65
+ /**
66
+ * An image to pull, with an explicit tag or digest (Daytona rejects
67
+ * `latest`, `lts` and `stable`). Pass a {@link Registry} as `registry`
68
+ * for private images. Exclusive with `dockerfile` and `from`.
69
+ */
70
+ image?: string;
71
+ /**
72
+ * A Dockerfile to build: a path (relative to the cwd), or inline content
73
+ * (`Dockerfile.inline`). Daytona builds it on its own builders, uploading
74
+ * the files it `COPY`s from {@link context}; no local Docker is needed.
75
+ * Set `build.remote: false` to build it with your local Docker instead.
76
+ * Changing the Dockerfile or any file it copies replaces the snapshot.
77
+ * Exclusive with `image` and `from`.
78
+ * @default `${context}/Dockerfile` when `context` is set
79
+ */
80
+ dockerfile?: string | InlineDockerfile;
81
+ /**
82
+ * Build context directory for a Dockerfile path. Only the files its
83
+ * `COPY`/`ADD` instructions reach are uploaded (after `.dockerignore`).
84
+ * Inline Dockerfiles have no context and can't `COPY` local files.
85
+ */
86
+ context?: string;
87
+ /** How the Dockerfile is built. */
88
+ build?: SnapshotBuild;
89
+ /**
90
+ * Build a warm base from another snapshot: start a sandbox from `from`,
91
+ * run `run` in it, and capture its filesystem as this snapshot. Use it to
92
+ * bake a cloned repository and installed dependencies into the snapshot
93
+ * sandboxes fork from. Exclusive with `image` and `dockerfile`.
94
+ */
95
+ from?: Ref<SnapshotRef>;
96
+ /**
97
+ * Shell commands run in order in the `from` sandbox before it is
98
+ * captured. A non-zero exit fails the deploy.
99
+ */
100
+ run?: string[];
101
+ /** Private registry to pull `image` (or a Dockerfile's base) from. */
102
+ registry?: Ref<SnapshotRegistry>;
103
+ /** The command a sandbox from this snapshot runs. */
104
+ entrypoint?: string[];
105
+ /** vCPUs for sandboxes from this snapshot. */
106
+ cpu?: number;
107
+ /** Memory in GiB. */
108
+ memory?: number;
109
+ /** Disk in GiB. */
110
+ disk?: number;
111
+ /** GPUs per sandbox. Billed per hour while a sandbox runs. */
112
+ gpu?: number;
113
+ /** Acceptable GPU types, in order of preference. */
114
+ gpuType?: GpuType[];
115
+ /** Region to build the snapshot in. */
116
+ regionId?: string;
117
+ /** Sandbox class (`container`, `linux-vm`, …). Dockerfile builds need `container`. */
118
+ sandboxClass?: snapshots.SandboxClass;
119
+ }
120
+
121
+ export interface SnapshotAttributes {
122
+ /** Snapshot ID. */
123
+ snapshotId: string;
124
+ /** Snapshot name; what sandboxes are created from. */
125
+ name: string;
126
+ /** State (`active` once usable). */
127
+ state: snapshots.SnapshotState;
128
+ /** The pulled or built image. */
129
+ imageName: string | undefined;
130
+ /** Internal image reference. */
131
+ ref: string | undefined;
132
+ /** vCPUs. */
133
+ cpu: number;
134
+ /** Memory in GiB. */
135
+ memory: number;
136
+ /** Disk in GiB. */
137
+ disk: number;
138
+ /** GPUs. */
139
+ gpu: number;
140
+ /** The sandbox this snapshot was captured from, for `from` snapshots. */
141
+ sourceSandboxId: string | undefined;
142
+ /** Hash of everything the snapshot was built from. */
143
+ buildHash: string;
144
+ }
145
+
146
+ export type Snapshot = Resource<
147
+ "Daytona.Snapshot",
148
+ SnapshotProps,
149
+ SnapshotAttributes,
150
+ never,
151
+ Providers,
152
+ SnapshotLookup
153
+ >;
154
+
155
+ /**
156
+ * Props for {@link Snapshot.lookup}: find a snapshot this stack
157
+ * doesn't manage by its name or id.
158
+ */
159
+ export interface SnapshotLookup {
160
+ /** Snapshot name or id. */
161
+ name: string;
162
+ }
163
+
164
+ /**
165
+ * A Daytona snapshot: the image sandboxes boot from, with its default
166
+ * size. Pull one from a registry, build one from a Dockerfile on
167
+ * Daytona's builders, or capture one from a sandbox after running setup
168
+ * commands in it (a warm base to fork from).
169
+ *
170
+ * Snapshots are immutable: changing what one is built from replaces it.
171
+ * Daytona deactivates a snapshot after two weeks without use; deploy
172
+ * reactivates it.
173
+ * @see https://www.daytona.io/docs/en/snapshots/
174
+ *
175
+ * ### Pulling an Image
176
+ * **Example:** A public image with a size
177
+ * ```typescript
178
+ * export const Node = Daytona.Snapshot("Node", {
179
+ * image: "node:22-bookworm",
180
+ * cpu: 2,
181
+ * memory: 4,
182
+ * disk: 10,
183
+ * });
184
+ * ```
185
+ *
186
+ * ### Building from a Dockerfile
187
+ * **Example:** An inline Dockerfile built by Daytona
188
+ * ```typescript
189
+ * export const Base = Daytona.Snapshot("Base", {
190
+ * dockerfile: Dockerfile.inline`
191
+ * FROM node:22-bookworm
192
+ * RUN apt-get update && apt-get install -y git ripgrep
193
+ * `,
194
+ * cpu: 2,
195
+ * memory: 4,
196
+ * });
197
+ * ```
198
+ *
199
+ * ### Building a Directory
200
+ * **Example:** A Dockerfile and the files it copies
201
+ * ```typescript
202
+ * export const App = Daytona.Snapshot("App", {
203
+ * context: "./sandbox",
204
+ * build: { args: { NODE_ENV: "production" } },
205
+ * });
206
+ * ```
207
+ *
208
+ * ### Building with Local Docker
209
+ * **Example:** A build that reads a private npm token
210
+ * ```typescript
211
+ * export const App = Daytona.Snapshot("App", {
212
+ * context: "./sandbox",
213
+ * build: {
214
+ * remote: false,
215
+ * secrets: { npmrc: Redacted.make(process.env.NPM_TOKEN!) },
216
+ * },
217
+ * });
218
+ * ```
219
+ *
220
+ * ### A Warm Base to Fork From
221
+ * **Example:** Clone and install once, then start every sandbox from it
222
+ * ```typescript
223
+ * export const Warm = Daytona.Snapshot("Warm", {
224
+ * from: Base,
225
+ * run: [
226
+ * "git clone https://github.com/alchemy-run/alchemy /home/daytona/alchemy",
227
+ * "cd /home/daytona/alchemy && pnpm install",
228
+ * ],
229
+ * });
230
+ * ```
231
+ *
232
+ * ### Looking up an existing snapshot
233
+ * `Snapshot.lookup` references a snapshot Alchemy doesn't manage (created in
234
+ * the Daytona dashboard or by another stack). It is read during plan/deploy,
235
+ * never created, updated or deleted.
236
+ * `buildHash` is empty: the image wasn't built by this stack.
237
+ *
238
+ * **Example:** Start a sandbox from an existing snapshot
239
+ * ```typescript
240
+ * const base = Daytona.Snapshot.lookup({ name: "python-ml" });
241
+ * ```
242
+ *
243
+ * @resource
244
+ * @product Snapshot
245
+ */
246
+ export const Snapshot = declarable<Snapshot>()(Resource<Snapshot>("Daytona.Snapshot"));
247
+
248
+ /**
249
+ * What Daytona's builders can build: any Dockerfile, for amd64. Daytona's
250
+ * build request has no field for build arguments, so the plan bakes them
251
+ * into `ARG` defaults, nor for BuildKit secrets, so the plan rejects them.
252
+ */
253
+ export const capabilities: BuildPlan.Capabilities = {
254
+ target: "Daytona.Snapshot",
255
+ buildArgs: false,
256
+ secrets: false,
257
+ platforms: ["linux/amd64"],
258
+ };
259
+
260
+ /** The snapshot props name more than one of `image`, `dockerfile` and `from`, or none. */
261
+ export class SnapshotSourceInvalid extends Data.TaggedError("Daytona.SnapshotSourceInvalid")<{
262
+ readonly message: string;
263
+ }> {}
264
+
265
+ /** A runtime build's Dockerfile copies local files, which only a deploy-time `context` can provide. */
266
+ export class SnapshotContextUnsupported extends Data.TaggedError(
267
+ "Daytona.SnapshotContextUnsupported",
268
+ )<{
269
+ readonly line: string;
270
+ }> {
271
+ override get message() {
272
+ return `A snapshot built at runtime can't copy local files (${this.line.trim()}). Declare it with Daytona.Snapshot and a \`context\`, or fetch the files in a RUN step.`;
273
+ }
274
+ }
275
+
276
+ /** A `run` command exited non-zero while building a snapshot from a sandbox. */
277
+ export class SnapshotRunFailed extends Data.TaggedError("Daytona.SnapshotRunFailed")<{
278
+ readonly command: string;
279
+ readonly exitCode: number;
280
+ readonly output: string;
281
+ }> {
282
+ override get message() {
283
+ return `\`${this.command}\` exited ${this.exitCode} while building the snapshot:\n${this.output.slice(-2000)}`;
284
+ }
285
+ }
286
+
287
+ /** The image has no tag, or a moving one Daytona rejects. */
288
+ export class SnapshotImageTagInvalid extends Data.TaggedError("Daytona.SnapshotImageTagInvalid")<{
289
+ readonly image: string;
290
+ }> {
291
+ override get message() {
292
+ return `Daytona needs an image with a fixed tag or digest; got ${this.image} (latest, lts and stable are rejected)`;
293
+ }
294
+ }
295
+
296
+ const sha256 = (value: unknown) =>
297
+ Effect.sync(() => createHash("sha256").update(JSON.stringify(value)).digest("hex"));
298
+
299
+ const refName = <T extends { readonly name: string }>(ref: Ref<T> | undefined) =>
300
+ Effect.succeed(ref === undefined ? undefined : resolved(ref).name);
301
+
302
+ const checkImage = (image: string) => {
303
+ const last = image.split("/").at(-1) ?? image;
304
+ const tag = image.includes("@")
305
+ ? "digest"
306
+ : last.includes(":")
307
+ ? last.split(":").at(-1)!
308
+ : undefined;
309
+ return tag === undefined || ["latest", "lts", "stable"].includes(tag)
310
+ ? Effect.fail(new SnapshotImageTagInvalid({ image }))
311
+ : Effect.void;
312
+ };
313
+
314
+ /** The Dockerfile's text for Daytona; the plan has baked build arguments in. */
315
+ const dockerfileText = (plan: BuildPlan.BuildPlan) => DockerfileParser.render(plan.dockerfile);
316
+
317
+ /**
318
+ * Check Dockerfile content built at runtime (a Worker has no file system,
319
+ * so there is no context to copy from).
320
+ */
321
+ export const checkRuntimeDockerfile = Effect.fn(function* (content: string) {
322
+ const parsed = yield* DockerfileParser.parse(content);
323
+ const { violations } = BuildPlan.check(parsed, capabilities, "linux/amd64");
324
+ if (violations.length > 0) {
325
+ return yield* new BuildPlan.UnsupportedDockerfile({
326
+ target: capabilities.target,
327
+ violations,
328
+ message: violations.map((v) => `line ${v.line}: ${v.instruction}: ${v.reason}`).join("\n"),
329
+ });
330
+ }
331
+ for (const stage of parsed.stages) {
332
+ for (const instruction of stage.instructions) {
333
+ if (instruction._tag !== "COPY" && instruction._tag !== "ADD") continue;
334
+ if (instruction.flags.some((flag) => flag.name === "from")) continue;
335
+ const local = instruction.sources.filter(
336
+ (source) => !/^(https?:\/\/|git@)/.test(source) && !source.startsWith("<<"),
337
+ );
338
+ if (local.length > 0) return yield* new SnapshotContextUnsupported({ line: instruction.raw });
339
+ }
340
+ }
341
+ });
342
+
343
+ /** What the snapshot is built from, resolved; its hash decides rebuilds. */
344
+ const resolveSource = Effect.fn(function* (news: SnapshotProps) {
345
+ const fromDockerfile = news.dockerfile !== undefined || news.context !== undefined;
346
+ const sources = [news.image, fromDockerfile ? true : undefined, news.from].filter(
347
+ (s) => s !== undefined,
348
+ );
349
+ if (sources.length !== 1) {
350
+ return yield* new SnapshotSourceInvalid({
351
+ message: "Daytona.Snapshot takes exactly one of `image`, `dockerfile`/`context` or `from`",
352
+ });
353
+ }
354
+ if (news.image !== undefined) yield* checkImage(news.image);
355
+ // A Dockerfile builds on Daytona unless it asks for local Docker, or
356
+ // has build secrets (Daytona's builders can't receive them).
357
+ const remote = news.build?.remote ?? news.build?.secrets === undefined;
358
+ const local: DockerfileImageSource | undefined =
359
+ fromDockerfile && !remote
360
+ ? {
361
+ dockerfile: news.dockerfile,
362
+ context: news.context,
363
+ build: { args: news.build?.args, secrets: news.build?.secrets },
364
+ }
365
+ : undefined;
366
+ const plan =
367
+ fromDockerfile && remote
368
+ ? yield* BuildPlan.plan(
369
+ { dockerfile: news.dockerfile, context: news.context, build: { args: news.build?.args } },
370
+ { capabilities },
371
+ )
372
+ : undefined;
373
+ const sizing = {
374
+ entrypoint: news.entrypoint,
375
+ cpu: news.cpu,
376
+ memory: news.memory,
377
+ disk: news.disk,
378
+ gpu: news.gpu,
379
+ gpuType: news.gpuType,
380
+ regionId: news.regionId,
381
+ sandboxClass: news.sandboxClass,
382
+ };
383
+ const source = {
384
+ image: news.image,
385
+ dockerfile: plan ? dockerfileText(plan) : undefined,
386
+ from: yield* refName(news.from),
387
+ run: news.run ?? [],
388
+ ...sizing,
389
+ };
390
+ const buildHash = yield* sha256({
391
+ image: source.image,
392
+ plan: plan?.hash,
393
+ local: local ? yield* computeStaticSourceHash(local, "linux/amd64") : undefined,
394
+ from: source.from,
395
+ run: source.run,
396
+ ...sizing,
397
+ });
398
+ return { source, plan, local, buildHash };
399
+ });
400
+
401
+ export const toSnapshotAttributes = (
402
+ s: snapshots.SnapshotDto,
403
+ buildHash: string,
404
+ ): SnapshotAttributes => ({
405
+ snapshotId: s.id,
406
+ name: s.name,
407
+ state: s.state,
408
+ imageName: s.imageName,
409
+ ref: s.ref,
410
+ cpu: s.cpu,
411
+ memory: s.mem,
412
+ disk: s.disk,
413
+ gpu: s.gpu,
414
+ sourceSandboxId: s.sourceSandboxId ?? undefined,
415
+ buildHash,
416
+ });
417
+
418
+ export const getSnapshot = (idOrName: string) =>
419
+ snapshots
420
+ .getSnapshot({ id: idOrName })
421
+ .pipe(Effect.catchTag("SnapshotNotFound", () => Effect.undefined));
422
+
423
+ /** Wait for a snapshot to become usable, reactivating it when Daytona has deactivated it. */
424
+ export const waitForSnapshot = Effect.fn(function* (idOrName: string) {
425
+ const current = yield* snapshots.getSnapshot({ id: idOrName });
426
+ if (current.state === "inactive") yield* snapshots.activateSnapshot({ id: current.id });
427
+ return yield* waitForState({
428
+ kind: "snapshot",
429
+ id: idOrName,
430
+ observe: snapshots.getSnapshot({ id: idOrName }),
431
+ want: ["active"],
432
+ fail: ["error", "build_failed"],
433
+ reason: (s) => s.errorReason,
434
+ budget: "30 minutes",
435
+ });
436
+ });
437
+
438
+ /** Delete a snapshot by name (Daytona deletes by id). Deleting a missing one succeeds. */
439
+ export const deleteSnapshotByName = Effect.fn(function* (name: string) {
440
+ const found = yield* getSnapshot(name);
441
+ if (found !== undefined) yield* deleteSnapshot(found.id);
442
+ });
443
+
444
+ export const deleteSnapshot = Effect.fn(function* (idOrName: string) {
445
+ yield* snapshots
446
+ .removeSnapshot({ id: idOrName })
447
+ .pipe(Effect.catchTag("SnapshotNotFound", () => Effect.void));
448
+ yield* waitUntilGone(
449
+ "snapshot",
450
+ idOrName,
451
+ getSnapshot(idOrName).pipe(Effect.map((s) => s !== undefined)),
452
+ "5 minutes",
453
+ );
454
+ });
455
+
456
+ /**
457
+ * Start a sandbox from `from`, run the commands in it and capture it as a
458
+ * snapshot named `name`. The build sandbox is deleted whatever happens, and
459
+ * deletes itself after an hour if this process dies first.
460
+ */
461
+ export const captureSnapshot = Effect.fn(function* (input: {
462
+ readonly labels: Record<string, string>;
463
+ readonly name: string;
464
+ readonly from: string;
465
+ readonly run: readonly string[];
466
+ readonly note: (message: string) => Effect.Effect<void>;
467
+ }) {
468
+ const build = yield* Effect.acquireRelease(
469
+ sandboxes.createSandbox({
470
+ name: `${input.name}-build`.slice(0, 63),
471
+ snapshot: input.from,
472
+ labels: { ...input.labels, "alchemy.role": "snapshot-build" },
473
+ autoDeleteInterval: 60,
474
+ }),
475
+ (sandbox) =>
476
+ sandboxes.deleteSandbox({ sandboxIdOrName: sandbox.id }).pipe(
477
+ Effect.catchTag("SandboxNotFound", () => Effect.void),
478
+ Effect.ignore,
479
+ ),
480
+ );
481
+ const started = yield* waitForState({
482
+ kind: "sandbox",
483
+ id: build.id,
484
+ observe: sandboxes.getSandbox({ sandboxIdOrName: build.id }),
485
+ want: ["started"],
486
+ fail: ["error", "build_failed", "destroyed"],
487
+ reason: (s) => s.errorReason,
488
+ });
489
+ for (const command of input.run) {
490
+ yield* input.note(`Running \`${command}\` in the snapshot build sandbox...`);
491
+ const result = yield* runCommand(started.id, started.toolboxProxyUrl, {
492
+ command,
493
+ timeout: 1800,
494
+ });
495
+ if ((result.exitCode ?? 0) !== 0) {
496
+ return yield* new SnapshotRunFailed({
497
+ command,
498
+ exitCode: result.exitCode ?? -1,
499
+ output: result.result,
500
+ });
501
+ }
502
+ }
503
+ yield* input.note(`Capturing snapshot ${input.name}...`);
504
+ yield* sandboxes.createSandboxSnapshot({ sandboxIdOrName: started.id, name: input.name });
505
+ return yield* waitForSnapshot(input.name);
506
+ }, Effect.scoped);
507
+
508
+ /** A snapshot's source with every input resolved (Dockerfile as content). */
509
+ export interface ResolvedSnapshotSource {
510
+ readonly image?: string | undefined;
511
+ readonly dockerfile?: string | undefined;
512
+ readonly from?: string | undefined;
513
+ readonly run?: readonly string[] | undefined;
514
+ readonly entrypoint?: string[] | undefined;
515
+ readonly cpu?: number | undefined;
516
+ readonly memory?: number | undefined;
517
+ readonly disk?: number | undefined;
518
+ readonly gpu?: number | undefined;
519
+ readonly gpuType?: GpuType[] | undefined;
520
+ readonly regionId?: string | undefined;
521
+ readonly sandboxClass?: snapshots.SandboxClass | undefined;
522
+ /** Uploaded build contexts the Dockerfile copies from (`buildInfo.contextHashes`). */
523
+ readonly contextHashes?: string[] | undefined;
524
+ }
525
+
526
+ /**
527
+ * Build a snapshot named `name` and wait until it is active: pull or
528
+ * build on Daytona's builders, or capture it from a `from` sandbox after
529
+ * running `run`. Shared by the resource and the runtime binding.
530
+ */
531
+ export const buildSnapshot = Effect.fn(function* (input: {
532
+ readonly name: string;
533
+ readonly source: ResolvedSnapshotSource;
534
+ readonly labels: Record<string, string>;
535
+ readonly note: (message: string) => Effect.Effect<void>;
536
+ }) {
537
+ const { name, source } = input;
538
+ if (source.from !== undefined) {
539
+ return yield* captureSnapshot({
540
+ labels: input.labels,
541
+ name,
542
+ from: source.from,
543
+ run: source.run ?? [],
544
+ note: input.note,
545
+ });
546
+ }
547
+ if (source.image !== undefined) yield* checkImage(source.image);
548
+ yield* input.note(
549
+ source.dockerfile !== undefined
550
+ ? `Building snapshot ${name} on Daytona...`
551
+ : `Pulling ${source.image} into snapshot ${name}...`,
552
+ );
553
+ yield* snapshots
554
+ .createSnapshot({
555
+ name,
556
+ imageName: source.image,
557
+ buildInfo:
558
+ source.dockerfile !== undefined
559
+ ? { dockerfileContent: source.dockerfile, contextHashes: source.contextHashes }
560
+ : undefined,
561
+ entrypoint: source.entrypoint,
562
+ cpu: source.cpu,
563
+ memory: source.memory,
564
+ disk: source.disk,
565
+ gpu: source.gpu,
566
+ gpuType: source.gpuType,
567
+ regionId: source.regionId,
568
+ sandboxClass: source.sandboxClass,
569
+ })
570
+ .pipe(Effect.catchTag("SnapshotAlreadyExists", () => Effect.void));
571
+ return yield* waitForSnapshot(name);
572
+ });
573
+
574
+ /**
575
+ * Daytona's transient registry as a push target: a short-lived robot
576
+ * account scoped to the organization's own project, which Daytona pulls
577
+ * snapshots from without a registry entry.
578
+ */
579
+ const transientRegistry = Effect.fn(function* (name: string, regionId: string | undefined) {
580
+ const access = yield* dockerRegistry.getTransientPushAccess({ regionId });
581
+ return {
582
+ repositoryUri: Effect.succeed(`${access.registryUrl}/${access.project}/${name}`),
583
+ // Pushes are content-addressed and the registry can't be listed with
584
+ // this account; pushing an existing tag is a no-op upload.
585
+ hasTag: () => Effect.succeed(false),
586
+ credentials: Effect.succeed({
587
+ server: access.registryUrl,
588
+ username: access.username,
589
+ password: access.secret,
590
+ }),
591
+ } satisfies ImageRegistryTarget;
592
+ });
593
+
594
+ export const SnapshotProvider = () =>
595
+ Provider.effect(
596
+ Snapshot,
597
+ Effect.gen(function* () {
598
+ const images = yield* makeContainerImageSource;
599
+ return {
600
+ stables: ["snapshotId"],
601
+ list: Effect.fn(function* () {
602
+ const all = yield* snapshots.getAllSnapshots.items({}).pipe(Stream.runCollect);
603
+ return Array.from(all)
604
+ .filter((s) => !s.general)
605
+ .map((s) => toSnapshotAttributes(s, ""));
606
+ }),
607
+ diff: Effect.fn(function* ({ id, news, output }) {
608
+ if (!isResolved(news) || output === undefined) return undefined;
609
+ const name = yield* resolveName(
610
+ id,
611
+ news.name,
612
+ news.name === undefined ? output.name : undefined,
613
+ );
614
+ const { buildHash } = yield* resolveSource(news);
615
+ if (name !== output.name || buildHash !== output.buildHash) {
616
+ // Sandboxes reference snapshots by name, so a pinned name is freed first.
617
+ return { action: "replace", deleteFirst: name === output.name } as const;
618
+ }
619
+ }),
620
+ lookup: Effect.fn(function* ({ name }) {
621
+ const found = yield* getSnapshot(name);
622
+ return found === undefined ? undefined : toSnapshotAttributes(found, "");
623
+ }),
624
+
625
+ read: Effect.fn(function* ({ id, olds, output }) {
626
+ const name = yield* resolveName(id, olds?.name, output?.name);
627
+ const found =
628
+ (output ? yield* getSnapshot(output.snapshotId) : undefined) ??
629
+ (yield* getSnapshot(name));
630
+ if (found === undefined) return undefined;
631
+ const attrs = toSnapshotAttributes(found, output?.buildHash ?? "");
632
+ return (yield* isOwnedName(id, found.name, output !== undefined))
633
+ ? attrs
634
+ : Unowned(attrs);
635
+ }),
636
+ reconcile: Effect.fn(function* ({ id, news, output, session }) {
637
+ const name = yield* resolveName(id, news.name, output?.name);
638
+ const { source, plan, local, buildHash } = yield* resolveSource(news);
639
+
640
+ // Observe
641
+ const current = yield* getSnapshot(output?.snapshotId ?? name);
642
+ if (current !== undefined && current.name === name) {
643
+ // Ensure usable: reactivate a snapshot Daytona deactivated after
644
+ // two weeks unused, and wait out a build already in flight.
645
+ const ready = current.state === "active" ? current : yield* waitForSnapshot(current.id);
646
+ return toSnapshotAttributes(ready, output?.buildHash ?? buildHash);
647
+ }
648
+
649
+ // Ensure — a local build pushes its image to Daytona's registry,
650
+ // which the snapshot then pulls.
651
+ if (local !== undefined) {
652
+ yield* session.note(`Building ${name} with local Docker...`);
653
+ const pushed = yield* images.resolve(
654
+ { id, source: local, session, platform: "linux/amd64", bootstrap: () => "" },
655
+ yield* transientRegistry(name, news.regionId),
656
+ );
657
+ const built = yield* buildSnapshot({
658
+ name,
659
+ source: { ...source, dockerfile: undefined, image: pushed.imageUri },
660
+ labels: yield* createInternalLabels(id),
661
+ note: session.note,
662
+ });
663
+ return toSnapshotAttributes(built, buildHash);
664
+ }
665
+
666
+ // Ensure — upload what the Dockerfile copies, then build.
667
+ for (const warning of plan?.warnings ?? []) yield* session.note(warning);
668
+ const contextHashes =
669
+ plan !== undefined && plan.files.length > 0
670
+ ? [yield* uploadBuildContext(plan)]
671
+ : undefined;
672
+ const built = yield* buildSnapshot({
673
+ name,
674
+ source: { ...source, contextHashes },
675
+ labels: yield* createInternalLabels(id),
676
+ note: session.note,
677
+ });
678
+ return toSnapshotAttributes(built, buildHash);
679
+ }),
680
+ delete: Effect.fn(function* ({ output }) {
681
+ yield* deleteSnapshot(output.snapshotId);
682
+ }),
683
+ };
684
+ }),
685
+ );