@alchemy.run/e2b 0.0.0 → 2.0.0-beta.87

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 (284) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -4
  3. package/lib/ApiKey.d.ts +53 -0
  4. package/lib/ApiKey.d.ts.map +1 -0
  5. package/lib/ApiKey.js +107 -0
  6. package/lib/ApiKey.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +60 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +293 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Bootstrap.d.ts +7 -0
  12. package/lib/Bootstrap.d.ts.map +1 -0
  13. package/lib/Bootstrap.js +25 -0
  14. package/lib/Bootstrap.js.map +1 -0
  15. package/lib/ConnectSandbox.d.ts +67 -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 +22 -0
  20. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  21. package/lib/ConnectSandboxHttp.js +30 -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 +139 -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 +22 -0
  32. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  33. package/lib/CreateSandboxHttp.js +99 -0
  34. package/lib/CreateSandboxHttp.js.map +1 -0
  35. package/lib/CreateSecret.d.ts +57 -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 +20 -0
  40. package/lib/CreateSecretHttp.d.ts.map +1 -0
  41. package/lib/CreateSecretHttp.js +64 -0
  42. package/lib/CreateSecretHttp.js.map +1 -0
  43. package/lib/CreateSnapshot.d.ts +53 -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 +20 -0
  48. package/lib/CreateSnapshotHttp.d.ts.map +1 -0
  49. package/lib/CreateSnapshotHttp.js +41 -0
  50. package/lib/CreateSnapshotHttp.js.map +1 -0
  51. package/lib/CreateTemplate.d.ts +96 -0
  52. package/lib/CreateTemplate.d.ts.map +1 -0
  53. package/lib/CreateTemplate.js +3 -0
  54. package/lib/CreateTemplate.js.map +1 -0
  55. package/lib/CreateTemplateHttp.d.ts +20 -0
  56. package/lib/CreateTemplateHttp.d.ts.map +1 -0
  57. package/lib/CreateTemplateHttp.js +99 -0
  58. package/lib/CreateTemplateHttp.js.map +1 -0
  59. package/lib/CreateVolume.d.ts +58 -0
  60. package/lib/CreateVolume.d.ts.map +1 -0
  61. package/lib/CreateVolume.js +3 -0
  62. package/lib/CreateVolume.js.map +1 -0
  63. package/lib/CreateVolumeHttp.d.ts +20 -0
  64. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  65. package/lib/CreateVolumeHttp.js +51 -0
  66. package/lib/CreateVolumeHttp.js.map +1 -0
  67. package/lib/Credentials.d.ts +18 -0
  68. package/lib/Credentials.d.ts.map +1 -0
  69. package/lib/Credentials.js +55 -0
  70. package/lib/Credentials.js.map +1 -0
  71. package/lib/E2BBindings.d.ts +35 -0
  72. package/lib/E2BBindings.d.ts.map +1 -0
  73. package/lib/E2BBindings.js +127 -0
  74. package/lib/E2BBindings.js.map +1 -0
  75. package/lib/Errors.d.ts +61 -0
  76. package/lib/Errors.d.ts.map +1 -0
  77. package/lib/Errors.js +23 -0
  78. package/lib/Errors.js.map +1 -0
  79. package/lib/InferEnv.d.ts +26 -0
  80. package/lib/InferEnv.d.ts.map +1 -0
  81. package/lib/InferEnv.js +1 -0
  82. package/lib/InferEnv.js.map +1 -0
  83. package/lib/MountVolume.d.ts +86 -0
  84. package/lib/MountVolume.d.ts.map +1 -0
  85. package/lib/MountVolume.js +3 -0
  86. package/lib/MountVolume.js.map +1 -0
  87. package/lib/MountVolumeLive.d.ts +23 -0
  88. package/lib/MountVolumeLive.d.ts.map +1 -0
  89. package/lib/MountVolumeLive.js +41 -0
  90. package/lib/MountVolumeLive.js.map +1 -0
  91. package/lib/Providers.d.ts +14 -0
  92. package/lib/Providers.d.ts.map +1 -0
  93. package/lib/Providers.js +23 -0
  94. package/lib/Providers.js.map +1 -0
  95. package/lib/ReadTeam.d.ts +41 -0
  96. package/lib/ReadTeam.d.ts.map +1 -0
  97. package/lib/ReadTeam.js +3 -0
  98. package/lib/ReadTeam.js.map +1 -0
  99. package/lib/ReadTeamHttp.d.ts +20 -0
  100. package/lib/ReadTeamHttp.d.ts.map +1 -0
  101. package/lib/ReadTeamHttp.js +40 -0
  102. package/lib/ReadTeamHttp.js.map +1 -0
  103. package/lib/ReadVolume.d.ts +25 -0
  104. package/lib/ReadVolume.d.ts.map +1 -0
  105. package/lib/ReadVolume.js +3 -0
  106. package/lib/ReadVolume.js.map +1 -0
  107. package/lib/ReadVolumeHttp.d.ts +20 -0
  108. package/lib/ReadVolumeHttp.d.ts.map +1 -0
  109. package/lib/ReadVolumeHttp.js +27 -0
  110. package/lib/ReadVolumeHttp.js.map +1 -0
  111. package/lib/ReadWebhook.d.ts +32 -0
  112. package/lib/ReadWebhook.d.ts.map +1 -0
  113. package/lib/ReadWebhook.js +3 -0
  114. package/lib/ReadWebhook.js.map +1 -0
  115. package/lib/ReadWebhookHttp.d.ts +20 -0
  116. package/lib/ReadWebhookHttp.d.ts.map +1 -0
  117. package/lib/ReadWebhookHttp.js +31 -0
  118. package/lib/ReadWebhookHttp.js.map +1 -0
  119. package/lib/ReadWriteVolume.d.ts +26 -0
  120. package/lib/ReadWriteVolume.d.ts.map +1 -0
  121. package/lib/ReadWriteVolume.js +3 -0
  122. package/lib/ReadWriteVolume.js.map +1 -0
  123. package/lib/ReadWriteVolumeHttp.d.ts +20 -0
  124. package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
  125. package/lib/ReadWriteVolumeHttp.js +27 -0
  126. package/lib/ReadWriteVolumeHttp.js.map +1 -0
  127. package/lib/Sandbox.d.ts +295 -0
  128. package/lib/Sandbox.d.ts.map +1 -0
  129. package/lib/Sandbox.js +351 -0
  130. package/lib/Sandbox.js.map +1 -0
  131. package/lib/SandboxClient.d.ts +226 -0
  132. package/lib/SandboxClient.d.ts.map +1 -0
  133. package/lib/SandboxClient.js +1 -0
  134. package/lib/SandboxClient.js.map +1 -0
  135. package/lib/SandboxEvents.d.ts +87 -0
  136. package/lib/SandboxEvents.d.ts.map +1 -0
  137. package/lib/SandboxEvents.js +39 -0
  138. package/lib/SandboxEvents.js.map +1 -0
  139. package/lib/SandboxEventsLive.d.ts +13 -0
  140. package/lib/SandboxEventsLive.d.ts.map +1 -0
  141. package/lib/SandboxEventsLive.js +80 -0
  142. package/lib/SandboxEventsLive.js.map +1 -0
  143. package/lib/Secret.d.ts +105 -0
  144. package/lib/Secret.d.ts.map +1 -0
  145. package/lib/Secret.js +161 -0
  146. package/lib/Secret.js.map +1 -0
  147. package/lib/Snapshot.d.ts +118 -0
  148. package/lib/Snapshot.d.ts.map +1 -0
  149. package/lib/Snapshot.js +158 -0
  150. package/lib/Snapshot.js.map +1 -0
  151. package/lib/Template.d.ts +313 -0
  152. package/lib/Template.d.ts.map +1 -0
  153. package/lib/Template.js +421 -0
  154. package/lib/Template.js.map +1 -0
  155. package/lib/Volume.d.ts +88 -0
  156. package/lib/Volume.d.ts.map +1 -0
  157. package/lib/Volume.js +136 -0
  158. package/lib/Volume.js.map +1 -0
  159. package/lib/VolumeFiles.d.ts +39 -0
  160. package/lib/VolumeFiles.d.ts.map +1 -0
  161. package/lib/VolumeFiles.js +1 -0
  162. package/lib/VolumeFiles.js.map +1 -0
  163. package/lib/Webhook.d.ts +83 -0
  164. package/lib/Webhook.d.ts.map +1 -0
  165. package/lib/Webhook.js +138 -0
  166. package/lib/Webhook.js.map +1 -0
  167. package/lib/WriteSecret.d.ts +34 -0
  168. package/lib/WriteSecret.d.ts.map +1 -0
  169. package/lib/WriteSecret.js +3 -0
  170. package/lib/WriteSecret.js.map +1 -0
  171. package/lib/WriteSecretHttp.d.ts +20 -0
  172. package/lib/WriteSecretHttp.d.ts.map +1 -0
  173. package/lib/WriteSecretHttp.js +37 -0
  174. package/lib/WriteSecretHttp.js.map +1 -0
  175. package/lib/WriteVolume.d.ts +25 -0
  176. package/lib/WriteVolume.d.ts.map +1 -0
  177. package/lib/WriteVolume.js +3 -0
  178. package/lib/WriteVolume.js.map +1 -0
  179. package/lib/WriteVolumeHttp.d.ts +20 -0
  180. package/lib/WriteVolumeHttp.d.ts.map +1 -0
  181. package/lib/WriteVolumeHttp.js +27 -0
  182. package/lib/WriteVolumeHttp.js.map +1 -0
  183. package/lib/index.d.ts +43 -0
  184. package/lib/index.d.ts.map +1 -0
  185. package/lib/index.js +43 -0
  186. package/lib/index.js.map +1 -0
  187. package/lib/internal/Declared.d.ts +29 -0
  188. package/lib/internal/Declared.d.ts.map +1 -0
  189. package/lib/internal/Declared.js +31 -0
  190. package/lib/internal/Declared.js.map +1 -0
  191. package/lib/internal/Login.d.ts +45 -0
  192. package/lib/internal/Login.d.ts.map +1 -0
  193. package/lib/internal/Login.js +157 -0
  194. package/lib/internal/Login.js.map +1 -0
  195. package/lib/internal/Ownership.d.ts +29 -0
  196. package/lib/internal/Ownership.d.ts.map +1 -0
  197. package/lib/internal/Ownership.js +49 -0
  198. package/lib/internal/Ownership.js.map +1 -0
  199. package/lib/internal/Program.d.ts +31 -0
  200. package/lib/internal/Program.d.ts.map +1 -0
  201. package/lib/internal/Program.js +60 -0
  202. package/lib/internal/Program.js.map +1 -0
  203. package/lib/internal/SandboxHandle.d.ts +25 -0
  204. package/lib/internal/SandboxHandle.d.ts.map +1 -0
  205. package/lib/internal/SandboxHandle.js +316 -0
  206. package/lib/internal/SandboxHandle.js.map +1 -0
  207. package/lib/internal/SnapshotBuild.d.ts +19 -0
  208. package/lib/internal/SnapshotBuild.d.ts.map +1 -0
  209. package/lib/internal/SnapshotBuild.js +49 -0
  210. package/lib/internal/SnapshotBuild.js.map +1 -0
  211. package/lib/internal/Tar.d.ts +10 -0
  212. package/lib/internal/Tar.d.ts.map +1 -0
  213. package/lib/internal/Tar.js +61 -0
  214. package/lib/internal/Tar.js.map +1 -0
  215. package/lib/internal/TemplateBuild.d.ts +142 -0
  216. package/lib/internal/TemplateBuild.d.ts.map +1 -0
  217. package/lib/internal/TemplateBuild.js +220 -0
  218. package/lib/internal/TemplateBuild.js.map +1 -0
  219. package/lib/internal/TemplatePlan.d.ts +57 -0
  220. package/lib/internal/TemplatePlan.d.ts.map +1 -0
  221. package/lib/internal/TemplatePlan.js +185 -0
  222. package/lib/internal/TemplatePlan.js.map +1 -0
  223. package/lib/internal/VolumeHttp.d.ts +5 -0
  224. package/lib/internal/VolumeHttp.d.ts.map +1 -0
  225. package/lib/internal/VolumeHttp.js +46 -0
  226. package/lib/internal/VolumeHttp.js.map +1 -0
  227. package/package.json +62 -3
  228. package/src/ApiKey.ts +148 -0
  229. package/src/AuthProvider.ts +437 -0
  230. package/src/Bootstrap.ts +51 -0
  231. package/src/ConnectSandbox.ts +71 -0
  232. package/src/ConnectSandboxHttp.ts +38 -0
  233. package/src/Contributions.ts +9 -0
  234. package/src/CreateSandbox.ts +157 -0
  235. package/src/CreateSandboxHttp.ts +131 -0
  236. package/src/CreateSecret.ts +65 -0
  237. package/src/CreateSecretHttp.ts +94 -0
  238. package/src/CreateSnapshot.ts +64 -0
  239. package/src/CreateSnapshotHttp.ts +55 -0
  240. package/src/CreateTemplate.ts +135 -0
  241. package/src/CreateTemplateHttp.ts +152 -0
  242. package/src/CreateVolume.ts +65 -0
  243. package/src/CreateVolumeHttp.ts +80 -0
  244. package/src/Credentials.ts +81 -0
  245. package/src/E2BBindings.ts +160 -0
  246. package/src/Errors.ts +47 -0
  247. package/src/InferEnv.ts +27 -0
  248. package/src/MountVolume.ts +92 -0
  249. package/src/MountVolumeLive.ts +49 -0
  250. package/src/Providers.ts +48 -0
  251. package/src/ReadTeam.ts +55 -0
  252. package/src/ReadTeamHttp.ts +44 -0
  253. package/src/ReadVolume.ts +29 -0
  254. package/src/ReadVolumeHttp.ts +32 -0
  255. package/src/ReadWebhook.ts +41 -0
  256. package/src/ReadWebhookHttp.ts +42 -0
  257. package/src/ReadWriteVolume.ts +30 -0
  258. package/src/ReadWriteVolumeHttp.ts +32 -0
  259. package/src/Sandbox.ts +581 -0
  260. package/src/SandboxClient.ts +268 -0
  261. package/src/SandboxEvents.ts +101 -0
  262. package/src/SandboxEventsLive.ts +118 -0
  263. package/src/Secret.ts +229 -0
  264. package/src/Snapshot.ts +234 -0
  265. package/src/Template.ts +636 -0
  266. package/src/Volume.ts +173 -0
  267. package/src/VolumeFiles.ts +53 -0
  268. package/src/Webhook.ts +215 -0
  269. package/src/WriteSecret.ts +39 -0
  270. package/src/WriteSecretHttp.ts +43 -0
  271. package/src/WriteVolume.ts +29 -0
  272. package/src/WriteVolumeHttp.ts +32 -0
  273. package/src/globals.d.ts +2 -0
  274. package/src/index.ts +42 -0
  275. package/src/internal/Declared.ts +80 -0
  276. package/src/internal/Login.ts +210 -0
  277. package/src/internal/Ownership.ts +67 -0
  278. package/src/internal/Program.ts +75 -0
  279. package/src/internal/SandboxHandle.ts +534 -0
  280. package/src/internal/SnapshotBuild.ts +74 -0
  281. package/src/internal/Tar.ts +74 -0
  282. package/src/internal/TemplateBuild.ts +383 -0
  283. package/src/internal/TemplatePlan.ts +230 -0
  284. package/src/internal/VolumeHttp.ts +88 -0
