@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
package/src/Volume.ts ADDED
@@ -0,0 +1,229 @@
1
+ import * as volumes from "@distilled.cloud/daytona/volumes";
2
+ import { Unowned } from "alchemy/AdoptPolicy";
3
+ import { isResolved } from "alchemy/Diff";
4
+ import * as Provider from "alchemy/Provider";
5
+ import { Resource } from "alchemy/Resource";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Schedule from "effect/Schedule";
8
+ import { isOwnedName, resolveName } from "./internal/Names.ts";
9
+ import { waitForState, waitUntilGone } from "./internal/Wait.ts";
10
+ import type { Providers } from "./Providers.ts";
11
+
12
+ export interface VolumeProps {
13
+ /**
14
+ * Volume name, unique in the organization. If omitted, a unique name is
15
+ * generated from the stack, stage and logical ID. Changing it replaces
16
+ * the volume (and its files).
17
+ */
18
+ name?: string;
19
+ }
20
+
21
+ export interface VolumeAttributes {
22
+ /** Volume ID. */
23
+ volumeId: string;
24
+ /** Volume name. */
25
+ name: string;
26
+ /** Volume state (`ready` once usable). */
27
+ state: volumes.VolumeState;
28
+ /** When the volume was created. */
29
+ createdAt: string;
30
+ }
31
+
32
+ export type Volume = Resource<
33
+ "Daytona.Volume",
34
+ VolumeProps,
35
+ VolumeAttributes,
36
+ never,
37
+ Providers,
38
+ VolumeLookup
39
+ >;
40
+
41
+ /**
42
+ * Props for {@link Volume.lookup}: find a volume this stack
43
+ * doesn't manage by its name.
44
+ */
45
+ export interface VolumeLookup {
46
+ /** Volume name. */
47
+ name: string;
48
+ }
49
+
50
+ /**
51
+ * A Daytona volume: a persistent disk, backed by object storage, that
52
+ * sandboxes mount at a path. Its files outlive every sandbox that mounts
53
+ * it, and several sandboxes can mount it at once.
54
+ *
55
+ * Declare a volume here for data your whole app shares, like a dataset or
56
+ * a package cache. For a volume per user or per session, create it at
57
+ * runtime with {@link CreateVolume}.
58
+ * @see https://www.daytona.io/docs/en/volumes/
59
+ *
60
+ * ### Creating a Volume
61
+ * **Example:** A shared cache volume
62
+ * ```typescript
63
+ * export const Cache = Daytona.Volume("Cache", {});
64
+ * ```
65
+ *
66
+ * ### Mounting a Volume
67
+ * **Example:** Mount it in a sandbox declared at deploy
68
+ * ```typescript
69
+ * export const DevBox = Daytona.Sandbox("DevBox", {
70
+ * snapshot: Base,
71
+ * mounts: [{ volume: Cache, path: "/cache" }],
72
+ * });
73
+ * ```
74
+ *
75
+ * **Example:** Mount it in sandboxes created at runtime
76
+ * ```typescript
77
+ * const cache = yield* Daytona.MountVolume(Cache, { path: "/cache" });
78
+ * const sandbox = yield* sandboxes.create({ mounts: [cache] });
79
+ * ```
80
+ *
81
+ * ### Looking up an existing volume
82
+ * `Volume.lookup` references a volume Alchemy doesn't manage (created in
83
+ * the Daytona dashboard or by another stack). It is read during plan/deploy,
84
+ * never created, updated or deleted.
85
+ *
86
+ * **Example:** Mount an existing volume
87
+ * ```typescript
88
+ * const data = Daytona.Volume.lookup({ name: "datasets" });
89
+ * ```
90
+ *
91
+ * @resource
92
+ * @product Volume
93
+ */
94
+ export const Volume = Resource<Volume>("Daytona.Volume");
95
+
96
+ export const toVolumeAttributes = (v: volumes.VolumeDto): VolumeAttributes => ({
97
+ volumeId: v.id,
98
+ name: v.name,
99
+ state: v.state,
100
+ createdAt: v.createdAt,
101
+ });
102
+
103
+ const isLive = (v: volumes.VolumeDto) =>
104
+ v.state !== "deleted" && v.state !== "deleting" && v.state !== "pending_delete";
105
+
106
+ export const getVolumeByName = (name: string) =>
107
+ volumes.getVolumeByName({ name }).pipe(
108
+ Effect.map((v) => (isLive(v) ? v : undefined)),
109
+ Effect.catchTag("VolumeNotFound", () => Effect.undefined),
110
+ );
111
+
112
+ export const getVolumeById = (volumeId: string) =>
113
+ volumes.getVolume({ volumeId }).pipe(
114
+ Effect.map((v) => (isLive(v) ? v : undefined)),
115
+ Effect.catchTag("VolumeNotFound", () => Effect.undefined),
116
+ );
117
+
118
+ export const waitForVolume = (volumeId: string) =>
119
+ waitForState({
120
+ kind: "volume",
121
+ id: volumeId,
122
+ observe: volumes.getVolume({ volumeId }),
123
+ want: ["ready"],
124
+ fail: ["error"],
125
+ reason: (v) => v.errorReason,
126
+ });
127
+
128
+ /**
129
+ * The volume named `name`, created if missing. A create racing another
130
+ * caller (`VolumeAlreadyExists`) re-reads the winner.
131
+ */
132
+ export const ensureVolume = Effect.fn(function* (name: string) {
133
+ const existing = yield* getVolumeByName(name);
134
+ const volume =
135
+ existing ??
136
+ (yield* volumes
137
+ .createVolume({ name })
138
+ .pipe(Effect.catchTag("VolumeAlreadyExists", () => volumes.getVolumeByName({ name }))));
139
+ return volume.state === "ready" ? volume : yield* waitForVolume(volume.id);
140
+ });
141
+
142
+ /**
143
+ * Delete a volume. With `wait` (deploy), retry while a sandbox still
144
+ * mounts it and wait until it is gone. At run time `wait` is off: a
145
+ * request fails at once with `VolumeInUse` and returns as soon as Daytona
146
+ * accepts the delete. Daytona releases a volume about a minute after the
147
+ * sandbox that wrote to it is deleted, which is longer than a request
148
+ * should wait.
149
+ */
150
+ export const deleteVolumeById = Effect.fn(function* (
151
+ volumeId: string,
152
+ options: { readonly wait: boolean } = { wait: true },
153
+ ) {
154
+ yield* volumes.deleteVolume({ volumeId }).pipe(
155
+ Effect.catchTags({
156
+ VolumeNotFound: () => Effect.void,
157
+ // Refused while the volume is still being created (wait for it) or is
158
+ // already being deleted (nothing left to do).
159
+ VolumeNotReady: (error) =>
160
+ Effect.flatMap(getVolumeById(volumeId), (live) =>
161
+ live === undefined ? Effect.void : Effect.fail(error),
162
+ ),
163
+ }),
164
+ // VolumeInUse: a sandbox still mounts it, e.g. while a destroy deletes
165
+ // the sandboxes alongside; it frees up once they are gone.
166
+ Effect.retry({
167
+ while: (e) => e._tag === "VolumeNotReady" || (options.wait && e._tag === "VolumeInUse"),
168
+ schedule: Schedule.spaced("2 seconds"),
169
+ times: 30,
170
+ }),
171
+ );
172
+ if (!options.wait) return;
173
+ yield* waitUntilGone(
174
+ "volume",
175
+ volumeId,
176
+ getVolumeById(volumeId).pipe(Effect.map((v) => v !== undefined)),
177
+ );
178
+ });
179
+
180
+ export const VolumeProvider = () =>
181
+ Provider.effect(
182
+ Volume,
183
+ Effect.succeed({
184
+ stables: ["volumeId", "createdAt"],
185
+ list: Effect.fn(function* () {
186
+ const all = yield* volumes.listVolumes({});
187
+ return all.filter(isLive).map(toVolumeAttributes);
188
+ }),
189
+ diff: Effect.fn(function* ({ id, news, output }) {
190
+ if (!isResolved(news) || output === undefined) return undefined;
191
+ const name = yield* resolveName(
192
+ id,
193
+ news.name,
194
+ news.name === undefined ? output.name : undefined,
195
+ );
196
+ if (name !== output.name) return { action: "replace", deleteFirst: true } as const;
197
+ }),
198
+ lookup: Effect.fn(function* ({ name }) {
199
+ const found = yield* getVolumeByName(name);
200
+ return found === undefined ? undefined : toVolumeAttributes(found);
201
+ }),
202
+
203
+ read: Effect.fn(function* ({ id, olds, output }) {
204
+ const name = yield* resolveName(id, olds?.name, output?.name);
205
+ const found =
206
+ (output ? yield* getVolumeById(output.volumeId) : undefined) ??
207
+ (yield* getVolumeByName(name));
208
+ if (found === undefined) return undefined;
209
+ const attrs = toVolumeAttributes(found);
210
+ return (yield* isOwnedName(id, found.name, output !== undefined)) ? attrs : Unowned(attrs);
211
+ }),
212
+ reconcile: Effect.fn(function* ({ id, news, output }) {
213
+ // Observe by id first: the name may already be taken by a peer.
214
+ const name = yield* resolveName(id, news.name, output?.name);
215
+ const byId = output ? yield* getVolumeById(output.volumeId) : undefined;
216
+ // Ensure — volumes have nothing mutable beyond existence.
217
+ const volume =
218
+ byId && byId.name === name
219
+ ? byId.state === "ready"
220
+ ? byId
221
+ : yield* waitForVolume(byId.id)
222
+ : yield* ensureVolume(name);
223
+ return toVolumeAttributes(volume);
224
+ }),
225
+ delete: Effect.fn(function* ({ output }) {
226
+ yield* deleteVolumeById(output.volumeId);
227
+ }),
228
+ }),
229
+ );
@@ -0,0 +1,128 @@
1
+ import * as warmPools from "@distilled.cloud/daytona/warm_pools";
2
+ import { isResolved } from "alchemy/Diff";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Effect from "effect/Effect";
6
+ import { declarable } from "./internal/Declared.ts";
7
+ import { resolved, type Ref } from "./internal/Ref.ts";
8
+ import type { Providers } from "./Providers.ts";
9
+
10
+ /** A snapshot, by name. Accepts a `Daytona.Snapshot` or `{ name }`. */
11
+ export type WarmPoolSnapshot = { readonly name: string };
12
+
13
+ export interface WarmPoolProps {
14
+ /** Snapshot whose sandboxes are kept warm. Changing it replaces the pool. */
15
+ snapshot: Ref<WarmPoolSnapshot>;
16
+ /** How many started sandboxes to keep ready. Updated in place. */
17
+ size: number;
18
+ /** Region to keep them in. Changing it replaces the pool. */
19
+ target?: string;
20
+ }
21
+
22
+ export interface WarmPoolAttributes {
23
+ /** Warm pool ID. */
24
+ warmPoolId: string;
25
+ /** Snapshot name. */
26
+ snapshot: string;
27
+ /** Region. */
28
+ target: string;
29
+ /** Desired pool size. */
30
+ size: number;
31
+ /** Sandboxes currently ready in the pool. */
32
+ currentSize: number;
33
+ }
34
+
35
+ export type WarmPool = Resource<
36
+ "Daytona.WarmPool",
37
+ WarmPoolProps,
38
+ WarmPoolAttributes,
39
+ never,
40
+ Providers
41
+ >;
42
+
43
+ /**
44
+ * Keeps started sandboxes of a {@link Snapshot} ready, so creating a
45
+ * sandbox from that snapshot hands one over instead of booting a new one.
46
+ * Daytona uses the pool automatically for matching creates.
47
+ *
48
+ * Deleting the snapshot deletes its warm pools. Daytona Cloud doesn't
49
+ * serve warm pools to every organization; where it doesn't, deploying one
50
+ * fails with `WarmPoolsUnavailable`.
51
+ * @see https://www.daytona.io/docs/en/warm-pools/
52
+ *
53
+ * ### Keeping Sandboxes Warm
54
+ * **Example:** Four ready sandboxes of a snapshot
55
+ * ```typescript
56
+ * export const Pool = Daytona.WarmPool("Pool", { snapshot: Base, size: 4 });
57
+ * ```
58
+ *
59
+ * @resource
60
+ * @product Warm Pool
61
+ */
62
+ export const WarmPool = declarable<WarmPool>()(Resource<WarmPool>("Daytona.WarmPool"));
63
+
64
+ const toAttributes = (p: warmPools.WarmPool): WarmPoolAttributes => ({
65
+ warmPoolId: p.id,
66
+ snapshot: p.snapshot,
67
+ target: p.target,
68
+ size: p.pool,
69
+ currentSize: p.currentSize,
70
+ });
71
+
72
+ const snapshotName = (snapshot: WarmPoolProps["snapshot"]) =>
73
+ Effect.succeed(resolved(snapshot).name);
74
+
75
+ export const WarmPoolProvider = () =>
76
+ Provider.effect(
77
+ WarmPool,
78
+ Effect.succeed({
79
+ stables: ["warmPoolId", "snapshot", "target"],
80
+ list: Effect.fn(function* () {
81
+ // Organizations Daytona doesn't serve warm pools to have none.
82
+ const pools = yield* warmPools
83
+ .listWarmPools({})
84
+ .pipe(Effect.catchTag("WarmPoolsUnavailable", () => Effect.succeed([])));
85
+ return pools.map(toAttributes);
86
+ }),
87
+ diff: Effect.fn(function* ({ news, output }) {
88
+ if (!isResolved(news) || output === undefined) return undefined;
89
+ const snapshot = yield* snapshotName(news.snapshot);
90
+ if (
91
+ snapshot !== output.snapshot ||
92
+ (news.target !== undefined && news.target !== output.target)
93
+ ) {
94
+ return { action: "replace" } as const;
95
+ }
96
+ }),
97
+ read: Effect.fn(function* ({ output }) {
98
+ if (output === undefined) return undefined;
99
+ const found = (yield* warmPools.listWarmPools({})).find((p) => p.id === output.warmPoolId);
100
+ return found ? toAttributes(found) : undefined;
101
+ }),
102
+ reconcile: Effect.fn(function* ({ news, output }) {
103
+ const snapshot = yield* snapshotName(news.snapshot);
104
+ // Observe — a pool is identified by its snapshot and region.
105
+ const pools = yield* warmPools.listWarmPools({});
106
+ const current =
107
+ pools.find((p) => p.id === output?.warmPoolId) ??
108
+ pools.find(
109
+ (p) =>
110
+ p.snapshot === snapshot && (news.target === undefined || p.target === news.target),
111
+ );
112
+ // Ensure
113
+ if (current === undefined) {
114
+ return toAttributes(
115
+ yield* warmPools.createWarmPool({ snapshot, pool: news.size, target: news.target }),
116
+ );
117
+ }
118
+ // Sync size
119
+ if (current.pool === news.size) return toAttributes(current);
120
+ return toAttributes(yield* warmPools.updateWarmPool({ id: current.id, pool: news.size }));
121
+ }),
122
+ delete: Effect.fn(function* ({ output }) {
123
+ yield* warmPools
124
+ .deleteWarmPool({ id: output.warmPoolId })
125
+ .pipe(Effect.catchTag("WarmPoolNotFound", () => Effect.void));
126
+ }),
127
+ }),
128
+ );
@@ -0,0 +1,121 @@
1
+ import * as webhooks from "@distilled.cloud/daytona/webhooks";
2
+ import { isResolved } from "alchemy/Diff";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Effect from "effect/Effect";
6
+ import { declarable } from "./internal/Declared.ts";
7
+ import {
8
+ movesOrganization,
9
+ organizationIdFor,
10
+ type OrganizationRef,
11
+ } from "./internal/Organization.ts";
12
+ import type { Ref } from "./internal/Ref.ts";
13
+ import { waitForState } from "./internal/Wait.ts";
14
+ import type { Providers } from "./Providers.ts";
15
+
16
+ export interface WebhooksProps {
17
+ /**
18
+ * Organization to act on, e.g. a `Daytona.Organization`. Defaults to the
19
+ * credentials' organization. Changing it replaces the resource.
20
+ */
21
+ organization?: Ref<OrganizationRef>;
22
+ /**
23
+ * Bump to re-sync the organization's webhook endpoints with Daytona's
24
+ * event types on the next deploy.
25
+ */
26
+ refresh?: number;
27
+ }
28
+
29
+ export interface WebhooksAttributes {
30
+ /** Organization ID. */
31
+ organizationId: string;
32
+ /** The Svix application Daytona delivers events through. */
33
+ svixApplicationId: string;
34
+ /** Refresh counter applied. */
35
+ refreshed: number;
36
+ }
37
+
38
+ export type Webhooks = Resource<
39
+ "Daytona.Webhooks",
40
+ WebhooksProps,
41
+ WebhooksAttributes,
42
+ never,
43
+ Providers
44
+ >;
45
+
46
+ /**
47
+ * Turn on webhooks for the Daytona organization: sandbox, snapshot and
48
+ * volume events delivered to endpoints you register in Daytona's webhook
49
+ * portal. Daytona delivers through Svix, which owns the endpoints, so this
50
+ * resource only enables delivery; open the portal with
51
+ * `getWebhookControllerAppPortalAccess` (on {@link ReadOrganization}) to
52
+ * add endpoints. Webhooks can't be turned off again through the API, so
53
+ * destroy leaves them enabled.
54
+ * @see https://www.daytona.io/docs/en/webhooks/
55
+ *
56
+ * ### Enabling Webhooks
57
+ * **Example:** Enable delivery for the organization
58
+ * ```typescript
59
+ * export const Events = Daytona.Webhooks("Events", {});
60
+ * ```
61
+ *
62
+ * @resource
63
+ * @product Organization
64
+ */
65
+ export const Webhooks = declarable<Webhooks>()(Resource<Webhooks>("Daytona.Webhooks"));
66
+
67
+ const status = (organizationId: string) =>
68
+ webhooks.getWebhookControllerInitializationStatus({ organizationId }).pipe(
69
+ Effect.map((s) => ({
70
+ ...s,
71
+ state: s.svixApplicationId ? "ready" : s.lastError ? "error" : "pending",
72
+ })),
73
+ // Before initialization Daytona has no status to report.
74
+ Effect.catchTag("WebhooksNotInitialized", () =>
75
+ Effect.succeed({ svixApplicationId: undefined, lastError: undefined, state: "pending" }),
76
+ ),
77
+ );
78
+
79
+ export const WebhooksProvider = () =>
80
+ Provider.effect(
81
+ Webhooks,
82
+ Effect.succeed({
83
+ stables: ["organizationId", "svixApplicationId"],
84
+ nuke: { singleton: true },
85
+ list: () => Effect.succeed([]),
86
+ diff: Effect.fn(function* ({ news, output }) {
87
+ if (!isResolved(news) || output === undefined) return undefined;
88
+ if (yield* movesOrganization(news.organization, output))
89
+ return { action: "replace" } as const;
90
+ }),
91
+ reconcile: Effect.fn(function* ({ news, output }) {
92
+ const organizationId = yield* organizationIdFor(news.organization);
93
+ // Observe
94
+ let current = yield* status(organizationId);
95
+ // Ensure
96
+ if (current.svixApplicationId == null) {
97
+ yield* webhooks.webhookControllerInitializeWebhooks({ organizationId });
98
+ current = yield* waitForState({
99
+ kind: "webhooks",
100
+ id: organizationId,
101
+ observe: status(organizationId),
102
+ want: ["ready"],
103
+ fail: ["error"],
104
+ reason: (s) => s.lastError,
105
+ budget: "2 minutes",
106
+ });
107
+ }
108
+ // Sync
109
+ const refresh = news.refresh ?? 0;
110
+ if (output !== undefined && refresh !== output.refreshed) {
111
+ yield* webhooks.refreshWebhookControllerEndpoints({ organizationId });
112
+ }
113
+ return {
114
+ organizationId,
115
+ svixApplicationId: current.svixApplicationId ?? "",
116
+ refreshed: refresh,
117
+ };
118
+ }),
119
+ delete: () => Effect.void,
120
+ }),
121
+ );
@@ -0,0 +1,2 @@
1
+ // `globalThis.__ALCHEMY_RUNTIME__`, declared by alchemy's Phase module.
2
+ import type {} from "alchemy/Phase";
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ export * from "./ApiKey.ts";
2
+ export * from "./AuthProvider.ts";
3
+ export * from "./ConnectSandbox.ts";
4
+ export * from "./ConnectSandboxHttp.ts";
5
+ export * from "./CreateSandbox.ts";
6
+ export * from "./CreateSandboxHttp.ts";
7
+ export * from "./CreateSecret.ts";
8
+ export * from "./CreateSecretHttp.ts";
9
+ export * from "./CreateSnapshot.ts";
10
+ export * from "./CreateSnapshotHttp.ts";
11
+ export * from "./CreateVolume.ts";
12
+ export * from "./CreateVolumeHttp.ts";
13
+ export * from "./Credentials.ts";
14
+ export * from "./DaytonaBindings.ts";
15
+ export * from "./IdentityProvider.ts";
16
+ export * from "./MountVolume.ts";
17
+ export * from "./Organization.ts";
18
+ export * from "./OrganizationInvitation.ts";
19
+ export * from "./OrganizationMember.ts";
20
+ export * from "./OrganizationOtelConfig.ts";
21
+ export * from "./OrganizationRole.ts";
22
+ export * from "./OrganizationSettings.ts";
23
+ export * from "./Providers.ts";
24
+ export * from "./ReadAuditLogs.ts";
25
+ export * from "./ReadOrganization.ts";
26
+ export * from "./ReadUsage.ts";
27
+ export * from "./Region.ts";
28
+ export * from "./Registry.ts";
29
+ export * from "./Runner.ts";
30
+ export * from "./Sandbox.ts";
31
+ export * from "./Secret.ts";
32
+ export * from "./Snapshot.ts";
33
+ export * from "./Volume.ts";
34
+ export * from "./WarmPool.ts";
35
+ export * from "./Webhooks.ts";
36
+ export type {
37
+ Command,
38
+ ConnectedSandbox,
39
+ DaytonaSandbox,
40
+ ExecOptions,
41
+ ExecResult,
42
+ PtyOpenOptions,
43
+ SandboxFiles,
44
+ SandboxProcess,
45
+ SandboxPty,
46
+ } from "./SandboxClient.ts";
@@ -0,0 +1,62 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as BuildPlan from "@alchemy.run/docker/BuildPlan";
3
+ import { fromCredentials } from "@distilled.cloud/aws/Credentials";
4
+ import * as AwsEndpoint from "@distilled.cloud/aws/Endpoint";
5
+ import type { RegionName } from "@distilled.cloud/aws/Region";
6
+ import * as s3 from "@distilled.cloud/aws/s3";
7
+ import * as objectStorage from "@distilled.cloud/daytona/object_storage";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Redacted from "effect/Redacted";
11
+ import { tar } from "./Tar.ts";
12
+
13
+ const redact = (value: string | Redacted.Redacted<string>) =>
14
+ Redacted.isRedacted(value) ? value : Redacted.make(value);
15
+
16
+ /**
17
+ * Upload a plan's context files to Daytona's build-context storage and
18
+ * return the hash `buildInfo.contextHashes` takes.
19
+ *
20
+ * Daytona's builders read each context from
21
+ * `<organizationId>/<hash>/context.tar` in the bucket `getPushAccess`
22
+ * names, with the temporary credentials it returns (the layout Daytona's
23
+ * own SDKs use). The files go in one archive at their context paths; an
24
+ * archive already uploaded under the same hash is reused.
25
+ */
26
+ export const uploadBuildContext = Effect.fn(function* (plan: BuildPlan.BuildPlan) {
27
+ const hash = yield* Effect.sync(() =>
28
+ createHash("sha256")
29
+ .update(JSON.stringify(plan.files.map((f) => [f.path, f.mode, f.sha256])))
30
+ .digest("hex")
31
+ .slice(0, 32),
32
+ );
33
+ const access = yield* objectStorage.getPushAccess({});
34
+ const prefix = `${access.organizationId}/${hash}/`;
35
+ const storage = Layer.mergeAll(
36
+ fromCredentials(
37
+ {
38
+ accessKeyId: Redacted.make(access.accessKey),
39
+ secretAccessKey: redact(access.secret),
40
+ sessionToken: redact(access.sessionToken),
41
+ },
42
+ access.region as RegionName,
43
+ ),
44
+ AwsEndpoint.of(access.storageUrl),
45
+ );
46
+ const existing = yield* s3
47
+ .listObjectsV2({ Bucket: access.bucket, Prefix: prefix, MaxKeys: 1 })
48
+ .pipe(Effect.provide(storage));
49
+ if ((existing.KeyCount ?? 0) > 0) return hash;
50
+ const entries = yield* Effect.forEach(plan.files, (file) =>
51
+ Effect.map(BuildPlan.readContextFile(file), (bytes) => ({
52
+ path: file.path,
53
+ mode: file.mode,
54
+ bytes,
55
+ })),
56
+ );
57
+ const body = yield* Effect.sync(() => tar(entries));
58
+ yield* s3
59
+ .putObject({ Bucket: access.bucket, Key: `${prefix}context.tar`, Body: body })
60
+ .pipe(Effect.provide(storage));
61
+ return hash;
62
+ });
@@ -0,0 +1,80 @@
1
+ import type { PropsInput } from "alchemy/Input";
2
+ import * as Output from "alchemy/Output";
3
+ import { isResource } from "alchemy/Resource";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Redacted from "effect/Redacted";
6
+ import type { Providers } from "../Providers.ts";
7
+
8
+ type Leaf =
9
+ | string
10
+ | number
11
+ | boolean
12
+ | bigint
13
+ | symbol
14
+ | null
15
+ | undefined
16
+ | Redacted.Redacted<any>
17
+ | Uint8Array
18
+ | Date
19
+ | ((...args: any[]) => any);
20
+
21
+ /**
22
+ * A prop value, or the declaration of a resource that has it: `template: Node`
23
+ * instead of `template: yield* Node`, at any depth.
24
+ */
25
+ export type Declared<T> = T extends Leaf
26
+ ? T
27
+ : T extends Output.Output<any>
28
+ ? T
29
+ : T extends ReadonlyArray<infer U>
30
+ ? Declared<U>[] | Effect.Effect<T, never, Providers>
31
+ : { [K in keyof T]: Declared<T[K]> } | Effect.Effect<T, never, Providers>;
32
+
33
+ /** Props where any value may be a resource declaration. */
34
+ export type DeclaredProps<P> = { [K in keyof PropsInput<P>]: Declared<PropsInput<P>[K]> };
35
+
36
+ const isPlain = (value: unknown): value is Record<string, unknown> =>
37
+ typeof value === "object" &&
38
+ value !== null &&
39
+ (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
40
+
41
+ /** A declaration still to be yielded: an Effect that isn't an Output or a resource. */
42
+ const isDeclaration = (value: unknown): value is Effect.Effect<unknown, never, Providers> =>
43
+ Effect.isEffect(value) && !Output.isOutput(value) && !isResource(value);
44
+
45
+ const hasDeclarations = (value: unknown): boolean =>
46
+ isDeclaration(value) ||
47
+ (Array.isArray(value) && value.some(hasDeclarations)) ||
48
+ (isPlain(value) && !isResource(value) && Object.values(value).some(hasDeclarations));
49
+
50
+ /** Yield every declaration in `value`, at any depth. */
51
+ export const resolveDeclarations = (value: unknown): Effect.Effect<unknown, never, Providers> =>
52
+ isDeclaration(value)
53
+ ? value
54
+ : Array.isArray(value)
55
+ ? Effect.forEach(value, resolveDeclarations)
56
+ : isPlain(value) && !isResource(value) && hasDeclarations(value)
57
+ ? Effect.map(
58
+ Effect.forEach(Object.entries(value), ([key, entry]) =>
59
+ Effect.map(resolveDeclarations(entry), (resolved) => [key, resolved] as const),
60
+ ),
61
+ Object.fromEntries,
62
+ )
63
+ : Effect.succeed(value);
64
+
65
+ /**
66
+ * Let a resource's props hold declarations of other resources, which are
67
+ * yielded when the resource is: `Boat.Sandbox("Box", { snapshot: Golden })`.
68
+ * Props without declarations are passed through unchanged.
69
+ */
70
+ export const declarable =
71
+ <R extends { readonly Props: any }>() =>
72
+ <C extends object>(
73
+ resource: C,
74
+ ): C & ((id: string, props: DeclaredProps<R["Props"]>) => Effect.Effect<R, never, Providers>) =>
75
+ new Proxy(resource as C & ((...args: unknown[]) => unknown), {
76
+ apply: (target, self, args: unknown[]) =>
77
+ typeof args[0] === "string" && hasDeclarations(args[1])
78
+ ? Reflect.apply(target, self, [args[0], resolveDeclarations(args[1])])
79
+ : Reflect.apply(target, self, args),
80
+ }) as never;