@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/Secret.ts ADDED
@@ -0,0 +1,229 @@
1
+ import * as secrets from "@distilled.cloud/e2b/secrets";
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 Redacted from "effect/Redacted";
8
+ import * as Stream from "effect/Stream";
9
+ import { E2BUnexpectedResponse } from "./Errors.ts";
10
+ import {
11
+ e2bName,
12
+ hasAnyAlchemyMetadata,
13
+ hasOwnershipMetadata,
14
+ ownershipMetadata,
15
+ userMetadata,
16
+ } from "./internal/Ownership.ts";
17
+ import type { Providers } from "./Providers.ts";
18
+
19
+ export interface SecretProps {
20
+ /**
21
+ * Secret name, unique within the team. E2B lowercases it. If omitted, a
22
+ * unique name is generated from the stack, stage and logical ID. Changing
23
+ * it replaces the secret.
24
+ */
25
+ name?: string;
26
+ /**
27
+ * The secret value. Write-only: E2B never returns it. Changing it stores a
28
+ * new version, which every sandbox resolves on its next request.
29
+ */
30
+ value: Redacted.Redacted<string>;
31
+ /** Your own metadata (at most 32 entries). */
32
+ metadata?: Record<string, string>;
33
+ }
34
+
35
+ export interface SecretAttributes {
36
+ /** E2B's secret id (`sec_…`). */
37
+ secretId: string;
38
+ /** The secret's canonical (lowercased) name. */
39
+ name: string;
40
+ /** The version sandboxes resolve. */
41
+ version: number;
42
+ /** Your metadata, without Alchemy's ownership stamp. */
43
+ metadata: Record<string, string>;
44
+ /**
45
+ * The placeholder E2B resolves to the secret's value at sandbox egress:
46
+ * `${e2b.secrets.<name>}`. Put it in a sandbox's network header rules; the
47
+ * value itself never enters the sandbox.
48
+ */
49
+ ref: string;
50
+ }
51
+
52
+ export interface Secret extends Resource<
53
+ "E2B.Secret",
54
+ SecretProps,
55
+ SecretAttributes,
56
+ never,
57
+ Providers,
58
+ { name: string }
59
+ > {}
60
+
61
+ /**
62
+ * An E2B secret: a write-only value that E2B injects into a sandbox's
63
+ * outbound requests, so code inside the sandbox never sees it.
64
+ *
65
+ * Reference a secret in a sandbox's network rules with its `ref`
66
+ * placeholder (`${e2b.secrets.<name>}`). E2B resolves the placeholder at
67
+ * egress, on the host, for requests to the domains you list.
68
+ *
69
+ * Alchemy stamps its stack, stage and logical ID into the secret's
70
+ * metadata to recognise the secrets it owns.
71
+ *
72
+ * ### Creating a Secret
73
+ * **Example:** A GitHub token from the stage's config
74
+ * ```typescript
75
+ * export const GitHubToken = E2B.Secret("GitHubToken", {
76
+ * value: Config.Redacted("GITHUB_TOKEN"),
77
+ * });
78
+ * ```
79
+ *
80
+ * ### Injecting a Secret at egress
81
+ * **Example:** Authenticate GitHub API calls from inside a sandbox
82
+ * ```typescript
83
+ * const github = yield* GitHubToken;
84
+ * const sandbox = yield* sandboxes.create({
85
+ * network: {
86
+ * allowOut: ["api.github.com"],
87
+ * rules: {
88
+ * "api.github.com": [{ transform: { headers: { Authorization: `Bearer ${github.ref}` } } }],
89
+ * },
90
+ * },
91
+ * });
92
+ * ```
93
+ *
94
+ * ### Rotating a Secret
95
+ * Changing `value` stores a new version in place. Sandboxes resolve the new
96
+ * version on their next request.
97
+ *
98
+ * **Example:** Rotate at runtime with `WriteSecret`
99
+ * ```typescript
100
+ * const token = yield* E2B.WriteSecret(GitHubToken);
101
+ * yield* token.put(Redacted.make(freshToken));
102
+ * ```
103
+ *
104
+ * ### Referencing a secret the stack doesn't manage
105
+ * `E2B.Secret.lookup` finds an existing secret by name. Nothing is created,
106
+ * adopted or deleted.
107
+ *
108
+ * **Example:** Look one up by name
109
+ * ```typescript
110
+ * const github = E2B.Secret.lookup({ name: "github-token" });
111
+ * const token = yield* E2B.WriteSecret(github);
112
+ * ```
113
+ *
114
+ * @resource
115
+ * @product Secret
116
+ */
117
+ export const Secret = Resource<Secret>("E2B.Secret");
118
+
119
+ /** E2B's egress placeholder for a secret name. */
120
+ export const secretRef = (name: string): string => `\${e2b.secrets.${name}}`;
121
+
122
+ export const toSecretAttributes = (secret: secrets.Secret): SecretAttributes => ({
123
+ secretId: secret.secretID,
124
+ name: secret.name,
125
+ version: secret.currentVersion,
126
+ metadata: userMetadata(secret.metadata as Record<string, string>),
127
+ ref: secretRef(secret.name),
128
+ });
129
+
130
+ /** A secret by id or canonical name (E2B accepts either). */
131
+ export const observeSecret = (idOrName: string) =>
132
+ secrets.getSecret({ secretID: idOrName }).pipe(
133
+ Effect.map((secret): secrets.Secret | undefined => secret),
134
+ Effect.catchTag("NotFound", () => Effect.undefined),
135
+ );
136
+
137
+ const sameMetadata = (a: Record<string, string>, b: Record<string, string | undefined>) => {
138
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
139
+ for (const key of keys) if (a[key] !== b[key]) return false;
140
+ return true;
141
+ };
142
+
143
+ export const SecretProvider = () =>
144
+ Provider.effect(
145
+ Secret,
146
+ Effect.gen(function* () {
147
+ return {
148
+ stables: ["secretId", "name", "ref"],
149
+ diff: Effect.fn(function* ({ id, olds, news, output }) {
150
+ if (!isResolved(news)) return undefined;
151
+ if (news.name === undefined && olds?.name === undefined) return undefined;
152
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
153
+ if (news.name?.toLowerCase() !== name) return { action: "replace" } as const;
154
+ return undefined;
155
+ }),
156
+ lookup: Effect.fn(function* ({ name }) {
157
+ const secret = yield* observeSecret(name.toLowerCase());
158
+ return secret === undefined ? undefined : toSecretAttributes(secret);
159
+ }),
160
+ read: Effect.fn(function* ({ id, olds, output }) {
161
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
162
+ const secret = yield* observeSecret(output?.secretId ?? name);
163
+ if (secret === undefined) return undefined;
164
+ const attrs = toSecretAttributes(secret);
165
+ const ours =
166
+ output !== undefined ||
167
+ (yield* hasOwnershipMetadata(id, secret.metadata as Record<string, string>));
168
+ return ours ? attrs : Unowned(attrs);
169
+ }),
170
+ reconcile: Effect.fn(function* ({ id, news, olds, output, session }) {
171
+ const name = output?.name ?? (yield* e2bName(id, news.name)).toLowerCase();
172
+ const metadata = { ...news.metadata, ...(yield* ownershipMetadata(id)) };
173
+
174
+ // Observe
175
+ let secret = yield* observeSecret(output?.secretId ?? name);
176
+
177
+ // Ensure — a Conflict is a peer creating the same name: read it back
178
+ // and fall through to the sync, which writes our value.
179
+ if (secret === undefined) {
180
+ yield* session.note(`Creating secret ${name}...`);
181
+ const created = yield* secrets.createSecret({ name, value: news.value, metadata }).pipe(
182
+ Effect.map((s): secrets.Secret | undefined => s),
183
+ Effect.catchTag("Conflict", () => Effect.undefined),
184
+ );
185
+ if (created !== undefined) return toSecretAttributes(created);
186
+ secret = yield* observeSecret(name);
187
+ if (secret === undefined) {
188
+ return yield* new E2BUnexpectedResponse({
189
+ message: `E2B secret ${name} vanished after create`,
190
+ });
191
+ }
192
+ // A secret someone else created under this name: write our value.
193
+ olds = undefined;
194
+ }
195
+
196
+ // Sync — values are write-only, so `olds` is the only signal that the
197
+ // stored value already matches. Without it (adoption, a lost write),
198
+ // store ours. Metadata is observed.
199
+ const valueChanged =
200
+ olds === undefined || Redacted.value(olds.value) !== Redacted.value(news.value);
201
+ const metadataChanged = !sameMetadata(
202
+ metadata,
203
+ secret.metadata as Record<string, string | undefined>,
204
+ );
205
+ if (valueChanged || metadataChanged) {
206
+ secret = yield* secrets.updateSecret({
207
+ secretID: secret.secretID,
208
+ value: news.value,
209
+ metadata,
210
+ });
211
+ }
212
+ return toSecretAttributes(secret);
213
+ }),
214
+ delete: Effect.fn(function* ({ output }) {
215
+ yield* secrets
216
+ .deleteSecret({ secretID: output.secretId })
217
+ .pipe(Effect.catchTag("NotFound", () => Effect.void));
218
+ }),
219
+ list: Effect.fn(function* () {
220
+ const all = Array.from(
221
+ yield* Stream.runCollect(secrets.listSecrets.items({ limit: 100 })),
222
+ );
223
+ return all
224
+ .filter((secret) => hasAnyAlchemyMetadata(secret.metadata as Record<string, string>))
225
+ .map(toSecretAttributes);
226
+ }),
227
+ };
228
+ }),
229
+ );
@@ -0,0 +1,234 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as templates from "@distilled.cloud/e2b/templates";
3
+ import { Unowned } from "alchemy/AdoptPolicy";
4
+ import { isResolved } from "alchemy/Diff";
5
+ import * as Provider from "alchemy/Provider";
6
+ import { Resource } from "alchemy/Resource";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Stream from "effect/Stream";
9
+ import { declarable } from "./internal/Declared.ts";
10
+ import { e2bName, isGeneratedName } from "./internal/Ownership.ts";
11
+ import { takeSnapshot } from "./internal/SnapshotBuild.ts";
12
+ import type { Providers } from "./Providers.ts";
13
+ import type { SandboxTemplate } from "./Sandbox.ts";
14
+
15
+ export interface SnapshotProps {
16
+ /**
17
+ * The `E2B.Template` (or `E2B.Snapshot`) the snapshot starts from.
18
+ * Rebuilding it takes the snapshot again.
19
+ */
20
+ template: SandboxTemplate;
21
+ /**
22
+ * Shell commands run in order (`bash -l -c`) before the snapshot is taken:
23
+ * clone a repository, install dependencies, warm a cache. A non-zero exit
24
+ * fails the deploy with `SnapshotSetupFailed`.
25
+ */
26
+ run: string[];
27
+ /** Environment variables for the setup commands. */
28
+ envs?: Record<string, string>;
29
+ /** Working directory for the setup commands. @default the sandbox user's home */
30
+ cwd?: string;
31
+ /**
32
+ * Capture memory as well as the filesystem, so sandboxes resume with the
33
+ * setup's processes still running. `false` captures the filesystem only
34
+ * and sandboxes cold-boot from it.
35
+ * @default true
36
+ */
37
+ memory?: boolean;
38
+ /**
39
+ * Snapshot name. If omitted, a unique name is generated from the stack,
40
+ * stage and logical ID. Changing it replaces the snapshot.
41
+ */
42
+ name?: string;
43
+ /**
44
+ * How long the setup sandbox may run, in seconds.
45
+ * @default 900
46
+ */
47
+ timeout?: number;
48
+ }
49
+
50
+ export interface SnapshotAttributes {
51
+ /** The id sandboxes are created from, e.g. `my-team/my-snapshot:default`. */
52
+ snapshotId: string;
53
+ /** The snapshot's name. */
54
+ name: string;
55
+ /** E2B's template id for the snapshot (a snapshot is stored as a template). */
56
+ templateId: string;
57
+ /** Every name the snapshot answers to. */
58
+ names: string[];
59
+ /** Content hash of the base and setup. A deploy re-snapshots only when it changes. */
60
+ setupHash: string;
61
+ }
62
+
63
+ export interface Snapshot extends Resource<
64
+ "E2B.Snapshot",
65
+ SnapshotProps,
66
+ SnapshotAttributes,
67
+ never,
68
+ Providers,
69
+ { name: string }
70
+ > {}
71
+
72
+ /**
73
+ * A warm base to fork sandboxes from: a template booted, set up with your
74
+ * commands, and snapshotted — memory included, so sandboxes created from it
75
+ * start exactly where the setup left off.
76
+ *
77
+ * Use a snapshot for work that is too slow to repeat per sandbox and does
78
+ * not belong in an image: cloning a repository, installing its
79
+ * dependencies, starting a language server. A deploy re-runs the setup only
80
+ * when the template or the commands change. E2B stores a snapshot as a
81
+ * template, so it is created from with `E2B.CreateSandbox(snapshot)` and
82
+ * deleted with its template.
83
+ *
84
+ * ### Creating a Snapshot
85
+ * **Example:** A repository with its dependencies installed
86
+ * ```typescript
87
+ * export const Agent = E2B.Template("Agent", { image: "node:22-bookworm" });
88
+ *
89
+ * export const Warm = E2B.Snapshot("Warm", {
90
+ * template: Agent,
91
+ * run: [
92
+ * "git clone https://github.com/alchemy-run/alchemy /home/user/alchemy",
93
+ * "cd /home/user/alchemy && pnpm install",
94
+ * ],
95
+ * });
96
+ * ```
97
+ *
98
+ * ### Forking sandboxes from a Snapshot
99
+ * **Example:** Every sandbox starts from the warm base
100
+ * ```typescript
101
+ * const sandboxes = yield* E2B.CreateSandbox(Warm);
102
+ * const sandbox = yield* sandboxes.create({ timeout: 600 });
103
+ * yield* sandbox.exec("cd alchemy && pnpm test");
104
+ * ```
105
+ *
106
+ * ### Referencing a snapshot the stack doesn't manage
107
+ * `E2B.Snapshot.lookup` finds an existing snapshot by name. Nothing is created,
108
+ * adopted or deleted.
109
+ *
110
+ * **Example:** Look one up by name
111
+ * ```typescript
112
+ * const warm = E2B.Snapshot.lookup({ name: "warm-repo" });
113
+ * const sandboxes = yield* E2B.CreateSandbox(warm);
114
+ * ```
115
+ *
116
+ * @resource
117
+ * @product Snapshot
118
+ */
119
+ export const Snapshot = declarable<Snapshot>()(Resource<Snapshot>("E2B.Snapshot"));
120
+
121
+ const setupHashOf = (props: SnapshotProps) =>
122
+ crypto
123
+ .createHash("sha256")
124
+ .update(
125
+ JSON.stringify({
126
+ template: props.template.templateId,
127
+ build: props.template.buildId ?? props.template.setupHash,
128
+ run: props.run,
129
+ envs: props.envs,
130
+ cwd: props.cwd,
131
+ memory: props.memory ?? true,
132
+ }),
133
+ )
134
+ .digest("hex");
135
+
136
+ /** The snapshot template, by alias. */
137
+ const observe = (name: string) =>
138
+ templates.getTemplateByAlias({ alias: name }).pipe(
139
+ Effect.flatMap((alias) => templates.getTemplate({ templateID: alias.templateID })),
140
+ Effect.map((template): templates.GetTemplateResponse | undefined => template),
141
+ Effect.catchTag("NotFound", () => Effect.undefined),
142
+ );
143
+
144
+ export const SnapshotProvider = () =>
145
+ Provider.effect(
146
+ Snapshot,
147
+ Effect.gen(function* () {
148
+ return {
149
+ stables: ["name", "templateId"],
150
+ diff: Effect.fn(function* ({ id, olds, news, output }) {
151
+ if (!isResolved(news)) return undefined;
152
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
153
+ if (news.name !== undefined && news.name !== name) return { action: "replace" } as const;
154
+ if (output !== undefined && setupHashOf(news) !== output.setupHash) {
155
+ return { action: "update" } as const;
156
+ }
157
+ return undefined;
158
+ }),
159
+ lookup: Effect.fn(function* ({ name }) {
160
+ const template = yield* observe(name);
161
+ if (template === undefined) return undefined;
162
+ return {
163
+ snapshotId: template.names[0] ?? template.templateID,
164
+ name,
165
+ templateId: template.templateID,
166
+ names: [...template.names],
167
+ setupHash: "",
168
+ } satisfies SnapshotAttributes;
169
+ }),
170
+ read: Effect.fn(function* ({ id, olds, output }) {
171
+ const name = output?.name ?? (yield* e2bName(id, olds?.name));
172
+ const template = yield* observe(name);
173
+ if (template === undefined) return undefined;
174
+ const attrs: SnapshotAttributes = {
175
+ snapshotId: output?.snapshotId ?? template.names[0] ?? template.templateID,
176
+ name,
177
+ templateId: template.templateID,
178
+ names: [...template.names],
179
+ setupHash: output?.setupHash ?? "",
180
+ };
181
+ return output !== undefined || olds?.name === undefined ? attrs : Unowned(attrs);
182
+ }),
183
+ reconcile: Effect.fn(function* ({ id, news, output, session }) {
184
+ const name = output?.name ?? (yield* e2bName(id, news.name));
185
+ const setupHash = setupHashOf(news);
186
+
187
+ // Observe — an existing ready snapshot of this exact setup is kept.
188
+ const observed = yield* observe(name);
189
+ if (
190
+ observed !== undefined &&
191
+ observed.buildStatus === "ready" &&
192
+ output?.setupHash === setupHash
193
+ ) {
194
+ return { ...output, names: [...observed.names], templateId: observed.templateID };
195
+ }
196
+
197
+ // Ensure — snapshotting under an existing name adds a new build to
198
+ // the same template.
199
+ yield* session.note(`Snapshotting ${name}...`);
200
+ const snapshot = yield* takeSnapshot(name, {
201
+ ...news,
202
+ template: news.template.templateId,
203
+ });
204
+ const template = yield* observe(name);
205
+ return {
206
+ snapshotId: snapshot.snapshotID,
207
+ name,
208
+ templateId: template?.templateID ?? snapshot.snapshotID,
209
+ names: [...snapshot.names],
210
+ setupHash,
211
+ };
212
+ }),
213
+ delete: Effect.fn(function* ({ output }) {
214
+ yield* templates
215
+ .deleteTemplate({ templateID: output.templateId })
216
+ .pipe(Effect.catchTag("NotFound", () => Effect.void));
217
+ }),
218
+ list: Effect.fn(function* () {
219
+ const all = Array.from(
220
+ yield* Stream.runCollect(templates.listTemplates.items({ limit: 100 })),
221
+ );
222
+ return all
223
+ .filter((template) => template.aliases.some(isGeneratedName))
224
+ .map((template): SnapshotAttributes => ({
225
+ snapshotId: template.names[0] ?? template.templateID,
226
+ name: template.aliases.find(isGeneratedName) ?? template.templateID,
227
+ templateId: template.templateID,
228
+ names: [...template.names],
229
+ setupHash: "",
230
+ }));
231
+ }),
232
+ };
233
+ }),
234
+ );