package/src/Volume.ts ADDED
@@ -0,0 +1,173 @@
1
+ import * as volumes from "@distilled.cloud/e2b/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 { E2BUnexpectedResponse } from "./Errors.ts";
8
+ import { e2bName, isGeneratedName } from "./internal/Ownership.ts";
9
+ import type { Providers } from "./Providers.ts";
10
+
11
+ export interface VolumeProps {
12
+ /**
13
+ * Volume name, unique within the team. Letters, digits, `-` and `_`. If
14
+ * omitted, a unique name is generated from the stack, stage and logical ID.
15
+ * Changing it replaces the volume, and its files are lost.
16
+ */
17
+ name?: string;
18
+ }
19
+
20
+ export interface VolumeAttributes {
21
+ /** E2B's volume id. */
22
+ volumeId: string;
23
+ /** The volume's name. Sandboxes mount a volume by name. */
24
+ name: string;
25
+ }
26
+
27
+ export interface Volume extends Resource<
28
+ "E2B.Volume",
29
+ VolumeProps,
30
+ VolumeAttributes,
31
+ never,
32
+ Providers,
33
+ { name: string }
34
+ > {}
35
+
36
+ /**
37
+ * A persistent E2B volume: a team-wide filesystem that sandboxes mount at a
38
+ * path and that outlives every sandbox using it.
39
+ *
40
+ * Declare a volume when every sandbox shares it (a package cache, a
41
+ * dataset). Per-user and per-session volumes are created at runtime by name
42
+ * with `E2B.CreateVolume()`, and both kinds mount the same way.
43
+ *
44
+ * E2B volumes carry no labels, so Alchemy recognises its volumes by their
45
+ * generated name. A volume found under an explicit name without prior state
46
+ * is reported as unowned and is only adopted with `--adopt`.
47
+ *
48
+ * E2B enables volumes per team; on a team without them, a deploy fails
49
+ * with `VolumesNotEnabled`.
50
+ *
51
+ * ### Creating a Volume
52
+ * **Example:** A shared cache volume
53
+ * ```typescript
54
+ * export const Cache = E2B.Volume("Cache", {});
55
+ * ```
56
+ *
57
+ * **Example:** A volume with a fixed name
58
+ * ```typescript
59
+ * export const Datasets = E2B.Volume("Datasets", { name: "datasets" });
60
+ * ```
61
+ *
62
+ * ### Mounting a Volume
63
+ * **Example:** Mount it into a declared sandbox
64
+ * ```typescript
65
+ * export const Builder = E2B.Sandbox("Builder", {
66
+ * template: Agent,
67
+ * mounts: [{ volume: Cache, path: "/cache" }],
68
+ * });
69
+ * ```
70
+ *
71
+ * **Example:** Mount it into the sandboxes a Worker creates
72
+ * ```typescript
73
+ * const cache = yield* E2B.MountVolume(Cache, { path: "/cache" });
74
+ * const sandboxes = yield* E2B.CreateSandbox(Agent);
75
+ * // in fetch:
76
+ * const sandbox = yield* sandboxes.create({ mounts: [cache] });
77
+ * ```
78
+ *
79
+ * ### Reading files from outside a sandbox
80
+ * **Example:** Read a report the sandbox wrote
81
+ * ```typescript
82
+ * const reports = yield* E2B.ReadVolume(Reports);
83
+ * const report = yield* reports.readFile("/report.json");
84
+ * ```
85
+ *
86
+ * ### Referencing a volume the stack doesn't manage
87
+ * `E2B.Volume.lookup` finds an existing volume by name. Nothing is created,
88
+ * adopted or deleted.
89
+ *
90
+ * **Example:** Look one up by name
91
+ * ```typescript
92
+ * const datasets = E2B.Volume.lookup({ name: "datasets" });
93
+ * const files = yield* E2B.ReadVolume(datasets);
94
+ * ```
95
+ *
96
+ * @resource
97
+ * @product Volume
98
+ */
99
+ export const Volume = Resource<Volume>("E2B.Volume");
100
+
101
+ const toAttrs = (volume: { volumeID: string; name: string }): VolumeAttributes => ({
102
+ volumeId: volume.volumeID,
103
+ name: volume.name,
104
+ });
105
+
106
+ /** The volume with this name, if any. E2B lists a team's volumes in one page. */
107
+ const findByName = (name: string) =>
108
+ volumes.listVolumes({}).pipe(Effect.map((all) => all.find((volume) => volume.name === name)));
109
+
110
+ const observe = (volumeId: string | undefined, name: string) =>
111
+ volumeId !== undefined
112
+ ? volumes.getVolume({ volumeID: volumeId }).pipe(
113
+ Effect.map((volume): volumes.Volume | undefined => volume),
114
+ Effect.catchTag("NotFound", () => findByName(name)),
115
+ )
116
+ : findByName(name);
117
+
118
+ export const VolumeProvider = () =>
119
+ Provider.effect(
120
+ Volume,
121
+ Effect.gen(function* () {
122
+ return {
123
+ stables: ["volumeId", "name"],
124
+ diff: Effect.fn(function* ({ id, olds, news, output }) {
125
+ if (!isResolved(news)) return undefined;
126
+ // Only an explicit name can force a rename; a generated name is
127
+ // engine-owned and never compared against a fresh computation.
128
+ if (news.name === undefined && olds?.name === undefined) return undefined;
129
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
130
+ if (news.name !== name) return { action: "replace" } as const;
131
+ return undefined;
132
+ }),
133
+ lookup: Effect.fn(function* ({ name }) {
134
+ const volume = yield* observe(undefined, name);
135
+ return volume === undefined ? undefined : toAttrs(volume);
136
+ }),
137
+ read: Effect.fn(function* ({ id, olds, output }) {
138
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
139
+ const volume = yield* observe(output?.volumeId, name);
140
+ if (volume === undefined) return undefined;
141
+ const attrs = toAttrs(volume);
142
+ return output !== undefined || olds?.name === undefined ? attrs : Unowned(attrs);
143
+ }),
144
+ reconcile: Effect.fn(function* ({ id, news, output, session }) {
145
+ const name = output?.name ?? (yield* e2bName(id, news.name));
146
+ // Observe
147
+ const observed = yield* observe(output?.volumeId, name);
148
+ if (observed !== undefined) return toAttrs(observed);
149
+ // Ensure — a Conflict is a peer creating the same name: read it back.
150
+ yield* session.note(`Creating volume ${name}...`);
151
+ const created = yield* volumes.createVolume({ name }).pipe(
152
+ Effect.map((volume): volumes.Volume | undefined => volume),
153
+ Effect.catchTag("Conflict", () => findByName(name)),
154
+ );
155
+ if (created === undefined) {
156
+ return yield* new E2BUnexpectedResponse({
157
+ message: `E2B volume ${name} vanished after create`,
158
+ });
159
+ }
160
+ return toAttrs(created);
161
+ }),
162
+ delete: Effect.fn(function* ({ output }) {
163
+ yield* volumes
164
+ .deleteVolume({ volumeID: output.volumeId })
165
+ .pipe(Effect.catchTag("NotFound", () => Effect.void));
166
+ }),
167
+ list: Effect.fn(function* () {
168
+ const all = yield* volumes.listVolumes({});
169
+ return all.filter((volume) => isGeneratedName(volume.name)).map(toAttrs);
170
+ }),
171
+ };
172
+ }),
173
+ );
@@ -0,0 +1,53 @@
1
+ import type * as volumeContent from "@distilled.cloud/e2b/volume_content";
2
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
3
+ import type * as Effect from "effect/Effect";
4
+ import type { E2BError } from "./SandboxClient.ts";
5
+
6
+ /** A file, directory or symlink in a volume. */
7
+ export type VolumeEntry = volumeContent.VolumeEntryStat;
8
+
9
+ /** Ownership and permissions for a written file or directory. */
10
+ export interface VolumeEntryOptions {
11
+ readonly uid?: number;
12
+ readonly gid?: number;
13
+ /** POSIX mode, e.g. `0o644`. */
14
+ readonly mode?: number;
15
+ }
16
+
17
+ /** Read a volume's files without a sandbox. Paths are absolute inside the volume. */
18
+ export interface ReadVolumeClient {
19
+ read(path: string): Effect.Effect<Uint8Array, E2BError, RuntimeContext>;
20
+ readText(path: string): Effect.Effect<string, E2BError, RuntimeContext>;
21
+ /** Directory entries, `depth` levels deep. */
22
+ list(
23
+ path: string,
24
+ options?: { readonly depth?: number },
25
+ ): Effect.Effect<ReadonlyArray<VolumeEntry>, E2BError, RuntimeContext>;
26
+ stat(path: string): Effect.Effect<VolumeEntry, E2BError, RuntimeContext>;
27
+ exists(path: string): Effect.Effect<boolean, E2BError, RuntimeContext>;
28
+ }
29
+
30
+ /** Write a volume's files without a sandbox. Paths are absolute inside the volume. */
31
+ export interface WriteVolumeClient {
32
+ /** Writes a file, creating parent directories. */
33
+ write(
34
+ path: string,
35
+ data: string | Uint8Array,
36
+ options?: VolumeEntryOptions,
37
+ ): Effect.Effect<VolumeEntry, E2BError, RuntimeContext>;
38
+ /** Creates a directory and its parents. */
39
+ makeDir(
40
+ path: string,
41
+ options?: VolumeEntryOptions,
42
+ ): Effect.Effect<VolumeEntry, E2BError, RuntimeContext>;
43
+ /** Changes ownership or mode. */
44
+ chmod(
45
+ path: string,
46
+ options: VolumeEntryOptions,
47
+ ): Effect.Effect<VolumeEntry, E2BError, RuntimeContext>;
48
+ /** Removes a file or a directory tree. Removing a missing path succeeds. */
49
+ remove(path: string): Effect.Effect<void, E2BError, RuntimeContext>;
50
+ }
51
+
52
+ /** Read and write a volume's files without a sandbox. */
53
+ export interface ReadWriteVolumeClient extends ReadVolumeClient, WriteVolumeClient {}
package/src/Webhook.ts ADDED
@@ -0,0 +1,215 @@
1
+ import * as webhooks from "@distilled.cloud/e2b/webhooks";
2
+ import { Unowned } from "alchemy/AdoptPolicy";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Redacted from "effect/Redacted";
7
+ import { E2BUnexpectedResponse } from "./Errors.ts";
8
+ import { e2bName, isGeneratedName } from "./internal/Ownership.ts";
9
+ import type { Providers } from "./Providers.ts";
10
+
11
+ /** Sandbox lifecycle events E2B delivers to webhooks. */
12
+ export type SandboxEventType =
13
+ | "sandbox.lifecycle.created"
14
+ | "sandbox.lifecycle.killed"
15
+ | "sandbox.lifecycle.updated"
16
+ | "sandbox.lifecycle.paused"
17
+ | "sandbox.lifecycle.resumed"
18
+ | "sandbox.lifecycle.checkpointed";
19
+
20
+ export const ALL_SANDBOX_EVENTS: ReadonlyArray<SandboxEventType> = [
21
+ "sandbox.lifecycle.created",
22
+ "sandbox.lifecycle.killed",
23
+ "sandbox.lifecycle.updated",
24
+ "sandbox.lifecycle.paused",
25
+ "sandbox.lifecycle.resumed",
26
+ "sandbox.lifecycle.checkpointed",
27
+ ];
28
+
29
+ export interface WebhookProps {
30
+ /**
31
+ * Webhook name. If omitted, a unique name is generated from the stack,
32
+ * stage and logical ID. Updated in place.
33
+ */
34
+ name?: string;
35
+ /** The URL E2B POSTs each event to. */
36
+ url: string;
37
+ /**
38
+ * The events to deliver.
39
+ * @default every sandbox lifecycle event
40
+ */
41
+ events?: SandboxEventType[];
42
+ /**
43
+ * Whether E2B delivers events.
44
+ * @default true
45
+ */
46
+ enabled?: boolean;
47
+ /**
48
+ * The secret E2B signs each delivery with. The `e2b-signature` header is
49
+ * the unpadded base64 SHA-256 of this secret followed by the raw body.
50
+ * At least 32 characters.
51
+ */
52
+ signatureSecret: Redacted.Redacted<string>;
53
+ }
54
+
55
+ export interface WebhookAttributes {
56
+ /** E2B's webhook id. */
57
+ webhookId: string;
58
+ name: string;
59
+ url: string;
60
+ events: string[];
61
+ enabled: boolean;
62
+ }
63
+
64
+ export interface Webhook extends Resource<
65
+ "E2B.Webhook",
66
+ WebhookProps,
67
+ WebhookAttributes,
68
+ never,
69
+ Providers,
70
+ { name: string }
71
+ > {}
72
+
73
+ /**
74
+ * An E2B webhook: E2B POSTs every sandbox lifecycle event (created, paused,
75
+ * resumed, checkpointed, updated, killed) in your team to a URL.
76
+ *
77
+ * Use `E2B.SandboxEvents` to point a webhook at a Worker or Function and
78
+ * handle the events there; it declares this resource for you and verifies
79
+ * each delivery's signature. Declare a `Webhook` directly for an endpoint
80
+ * Alchemy does not deploy.
81
+ *
82
+ * E2B webhooks carry no labels, so Alchemy recognises its webhooks by their
83
+ * generated name.
84
+ *
85
+ * ### Creating a Webhook
86
+ * **Example:** Deliver pause and kill events to an existing endpoint
87
+ * ```typescript
88
+ * export const Lifecycle = E2B.Webhook("Lifecycle", {
89
+ * url: "https://hooks.example.com/e2b",
90
+ * events: ["sandbox.lifecycle.paused", "sandbox.lifecycle.killed"],
91
+ * signatureSecret: Config.Redacted("E2B_WEBHOOK_SECRET"),
92
+ * });
93
+ * ```
94
+ *
95
+ * ### Referencing a webhook the stack doesn't manage
96
+ * `E2B.Webhook.lookup` finds an existing webhook by name. Nothing is created,
97
+ * adopted or deleted.
98
+ *
99
+ * **Example:** Look one up by name
100
+ * ```typescript
101
+ * const audit = E2B.Webhook.lookup({ name: "audit" });
102
+ * const deliveries = yield* E2B.ReadWebhook(audit);
103
+ * ```
104
+ *
105
+ * @resource
106
+ * @product Webhook
107
+ */
108
+ export const Webhook = Resource<Webhook>("E2B.Webhook");
109
+
110
+ const toAttrs = (
111
+ webhook: webhooks.WebhookDetail | webhooks.WebhookCreation,
112
+ ): WebhookAttributes => ({
113
+ webhookId: webhook.id,
114
+ name: webhook.name,
115
+ url: webhook.url,
116
+ events: [...webhook.events],
117
+ enabled: webhook.enabled,
118
+ });
119
+
120
+ const findByName = (name: string) =>
121
+ webhooks.listWebhooks({}).pipe(Effect.map((all) => all.find((hook) => hook.name === name)));
122
+
123
+ const observe = (webhookId: string | undefined, name: string) =>
124
+ webhookId !== undefined
125
+ ? webhooks.getWebhook({ webhookID: webhookId }).pipe(
126
+ Effect.map((hook): webhooks.WebhookDetail | undefined => hook),
127
+ Effect.catchTag("NotFound", () => findByName(name)),
128
+ )
129
+ : findByName(name);
130
+
131
+ const sameEvents = (a: ReadonlyArray<string>, b: ReadonlyArray<string>) =>
132
+ a.length === b.length && [...a].sort().join() === [...b].sort().join();
133
+
134
+ export const WebhookProvider = () =>
135
+ Provider.effect(
136
+ Webhook,
137
+ Effect.gen(function* () {
138
+ return {
139
+ stables: ["webhookId"],
140
+ lookup: Effect.fn(function* ({ name }) {
141
+ const hook = yield* observe(undefined, name);
142
+ return hook === undefined ? undefined : toAttrs(hook);
143
+ }),
144
+ read: Effect.fn(function* ({ id, olds, output }) {
145
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
146
+ const hook = yield* observe(output?.webhookId, name);
147
+ if (hook === undefined) return undefined;
148
+ const attrs = toAttrs(hook);
149
+ return output !== undefined || olds?.name === undefined ? attrs : Unowned(attrs);
150
+ }),
151
+ reconcile: Effect.fn(function* ({ id, news, olds, output, session }) {
152
+ // Webhooks are renamed in place, so the desired name wins over the
153
+ // stored one; the stored id finds the webhook either way.
154
+ const name = news.name ?? output?.name ?? (yield* e2bName(id, undefined));
155
+ const events = [...(news.events ?? ALL_SANDBOX_EVENTS)];
156
+ const enabled = news.enabled ?? true;
157
+
158
+ // Observe
159
+ const observed = yield* observe(output?.webhookId, output?.name ?? name);
160
+
161
+ // Ensure
162
+ if (observed === undefined) {
163
+ yield* session.note(`Creating webhook ${name}...`);
164
+ const created = yield* webhooks.createWebhook({
165
+ name,
166
+ url: news.url,
167
+ events,
168
+ enabled,
169
+ signatureSecret: news.signatureSecret,
170
+ });
171
+ return toAttrs(created);
172
+ }
173
+
174
+ // Sync — the signing secret is write-only, so `olds` is the only
175
+ // signal that the stored one already matches.
176
+ const secretChanged =
177
+ olds === undefined ||
178
+ Redacted.value(olds.signatureSecret) !== Redacted.value(news.signatureSecret);
179
+ if (
180
+ observed.name !== name ||
181
+ observed.url !== news.url ||
182
+ observed.enabled !== enabled ||
183
+ !sameEvents(observed.events, events) ||
184
+ secretChanged
185
+ ) {
186
+ yield* webhooks.updateWebhook({
187
+ webhookID: observed.id,
188
+ name,
189
+ url: news.url,
190
+ events,
191
+ enabled,
192
+ ...(secretChanged ? { signatureSecret: news.signatureSecret } : {}),
193
+ });
194
+ const updated = yield* observe(observed.id, name);
195
+ if (updated === undefined) {
196
+ return yield* new E2BUnexpectedResponse({
197
+ message: `E2B webhook ${name} vanished after update`,
198
+ });
199
+ }
200
+ return toAttrs(updated);
201
+ }
202
+ return toAttrs(observed);
203
+ }),
204
+ delete: Effect.fn(function* ({ output }) {
205
+ yield* webhooks
206
+ .deleteWebhook({ webhookID: output.webhookId })
207
+ .pipe(Effect.catchTag("NotFound", () => Effect.void));
208
+ }),
209
+ list: Effect.fn(function* () {
210
+ const all = yield* webhooks.listWebhooks({});
211
+ return all.filter((hook) => isGeneratedName(hook.name)).map(toAttrs);
212
+ }),
213
+ };
214
+ }),
215
+ );
@@ -0,0 +1,39 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
3
+ import type * as Effect from "effect/Effect";
4
+ import type * as Redacted from "effect/Redacted";
5
+ import type { E2BError } from "./SandboxClient.ts";
6
+ import type { Secret, SecretAttributes } from "./Secret.ts";
7
+
8
+ /** Rotate one declared secret. */
9
+ export interface WriteSecretClient {
10
+ /** Stores a new version of the secret. Sandboxes resolve it on their next request. */
11
+ put(value: Redacted.Redacted<string>): Effect.Effect<SecretAttributes, E2BError, RuntimeContext>;
12
+ }
13
+
14
+ /**
15
+ * Rotate a declared {@link Secret} at runtime: a scheduled job refreshing a
16
+ * short-lived token, or a webhook storing a reissued credential. Sandboxes
17
+ * resolve the new version on their next request.
18
+ *
19
+ * A later deploy stores the declared `value` again only when that value
20
+ * changes.
21
+ *
22
+ * ### Rotating a secret
23
+ * **Example:** Store a refreshed token
24
+ * ```typescript
25
+ * const token = yield* E2B.WriteSecret(GitHubToken);
26
+ * // in a scheduled handler:
27
+ * yield* token.put(Redacted.make(fresh));
28
+ * ```
29
+ *
30
+ * @binding
31
+ * @product Secret
32
+ */
33
+ export interface WriteSecret extends Binding.Service<
34
+ WriteSecret,
35
+ "E2B.WriteSecret",
36
+ (secret: Secret) => Effect.Effect<WriteSecretClient>
37
+ > {}
38
+
39
+ export const WriteSecret = Binding.Service<WriteSecret>("E2B.WriteSecret");
@@ -0,0 +1,43 @@
1
+ import * as secrets from "@distilled.cloud/e2b/secrets";
2
+ import type * as Output from "alchemy/Output";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import { bindE2B } from "./E2BBindings.ts";
6
+ import { type Secret, toSecretAttributes } from "./Secret.ts";
7
+ import { WriteSecret, type WriteSecretClient } from "./WriteSecret.ts";
8
+
9
+ /**
10
+ * HTTP implementation of {@link WriteSecret}. Works on any host.
11
+ *
12
+ * ### Provide the layer
13
+ * **Example:** On a Worker
14
+ * ```typescript
15
+ * Effect.gen(function* () {
16
+ * const token = yield* E2B.WriteSecret(GitHubToken);
17
+ * // ...
18
+ * }).pipe(Effect.provide(E2B.WriteSecretHttp))
19
+ * ```
20
+ *
21
+ * @layer
22
+ * @product Secret
23
+ * @provides E2B.WriteSecret
24
+ */
25
+ export const WriteSecretHttp = Layer.succeed(
26
+ WriteSecret,
27
+ Effect.fn("E2B.WriteSecret")(function* (secret: Secret) {
28
+ const auth = yield* bindE2B;
29
+ const secretId = yield* (secret.secretId as Output.Output<string, never>).asEffect();
30
+ const client: WriteSecretClient = {
31
+ put: Effect.fn("E2B.WriteSecret.put")(function* (value) {
32
+ const id = yield* secretId;
33
+ // Keep the secret's metadata (ownership stamp included) as it is.
34
+ const current = yield* auth.authorize(secrets.getSecret({ secretID: id }));
35
+ const stored = yield* auth.authorize(
36
+ secrets.updateSecret({ secretID: id, value, metadata: current.metadata }),
37
+ );
38
+ return toSecretAttributes(stored);
39
+ }),
40
+ };
41
+ return client;
42
+ }),
43
+ );
@@ -0,0 +1,29 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type * as Effect from "effect/Effect";
3
+ import type { Volume } from "./Volume.ts";
4
+ import type { WriteVolumeClient } from "./VolumeFiles.ts";
5
+
6
+ /**
7
+ * Write a {@link Volume}'s files from outside any sandbox: seed inputs before sandboxes start, or clean up after them.
8
+ *
9
+ * The client talks to E2B's volume content API with the volume's own token,
10
+ * looked up once through the host's API key.
11
+ *
12
+ * ### Using the client
13
+ * **Example:** In a Worker
14
+ * ```typescript
15
+ * const inputs = yield* E2B.WriteVolume(Inputs);
16
+ * // in fetch:
17
+ * yield* inputs.write("/task.json", JSON.stringify(task));
18
+ * ```
19
+ *
20
+ * @binding
21
+ * @product Volume
22
+ */
23
+ export interface WriteVolume extends Binding.Service<
24
+ WriteVolume,
25
+ "E2B.WriteVolume",
26
+ (volume: Volume) => Effect.Effect<WriteVolumeClient>
27
+ > {}
28
+
29
+ export const WriteVolume = Binding.Service<WriteVolume>("E2B.WriteVolume");
@@ -0,0 +1,32 @@
1
+ import type * as Output from "alchemy/Output";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import { bindE2B } from "./E2BBindings.ts";
5
+ import { makeVolumeFiles } from "./internal/VolumeHttp.ts";
6
+ import type { Volume } from "./Volume.ts";
7
+ import { WriteVolume } from "./WriteVolume.ts";
8
+
9
+ /**
10
+ * HTTP implementation of {@link WriteVolume}. Works on any host.
11
+ *
12
+ * ### Provide the layer
13
+ * **Example:** On a Worker
14
+ * ```typescript
15
+ * Effect.gen(function* () {
16
+ * const inputs = yield* E2B.WriteVolume(Inputs);
17
+ * // ...
18
+ * }).pipe(Effect.provide(E2B.WriteVolumeHttp))
19
+ * ```
20
+ *
21
+ * @layer
22
+ * @product Volume
23
+ * @provides E2B.WriteVolume
24
+ */
25
+ export const WriteVolumeHttp = Layer.succeed(
26
+ WriteVolume,
27
+ Effect.fn("E2B.WriteVolume")(function* (volume: Volume) {
28
+ const auth = yield* bindE2B;
29
+ const volumeId = yield* (volume.volumeId as Output.Output<string, never>).asEffect();
30
+ return makeVolumeFiles(auth, volumeId);
31
+ }),
32
+ );
@@ -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,42 @@
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 "./CreateTemplate.ts";
12
+ export * from "./CreateTemplateHttp.ts";
13
+ export * from "./CreateVolume.ts";
14
+ export * from "./CreateVolumeHttp.ts";
15
+ export * from "./Credentials.ts";
16
+ export * from "./Errors.ts";
17
+ export * from "./InferEnv.ts";
18
+ export * from "./MountVolume.ts";
19
+ export * from "./MountVolumeLive.ts";
20
+ export * from "./Providers.ts";
21
+ export * from "./ReadTeam.ts";
22
+ export * from "./ReadTeamHttp.ts";
23
+ export * from "./ReadVolume.ts";
24
+ export * from "./ReadWebhook.ts";
25
+ export * from "./ReadWebhookHttp.ts";
26
+ export * from "./ReadVolumeHttp.ts";
27
+ export * from "./ReadWriteVolume.ts";
28
+ export * from "./ReadWriteVolumeHttp.ts";
29
+ export * from "./Sandbox.ts";
30
+ export * from "./SandboxClient.ts";
31
+ export * from "./SandboxEvents.ts";
32
+ export * from "./SandboxEventsLive.ts";
33
+ export * from "./Secret.ts";
34
+ export * from "./Snapshot.ts";
35
+ export * from "./Template.ts";
36
+ export * from "./Volume.ts";
37
+ export * from "./VolumeFiles.ts";
38
+ export * from "./Webhook.ts";
39
+ export * from "./WriteSecret.ts";
40
+ export * from "./WriteSecretHttp.ts";
41
+ export * from "./WriteVolume.ts";
42
+ export * from "./WriteVolumeHttp.ts";