@alchemy.run/daytona 0.0.0 → 2.0.0-beta.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +76 -4
  3. package/lib/ApiKey.d.ts +99 -0
  4. package/lib/ApiKey.d.ts.map +1 -0
  5. package/lib/ApiKey.js +152 -0
  6. package/lib/ApiKey.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +55 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +280 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Bootstrap.d.ts +8 -0
  12. package/lib/Bootstrap.d.ts.map +1 -0
  13. package/lib/Bootstrap.js +28 -0
  14. package/lib/Bootstrap.js.map +1 -0
  15. package/lib/ConnectSandbox.d.ts +72 -0
  16. package/lib/ConnectSandbox.d.ts.map +1 -0
  17. package/lib/ConnectSandbox.js +3 -0
  18. package/lib/ConnectSandbox.js.map +1 -0
  19. package/lib/ConnectSandboxHttp.d.ts +5 -0
  20. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  21. package/lib/ConnectSandboxHttp.js +22 -0
  22. package/lib/ConnectSandboxHttp.js.map +1 -0
  23. package/lib/Contributions.d.ts +7 -0
  24. package/lib/Contributions.d.ts.map +1 -0
  25. package/lib/Contributions.js +6 -0
  26. package/lib/Contributions.js.map +1 -0
  27. package/lib/CreateSandbox.d.ts +86 -0
  28. package/lib/CreateSandbox.d.ts.map +1 -0
  29. package/lib/CreateSandbox.js +3 -0
  30. package/lib/CreateSandbox.js.map +1 -0
  31. package/lib/CreateSandboxHttp.d.ts +5 -0
  32. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  33. package/lib/CreateSandboxHttp.js +65 -0
  34. package/lib/CreateSandboxHttp.js.map +1 -0
  35. package/lib/CreateSecret.d.ts +56 -0
  36. package/lib/CreateSecret.d.ts.map +1 -0
  37. package/lib/CreateSecret.js +3 -0
  38. package/lib/CreateSecret.js.map +1 -0
  39. package/lib/CreateSecretHttp.d.ts +5 -0
  40. package/lib/CreateSecretHttp.d.ts.map +1 -0
  41. package/lib/CreateSecretHttp.js +39 -0
  42. package/lib/CreateSecretHttp.js.map +1 -0
  43. package/lib/CreateSnapshot.d.ts +80 -0
  44. package/lib/CreateSnapshot.d.ts.map +1 -0
  45. package/lib/CreateSnapshot.js +3 -0
  46. package/lib/CreateSnapshot.js.map +1 -0
  47. package/lib/CreateSnapshotHttp.d.ts +5 -0
  48. package/lib/CreateSnapshotHttp.d.ts.map +1 -0
  49. package/lib/CreateSnapshotHttp.js +47 -0
  50. package/lib/CreateSnapshotHttp.js.map +1 -0
  51. package/lib/CreateVolume.d.ts +81 -0
  52. package/lib/CreateVolume.d.ts.map +1 -0
  53. package/lib/CreateVolume.js +10 -0
  54. package/lib/CreateVolume.js.map +1 -0
  55. package/lib/CreateVolumeHttp.d.ts +5 -0
  56. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  57. package/lib/CreateVolumeHttp.js +31 -0
  58. package/lib/CreateVolumeHttp.js.map +1 -0
  59. package/lib/Credentials.d.ts +9 -0
  60. package/lib/Credentials.d.ts.map +1 -0
  61. package/lib/Credentials.js +18 -0
  62. package/lib/Credentials.js.map +1 -0
  63. package/lib/DaytonaBindings.d.ts +51 -0
  64. package/lib/DaytonaBindings.d.ts.map +1 -0
  65. package/lib/DaytonaBindings.js +61 -0
  66. package/lib/DaytonaBindings.js.map +1 -0
  67. package/lib/IdentityProvider.d.ts +80 -0
  68. package/lib/IdentityProvider.d.ts.map +1 -0
  69. package/lib/IdentityProvider.js +111 -0
  70. package/lib/IdentityProvider.js.map +1 -0
  71. package/lib/MountVolume.d.ts +86 -0
  72. package/lib/MountVolume.d.ts.map +1 -0
  73. package/lib/MountVolume.js +27 -0
  74. package/lib/MountVolume.js.map +1 -0
  75. package/lib/OAuthClient.d.ts +14 -0
  76. package/lib/OAuthClient.d.ts.map +1 -0
  77. package/lib/OAuthClient.js +23 -0
  78. package/lib/OAuthClient.js.map +1 -0
  79. package/lib/Organization.d.ts +40 -0
  80. package/lib/Organization.d.ts.map +1 -0
  81. package/lib/Organization.js +80 -0
  82. package/lib/Organization.js.map +1 -0
  83. package/lib/OrganizationInvitation.d.ts +70 -0
  84. package/lib/OrganizationInvitation.d.ts.map +1 -0
  85. package/lib/OrganizationInvitation.js +109 -0
  86. package/lib/OrganizationInvitation.js.map +1 -0
  87. package/lib/OrganizationMember.d.ts +86 -0
  88. package/lib/OrganizationMember.d.ts.map +1 -0
  89. package/lib/OrganizationMember.js +109 -0
  90. package/lib/OrganizationMember.js.map +1 -0
  91. package/lib/OrganizationOtelConfig.d.ts +59 -0
  92. package/lib/OrganizationOtelConfig.d.ts.map +1 -0
  93. package/lib/OrganizationOtelConfig.js +87 -0
  94. package/lib/OrganizationOtelConfig.js.map +1 -0
  95. package/lib/OrganizationRole.d.ts +66 -0
  96. package/lib/OrganizationRole.d.ts.map +1 -0
  97. package/lib/OrganizationRole.js +106 -0
  98. package/lib/OrganizationRole.js.map +1 -0
  99. package/lib/OrganizationSettings.d.ts +76 -0
  100. package/lib/OrganizationSettings.d.ts.map +1 -0
  101. package/lib/OrganizationSettings.js +107 -0
  102. package/lib/OrganizationSettings.js.map +1 -0
  103. package/lib/Providers.d.ts +14 -0
  104. package/lib/Providers.d.ts.map +1 -0
  105. package/lib/Providers.js +53 -0
  106. package/lib/Providers.js.map +1 -0
  107. package/lib/ReadAuditLogs.d.ts +30 -0
  108. package/lib/ReadAuditLogs.d.ts.map +1 -0
  109. package/lib/ReadAuditLogs.js +17 -0
  110. package/lib/ReadAuditLogs.js.map +1 -0
  111. package/lib/ReadOrganization.d.ts +39 -0
  112. package/lib/ReadOrganization.d.ts.map +1 -0
  113. package/lib/ReadOrganization.js +39 -0
  114. package/lib/ReadOrganization.js.map +1 -0
  115. package/lib/ReadUsage.d.ts +31 -0
  116. package/lib/ReadUsage.d.ts.map +1 -0
  117. package/lib/ReadUsage.js +17 -0
  118. package/lib/ReadUsage.js.map +1 -0
  119. package/lib/Region.d.ts +78 -0
  120. package/lib/Region.d.ts.map +1 -0
  121. package/lib/Region.js +169 -0
  122. package/lib/Region.js.map +1 -0
  123. package/lib/Registry.d.ts +84 -0
  124. package/lib/Registry.d.ts.map +1 -0
  125. package/lib/Registry.js +97 -0
  126. package/lib/Registry.js.map +1 -0
  127. package/lib/Runner.d.ts +66 -0
  128. package/lib/Runner.d.ts.map +1 -0
  129. package/lib/Runner.js +116 -0
  130. package/lib/Runner.js.map +1 -0
  131. package/lib/Sandbox.d.ts +376 -0
  132. package/lib/Sandbox.d.ts.map +1 -0
  133. package/lib/Sandbox.js +565 -0
  134. package/lib/Sandbox.js.map +1 -0
  135. package/lib/SandboxClient.d.ts +258 -0
  136. package/lib/SandboxClient.d.ts.map +1 -0
  137. package/lib/SandboxClient.js +233 -0
  138. package/lib/SandboxClient.js.map +1 -0
  139. package/lib/Secret.d.ts +93 -0
  140. package/lib/Secret.d.ts.map +1 -0
  141. package/lib/Secret.js +130 -0
  142. package/lib/Secret.js.map +1 -0
  143. package/lib/Snapshot.d.ts +327 -0
  144. package/lib/Snapshot.d.ts.map +1 -0
  145. package/lib/Snapshot.js +447 -0
  146. package/lib/Snapshot.js.map +1 -0
  147. package/lib/Volume.d.ts +99 -0
  148. package/lib/Volume.d.ts.map +1 -0
  149. package/lib/Volume.js +152 -0
  150. package/lib/Volume.js.map +1 -0
  151. package/lib/WarmPool.d.ts +61 -0
  152. package/lib/WarmPool.d.ts.map +1 -0
  153. package/lib/WarmPool.js +81 -0
  154. package/lib/WarmPool.js.map +1 -0
  155. package/lib/Webhooks.d.ts +58 -0
  156. package/lib/Webhooks.d.ts.map +1 -0
  157. package/lib/Webhooks.js +75 -0
  158. package/lib/Webhooks.js.map +1 -0
  159. package/lib/index.d.ts +37 -0
  160. package/lib/index.d.ts.map +1 -0
  161. package/lib/index.js +36 -0
  162. package/lib/index.js.map +1 -0
  163. package/lib/internal/BuildContext.d.ts +16 -0
  164. package/lib/internal/BuildContext.d.ts.map +1 -0
  165. package/lib/internal/BuildContext.js +50 -0
  166. package/lib/internal/BuildContext.js.map +1 -0
  167. package/lib/internal/Declared.d.ts +29 -0
  168. package/lib/internal/Declared.d.ts.map +1 -0
  169. package/lib/internal/Declared.js +31 -0
  170. package/lib/internal/Declared.js.map +1 -0
  171. package/lib/internal/Derived.d.ts +40 -0
  172. package/lib/internal/Derived.d.ts.map +1 -0
  173. package/lib/internal/Derived.js +64 -0
  174. package/lib/internal/Derived.js.map +1 -0
  175. package/lib/internal/Errors.d.ts +4 -0
  176. package/lib/internal/Errors.d.ts.map +1 -0
  177. package/lib/internal/Errors.js +1 -0
  178. package/lib/internal/Errors.js.map +1 -0
  179. package/lib/internal/Labels.d.ts +20 -0
  180. package/lib/internal/Labels.d.ts.map +1 -0
  181. package/lib/internal/Labels.js +22 -0
  182. package/lib/internal/Labels.js.map +1 -0
  183. package/lib/internal/Names.d.ts +18 -0
  184. package/lib/internal/Names.d.ts.map +1 -0
  185. package/lib/internal/Names.js +25 -0
  186. package/lib/internal/Names.js.map +1 -0
  187. package/lib/internal/Organization.d.ts +27 -0
  188. package/lib/internal/Organization.d.ts.map +1 -0
  189. package/lib/internal/Organization.js +32 -0
  190. package/lib/internal/Organization.js.map +1 -0
  191. package/lib/internal/Process.d.ts +14 -0
  192. package/lib/internal/Process.d.ts.map +1 -0
  193. package/lib/internal/Process.js +15 -0
  194. package/lib/internal/Process.js.map +1 -0
  195. package/lib/internal/Program.d.ts +83 -0
  196. package/lib/internal/Program.d.ts.map +1 -0
  197. package/lib/internal/Program.js +169 -0
  198. package/lib/internal/Program.js.map +1 -0
  199. package/lib/internal/Ref.d.ts +14 -0
  200. package/lib/internal/Ref.d.ts.map +1 -0
  201. package/lib/internal/Ref.js +7 -0
  202. package/lib/internal/Ref.js.map +1 -0
  203. package/lib/internal/Scoped.d.ts +31 -0
  204. package/lib/internal/Scoped.d.ts.map +1 -0
  205. package/lib/internal/Scoped.js +48 -0
  206. package/lib/internal/Scoped.js.map +1 -0
  207. package/lib/internal/Tar.d.ts +13 -0
  208. package/lib/internal/Tar.d.ts.map +1 -0
  209. package/lib/internal/Tar.js +62 -0
  210. package/lib/internal/Tar.js.map +1 -0
  211. package/lib/internal/Toolbox.d.ts +11 -0
  212. package/lib/internal/Toolbox.d.ts.map +1 -0
  213. package/lib/internal/Toolbox.js +20 -0
  214. package/lib/internal/Toolbox.js.map +1 -0
  215. package/lib/internal/Wait.d.ts +49 -0
  216. package/lib/internal/Wait.d.ts.map +1 -0
  217. package/lib/internal/Wait.js +57 -0
  218. package/lib/internal/Wait.js.map +1 -0
  219. package/package.json +64 -3
  220. package/src/ApiKey.ts +221 -0
  221. package/src/AuthProvider.ts +406 -0
  222. package/src/Bootstrap.ts +55 -0
  223. package/src/ConnectSandbox.ts +76 -0
  224. package/src/ConnectSandboxHttp.ts +26 -0
  225. package/src/Contributions.ts +9 -0
  226. package/src/CreateSandbox.ts +107 -0
  227. package/src/CreateSandboxHttp.ts +97 -0
  228. package/src/CreateSecret.ts +73 -0
  229. package/src/CreateSecretHttp.ts +58 -0
  230. package/src/CreateSnapshot.ts +133 -0
  231. package/src/CreateSnapshotHttp.ts +77 -0
  232. package/src/CreateVolume.ts +104 -0
  233. package/src/CreateVolumeHttp.ts +50 -0
  234. package/src/Credentials.ts +37 -0
  235. package/src/DaytonaBindings.ts +111 -0
  236. package/src/IdentityProvider.ts +181 -0
  237. package/src/MountVolume.ts +122 -0
  238. package/src/OAuthClient.ts +27 -0
  239. package/src/Organization.ts +121 -0
  240. package/src/OrganizationInvitation.ts +174 -0
  241. package/src/OrganizationMember.ts +174 -0
  242. package/src/OrganizationOtelConfig.ts +138 -0
  243. package/src/OrganizationRole.ts +167 -0
  244. package/src/OrganizationSettings.ts +187 -0
  245. package/src/Providers.ts +93 -0
  246. package/src/ReadAuditLogs.ts +50 -0
  247. package/src/ReadOrganization.ts +83 -0
  248. package/src/ReadUsage.ts +51 -0
  249. package/src/Region.ts +226 -0
  250. package/src/Registry.ts +164 -0
  251. package/src/Runner.ts +166 -0
  252. package/src/Sandbox.ts +927 -0
  253. package/src/SandboxClient.ts +625 -0
  254. package/src/Secret.ts +203 -0
  255. package/src/Snapshot.ts +685 -0
  256. package/src/Volume.ts +229 -0
  257. package/src/WarmPool.ts +128 -0
  258. package/src/Webhooks.ts +121 -0
  259. package/src/globals.d.ts +2 -0
  260. package/src/index.ts +46 -0
  261. package/src/internal/BuildContext.ts +62 -0
  262. package/src/internal/Declared.ts +80 -0
  263. package/src/internal/Derived.ts +97 -0
  264. package/src/internal/Errors.ts +4 -0
  265. package/src/internal/Labels.ts +39 -0
  266. package/src/internal/Names.ts +37 -0
  267. package/src/internal/Organization.ts +40 -0
  268. package/src/internal/Process.ts +25 -0
  269. package/src/internal/Program.ts +239 -0
  270. package/src/internal/Ref.ts +15 -0
  271. package/src/internal/Scoped.ts +102 -0
  272. package/src/internal/Tar.ts +72 -0
  273. package/src/internal/Toolbox.ts +27 -0
  274. package/src/internal/Wait.ts +105 -0
@@ -0,0 +1,187 @@
1
+ import * as organizations from "@distilled.cloud/daytona/organizations";
2
+ import { deepEqual, isResolved } from "alchemy/Diff";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Duration from "effect/Duration";
6
+ import * as Effect from "effect/Effect";
7
+ import { declarable } from "./internal/Declared.ts";
8
+ import {
9
+ currentOrganizationId,
10
+ movesOrganization,
11
+ organizationIdFor,
12
+ type OrganizationRef,
13
+ } from "./internal/Organization.ts";
14
+ import type { Ref } from "./internal/Ref.ts";
15
+ import type { Providers } from "./Providers.ts";
16
+
17
+ export interface OrganizationSettingsProps {
18
+ /**
19
+ * Organization to act on, e.g. a `Daytona.Organization`. Defaults to the
20
+ * credentials' organization. Changing it replaces the resource.
21
+ */
22
+ organization?: Ref<OrganizationRef>;
23
+ /** Region new sandboxes go to when they don't name one. */
24
+ defaultRegionId?: string;
25
+ /**
26
+ * How long a sandbox create may wait in the queue for capacity before it
27
+ * fails. `null` waits indefinitely.
28
+ */
29
+ defaultQueueTimeout?: Duration.Input | null;
30
+ /** Experimental feature flags, as Daytona names them. */
31
+ experimentalConfig?: Record<string, unknown>;
32
+ }
33
+
34
+ interface Captured {
35
+ defaultRegionId: string | undefined;
36
+ defaultQueueTimeout: number | null;
37
+ experimentalConfig: unknown;
38
+ }
39
+
40
+ export interface OrganizationSettingsAttributes {
41
+ /** Organization ID. */
42
+ organizationId: string;
43
+ /** Organization name. */
44
+ name: string;
45
+ /** Default region. */
46
+ defaultRegionId: string | undefined;
47
+ /** Default queue timeout in seconds, or `null`. */
48
+ defaultQueueTimeout: number | null;
49
+ /** Experimental flags. */
50
+ experimentalConfig: unknown;
51
+ /** The settings before alchemy first changed them; restored on destroy. */
52
+ original: Captured;
53
+ }
54
+
55
+ export type OrganizationSettings = Resource<
56
+ "Daytona.OrganizationSettings",
57
+ OrganizationSettingsProps,
58
+ OrganizationSettingsAttributes,
59
+ never,
60
+ Providers
61
+ >;
62
+
63
+ /**
64
+ * Settings of the Daytona organization the credentials belong to: the
65
+ * default region, the queue timeout for creates waiting on capacity, and
66
+ * experimental flags. Only the settings you declare are changed.
67
+ *
68
+ * The organization always exists, so this resource captures the settings
69
+ * it finds on first deploy and puts them back on destroy.
70
+ *
71
+ * ### Configuring the Organization
72
+ * **Example:** Default region and a one-minute queue timeout
73
+ * ```typescript
74
+ * export const Settings = Daytona.OrganizationSettings("Settings", {
75
+ * defaultRegionId: "eu",
76
+ * defaultQueueTimeout: "1 minute",
77
+ * });
78
+ * ```
79
+ *
80
+ * @resource
81
+ * @product Organization
82
+ */
83
+ export const OrganizationSettings = declarable<OrganizationSettings>()(
84
+ Resource<OrganizationSettings>("Daytona.OrganizationSettings"),
85
+ );
86
+
87
+ const seconds = (d: Duration.Input | null) =>
88
+ d === null ? null : Math.round(Duration.toSeconds(d));
89
+
90
+ const toAttributes = (
91
+ org: organizations.Organization,
92
+ original: Captured,
93
+ ): OrganizationSettingsAttributes => ({
94
+ organizationId: org.id,
95
+ name: org.name,
96
+ defaultRegionId: org.defaultRegionId,
97
+ defaultQueueTimeout: org.defaultQueueTimeout,
98
+ experimentalConfig: org.experimentalConfig,
99
+ original,
100
+ });
101
+
102
+ const apply = Effect.fn(function* (
103
+ org: organizations.Organization,
104
+ want: {
105
+ defaultRegionId?: string | undefined;
106
+ defaultQueueTimeout?: number | null | undefined;
107
+ experimentalConfig?: unknown;
108
+ },
109
+ ) {
110
+ if (want.defaultRegionId !== undefined && want.defaultRegionId !== org.defaultRegionId) {
111
+ yield* organizations.setOrganizationDefaultRegion({
112
+ organizationId: org.id,
113
+ defaultRegionId: want.defaultRegionId,
114
+ });
115
+ }
116
+ if (
117
+ want.defaultQueueTimeout !== undefined &&
118
+ want.defaultQueueTimeout !== org.defaultQueueTimeout
119
+ ) {
120
+ yield* organizations.updateOrganizationDefaultQueueTimeout({
121
+ organizationId: org.id,
122
+ defaultQueueTimeout: want.defaultQueueTimeout,
123
+ });
124
+ }
125
+ if (
126
+ want.experimentalConfig !== undefined &&
127
+ !deepEqual(want.experimentalConfig, org.experimentalConfig)
128
+ ) {
129
+ yield* organizations.updateExperimentalConfig({
130
+ organizationId: org.id,
131
+ body: want.experimentalConfig as organizations.UpdateExperimentalConfigRequest["body"],
132
+ });
133
+ }
134
+ });
135
+
136
+ export const OrganizationSettingsProvider = () =>
137
+ Provider.effect(
138
+ OrganizationSettings,
139
+ Effect.succeed({
140
+ stables: ["organizationId", "original"],
141
+ nuke: { singleton: true },
142
+ list: () => Effect.succeed([]),
143
+ diff: Effect.fn(function* ({ news, output }) {
144
+ if (!isResolved(news) || output === undefined) return undefined;
145
+ if (yield* movesOrganization(news.organization, output))
146
+ return { action: "replace" } as const;
147
+ }),
148
+ read: Effect.fn(function* ({ output }) {
149
+ const org = yield* organizations.getOrganization({
150
+ organizationId: output?.organizationId ?? (yield* currentOrganizationId),
151
+ });
152
+ return toAttributes(
153
+ org,
154
+ output?.original ?? {
155
+ defaultRegionId: org.defaultRegionId,
156
+ defaultQueueTimeout: org.defaultQueueTimeout,
157
+ experimentalConfig: org.experimentalConfig,
158
+ },
159
+ );
160
+ }),
161
+ reconcile: Effect.fn(function* ({ news, output }) {
162
+ const organizationId = yield* organizationIdFor(news.organization);
163
+ // Observe
164
+ const org = yield* organizations.getOrganization({ organizationId });
165
+ const original = output?.original ?? {
166
+ defaultRegionId: org.defaultRegionId,
167
+ defaultQueueTimeout: org.defaultQueueTimeout,
168
+ experimentalConfig: org.experimentalConfig,
169
+ };
170
+ // Sync only what is declared, against observed settings.
171
+ yield* apply(org, {
172
+ defaultRegionId: news.defaultRegionId,
173
+ defaultQueueTimeout:
174
+ news.defaultQueueTimeout === undefined ? undefined : seconds(news.defaultQueueTimeout),
175
+ experimentalConfig: news.experimentalConfig,
176
+ });
177
+ return toAttributes(yield* organizations.getOrganization({ organizationId }), original);
178
+ }),
179
+ delete: Effect.fn(function* ({ output }) {
180
+ const org = yield* organizations
181
+ .getOrganization({ organizationId: output.organizationId })
182
+ .pipe(Effect.catchTag("OrganizationNotFound", () => Effect.undefined));
183
+ if (org === undefined) return;
184
+ yield* apply(org, output.original);
185
+ }),
186
+ }),
187
+ );
@@ -0,0 +1,93 @@
1
+ import { DockerLive } from "@alchemy.run/docker/Docker";
2
+ import { CredentialsStoreLive } from "alchemy/Auth/Credentials";
3
+ import { ProfileStoreLive } from "alchemy/Auth/Profile";
4
+ import * as Provider from "alchemy/Provider";
5
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
6
+ import * as Layer from "effect/Layer";
7
+ import { ApiKey, ApiKeyProvider } from "./ApiKey.ts";
8
+ import { DaytonaAuth } from "./AuthProvider.ts";
9
+ import * as Credentials from "./Credentials.ts";
10
+ import { IdentityProvider, IdentityProviderProvider } from "./IdentityProvider.ts";
11
+ import { Organization, OrganizationProvider } from "./Organization.ts";
12
+ import {
13
+ OrganizationInvitation,
14
+ OrganizationInvitationProvider,
15
+ } from "./OrganizationInvitation.ts";
16
+ import { OrganizationMember, OrganizationMemberProvider } from "./OrganizationMember.ts";
17
+ import {
18
+ OrganizationOtelConfig,
19
+ OrganizationOtelConfigProvider,
20
+ } from "./OrganizationOtelConfig.ts";
21
+ import { OrganizationRole, OrganizationRoleProvider } from "./OrganizationRole.ts";
22
+ import { OrganizationSettings, OrganizationSettingsProvider } from "./OrganizationSettings.ts";
23
+ import { Region, RegionProvider } from "./Region.ts";
24
+ import { Registry, RegistryProvider } from "./Registry.ts";
25
+ import { Runner, RunnerProvider } from "./Runner.ts";
26
+ import { Sandbox, SandboxProvider } from "./Sandbox.ts";
27
+ import { Secret, SecretProvider } from "./Secret.ts";
28
+ import { Snapshot, SnapshotProvider } from "./Snapshot.ts";
29
+ import { Volume, VolumeProvider } from "./Volume.ts";
30
+ import { WarmPool, WarmPoolProvider } from "./WarmPool.ts";
31
+ import { Webhooks, WebhooksProvider } from "./Webhooks.ts";
32
+
33
+ export class Providers extends Provider.ProviderCollection<Providers>()("Daytona") {}
34
+
35
+ export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
36
+
37
+ /**
38
+ * Daytona providers and credentials: every Daytona resource, and the
39
+ * Daytona AuthProvider so `alchemy profile edit` can configure it.
40
+ */
41
+ export const providers = () =>
42
+ Layer.effect(
43
+ Providers,
44
+ Provider.collection([
45
+ ApiKey,
46
+ IdentityProvider,
47
+ Organization,
48
+ OrganizationInvitation,
49
+ OrganizationMember,
50
+ OrganizationOtelConfig,
51
+ OrganizationRole,
52
+ OrganizationSettings,
53
+ Region,
54
+ Registry,
55
+ Runner,
56
+ Sandbox,
57
+ Secret,
58
+ Snapshot,
59
+ Volume,
60
+ WarmPool,
61
+ Webhooks,
62
+ ]),
63
+ ).pipe(
64
+ Layer.provide(
65
+ Layer.mergeAll(
66
+ ApiKeyProvider(),
67
+ IdentityProviderProvider(),
68
+ OrganizationProvider(),
69
+ OrganizationInvitationProvider(),
70
+ OrganizationMemberProvider(),
71
+ OrganizationOtelConfigProvider(),
72
+ OrganizationRoleProvider(),
73
+ OrganizationSettingsProvider(),
74
+ RegionProvider(),
75
+ RegistryProvider(),
76
+ RunnerProvider(),
77
+ SandboxProvider(),
78
+ SecretProvider(),
79
+ SnapshotProvider(),
80
+ VolumeProvider(),
81
+ WarmPoolProvider(),
82
+ WebhooksProvider(),
83
+ ),
84
+ ),
85
+ // Snapshots Daytona can't build are built with local Docker.
86
+ Layer.provideMerge(DockerLive),
87
+ Layer.provideMerge(Credentials.fromAuthProvider()),
88
+ Layer.provideMerge(FetchHttpClient.layer),
89
+ Layer.provideMerge(DaytonaAuth),
90
+ Layer.provideMerge(ProfileStoreLive),
91
+ Layer.provideMerge(CredentialsStoreLive),
92
+ Layer.orDie,
93
+ );
@@ -0,0 +1,50 @@
1
+ import * as audit from "@distilled.cloud/daytona/audit";
2
+ import * as Binding from "alchemy/Binding";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import { makeAccess } from "./DaytonaBindings.ts";
6
+ import { scopeModule, type OrganizationFixed, type ScopedModule } from "./internal/Scoped.ts";
7
+ import { lazyScoped } from "./ReadOrganization.ts";
8
+
9
+ /** The organization's audit log. */
10
+ export interface ReadAuditLogsClient extends ScopedModule<typeof audit, OrganizationFixed> {}
11
+
12
+ /**
13
+ * Read the organization's audit log at runtime: who created, started,
14
+ * deleted or changed what, and with which API key.
15
+ *
16
+ * The host gets its own Daytona API key with `read:audit_logs`.
17
+ *
18
+ * ### Reading the Audit Log
19
+ * **Example:** The latest entries
20
+ * ```typescript
21
+ * const audit = yield* Daytona.ReadAuditLogs();
22
+ * const page = yield* audit.getOrganizationAuditLogs({ limit: 50 });
23
+ * ```
24
+ *
25
+ * @binding
26
+ * @product Organization
27
+ */
28
+ export interface ReadAuditLogs extends Binding.Service<
29
+ ReadAuditLogs,
30
+ "Daytona.ReadAuditLogs",
31
+ () => Effect.Effect<ReadAuditLogsClient>
32
+ > {}
33
+
34
+ export const ReadAuditLogs = Binding.Service<ReadAuditLogs>("Daytona.ReadAuditLogs");
35
+
36
+ /** {@link ReadAuditLogs} over Daytona's HTTP API; works on any host. */
37
+ export const ReadAuditLogsHttp = Layer.effect(
38
+ ReadAuditLogs,
39
+ Effect.gen(function* () {
40
+ const access = yield* makeAccess;
41
+ return Effect.fn(function* () {
42
+ const { authorize, organizationId } = yield* access("AuditLogs", ["read:audit_logs"]);
43
+ return lazyScoped(organizationId, (id) =>
44
+ scopeModule<typeof audit, OrganizationFixed>(audit, { organizationId: id }, (effect) =>
45
+ authorize(effect),
46
+ ),
47
+ );
48
+ });
49
+ }),
50
+ );
@@ -0,0 +1,83 @@
1
+ import * as organizations from "@distilled.cloud/daytona/organizations";
2
+ import * as regions from "@distilled.cloud/daytona/regions";
3
+ import * as webhooks from "@distilled.cloud/daytona/webhooks";
4
+ import * as Binding from "alchemy/Binding";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Layer from "effect/Layer";
7
+ import { makeAccess } from "./DaytonaBindings.ts";
8
+ import { pick, scopeModule, type ScopedModule } from "./internal/Scoped.ts";
9
+
10
+ const ORGANIZATION_READS = [
11
+ "getGpuCapacity",
12
+ "listAvailableSandboxClasses",
13
+ "getOrganizationUsageOverview",
14
+ ] as const;
15
+
16
+ const WEBHOOK_READS = [
17
+ "getWebhookControllerAppPortalAccess",
18
+ "getWebhookControllerInitializationStatus",
19
+ ] as const;
20
+
21
+ /** Read-only views of the organization: quotas, usage, GPU capacity, regions and the webhook portal. */
22
+ export interface ReadOrganizationClient
23
+ extends
24
+ ScopedModule<Pick<typeof organizations, (typeof ORGANIZATION_READS)[number]>>,
25
+ ScopedModule<typeof regions>,
26
+ ScopedModule<Pick<typeof webhooks, (typeof WEBHOOK_READS)[number]>> {}
27
+
28
+ /**
29
+ * Read the organization's limits and usage at runtime: current usage
30
+ * against quotas, which sandbox classes and GPUs are available, and the
31
+ * shared regions. Use it to pick a region or refuse work before hitting a
32
+ * quota.
33
+ *
34
+ * The host gets its own Daytona API key with `read:limits`.
35
+ *
36
+ * ### Checking Capacity
37
+ * **Example:** Refuse a GPU job when no GPUs are free
38
+ * ```typescript
39
+ * const org = yield* Daytona.ReadOrganization();
40
+ * const capacity = yield* org.getGpuCapacity();
41
+ * ```
42
+ *
43
+ * @binding
44
+ * @product Organization
45
+ */
46
+ export interface ReadOrganization extends Binding.Service<
47
+ ReadOrganization,
48
+ "Daytona.ReadOrganization",
49
+ () => Effect.Effect<ReadOrganizationClient>
50
+ > {}
51
+
52
+ export const ReadOrganization = Binding.Service<ReadOrganization>("Daytona.ReadOrganization");
53
+
54
+ /** {@link ReadOrganization} over Daytona's HTTP API; works on any host. */
55
+ export const ReadOrganizationHttp = Layer.effect(
56
+ ReadOrganization,
57
+ Effect.gen(function* () {
58
+ const access = yield* makeAccess;
59
+ return Effect.fn(function* () {
60
+ const { authorize, organizationId } = yield* access("Organization", ["read:limits"]);
61
+ const run = <A, E, R>(effect: Effect.Effect<A, E, R>) => authorize(effect);
62
+ return lazyScoped(organizationId, (id) => ({
63
+ ...scopeModule(pick(organizations, ORGANIZATION_READS), { organizationId: id }, run),
64
+ ...scopeModule(regions, {}, run),
65
+ ...scopeModule(pick(webhooks, WEBHOOK_READS), { organizationId: id }, run),
66
+ }));
67
+ });
68
+ }),
69
+ );
70
+
71
+ /** Build a scoped client whose organization id resolves at runtime. */
72
+ export const lazyScoped = <C extends object>(
73
+ organizationId: Effect.Effect<string, never, any>,
74
+ make: (organizationId: string) => C,
75
+ ): C =>
76
+ new Proxy({} as C, {
77
+ get:
78
+ (_t, key: string) =>
79
+ (...args: unknown[]) =>
80
+ // `key` is a property of C (the Proxy only sees C's members), but a
81
+ // Proxy trap types it as string, so the lookup is untyped here.
82
+ Effect.flatMap(organizationId, (id) => (make(id) as any)[key](...args)),
83
+ });
@@ -0,0 +1,51 @@
1
+ import * as usage from "@distilled.cloud/daytona/usage";
2
+ import * as Binding from "alchemy/Binding";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import { makeAccess } from "./DaytonaBindings.ts";
6
+ import { scopeModule, type OrganizationFixed, type ScopedModule } from "./internal/Scoped.ts";
7
+ import { lazyScoped } from "./ReadOrganization.ts";
8
+
9
+ /** Usage from Daytona's analytics API: totals, charts and per-sandbox usage. */
10
+ export interface ReadUsageClient extends ScopedModule<typeof usage, OrganizationFixed> {}
11
+
12
+ /**
13
+ * Read the organization's compute usage at runtime: aggregated totals, a
14
+ * chart over time, and usage per sandbox. Use it to bill your own users
15
+ * for the sandboxes you run for them.
16
+ *
17
+ * The host gets its own Daytona API key with `read:billing`.
18
+ *
19
+ * ### Usage per Sandbox
20
+ * **Example:** Usage over the last day
21
+ * ```typescript
22
+ * const usage = yield* Daytona.ReadUsage();
23
+ * const rows = yield* usage.listOrganizationUsageSandbox({ from, to });
24
+ * ```
25
+ *
26
+ * @binding
27
+ * @product Organization
28
+ */
29
+ export interface ReadUsage extends Binding.Service<
30
+ ReadUsage,
31
+ "Daytona.ReadUsage",
32
+ () => Effect.Effect<ReadUsageClient>
33
+ > {}
34
+
35
+ export const ReadUsage = Binding.Service<ReadUsage>("Daytona.ReadUsage");
36
+
37
+ /** {@link ReadUsage} over Daytona's analytics API; works on any host. */
38
+ export const ReadUsageHttp = Layer.effect(
39
+ ReadUsage,
40
+ Effect.gen(function* () {
41
+ const access = yield* makeAccess;
42
+ return Effect.fn(function* () {
43
+ const { authorize, organizationId } = yield* access("Usage", ["read:billing"]);
44
+ return lazyScoped(organizationId, (id) =>
45
+ scopeModule<typeof usage, OrganizationFixed>(usage, { organizationId: id }, (effect) =>
46
+ authorize(effect),
47
+ ),
48
+ );
49
+ });
50
+ }),
51
+ );
package/src/Region.ts ADDED
@@ -0,0 +1,226 @@
1
+ import * as organizations from "@distilled.cloud/daytona/organizations";
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 { isOwnedName, resolveName } from "./internal/Names.ts";
9
+ import type { Providers } from "./Providers.ts";
10
+
11
+ export interface RegionProps {
12
+ /** Region name. If omitted, generated from the stack, stage and logical ID. Changing it replaces the region. */
13
+ name?: string;
14
+ /** URL of the region's sandbox proxy. */
15
+ proxyUrl?: string;
16
+ /** URL of the region's SSH gateway. */
17
+ sshGatewayUrl?: string;
18
+ /** URL of the region's snapshot manager (image registry). */
19
+ snapshotManagerUrl?: string;
20
+ /** OTLP endpoint the region's runners export to. */
21
+ otelEndpoint?: string;
22
+ /** S3 bucket for Dockerfile build contexts. */
23
+ buildContextBucket?: string;
24
+ /** Region of that bucket. */
25
+ buildContextBucketRegion?: string;
26
+ /** IAM role Daytona assumes to use the bucket. */
27
+ buildContextRoleArn?: string;
28
+ /**
29
+ * Bump a counter to rotate that credential on the next deploy.
30
+ */
31
+ rotate?: { proxyApiKey?: number; sshGatewayApiKey?: number; snapshotManagerCredentials?: number };
32
+ }
33
+
34
+ export interface RegionAttributes {
35
+ /** Region ID; what runners, snapshots and sandboxes take. */
36
+ regionId: string;
37
+ /** Region name. */
38
+ name: string;
39
+ /** API key the region's proxy authenticates with. */
40
+ proxyApiKey: Redacted.Redacted<string> | undefined;
41
+ /** API key the region's SSH gateway authenticates with. */
42
+ sshGatewayApiKey: Redacted.Redacted<string> | undefined;
43
+ /** Snapshot manager user name. */
44
+ snapshotManagerUsername: string | undefined;
45
+ /** Snapshot manager password. */
46
+ snapshotManagerPassword: Redacted.Redacted<string> | undefined;
47
+ /** Rotation counters applied. */
48
+ rotated: { proxyApiKey: number; sshGatewayApiKey: number; snapshotManagerCredentials: number };
49
+ }
50
+
51
+ export type Region = Resource<"Daytona.Region", RegionProps, RegionAttributes, never, Providers>;
52
+
53
+ /**
54
+ * A custom region: your own compute (see {@link Runner}) registered with
55
+ * Daytona, so sandboxes run on machines you operate while Daytona runs
56
+ * the control plane. Needs an organization allowed to bring its own
57
+ * compute.
58
+ *
59
+ * Daytona shows the region's API keys only when they are created or
60
+ * rotated, so alchemy keeps them in state as `Redacted` values. Bump a
61
+ * `rotate` counter to rotate one.
62
+ * @see https://www.daytona.io/docs/en/regions/
63
+ *
64
+ * ### Bringing Your Own Compute
65
+ * **Example:** A region with its own proxy and SSH gateway
66
+ * ```typescript
67
+ * export const Home = Daytona.Region("Home", {
68
+ * proxyUrl: "https://proxy.daytona.example.com",
69
+ * sshGatewayUrl: "ssh.daytona.example.com:2222",
70
+ * });
71
+ * ```
72
+ *
73
+ * @resource
74
+ * @product Region
75
+ */
76
+ export const Region = Resource<Region>("Daytona.Region");
77
+
78
+ const redact = (value: string | Redacted.Redacted<string> | null | undefined) =>
79
+ value === null || value === undefined
80
+ ? undefined
81
+ : Redacted.isRedacted(value)
82
+ ? value
83
+ : Redacted.make(value);
84
+
85
+ const getById = (id: string) =>
86
+ organizations.getRegion({ id }).pipe(Effect.catchTag("RegionNotFound", () => Effect.undefined));
87
+
88
+ const findByName = (name: string) =>
89
+ organizations
90
+ .listAvailableRegions({})
91
+ .pipe(Effect.map((all) => all.find((r) => r.name === name)));
92
+
93
+ export const RegionProvider = () =>
94
+ Provider.effect(
95
+ Region,
96
+ Effect.succeed({
97
+ stables: ["regionId"],
98
+ list: Effect.fn(function* () {
99
+ const all = yield* organizations.listAvailableRegions({});
100
+ return all
101
+ .filter((r) => r.organizationId)
102
+ .map((r) => ({
103
+ regionId: r.id,
104
+ name: r.name,
105
+ proxyApiKey: undefined,
106
+ sshGatewayApiKey: undefined,
107
+ snapshotManagerUsername: undefined,
108
+ snapshotManagerPassword: undefined,
109
+ rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
110
+ }));
111
+ }),
112
+ diff: Effect.fn(function* ({ id, news, output }) {
113
+ if (!isResolved(news) || output === undefined) return undefined;
114
+ const name = yield* resolveName(
115
+ id,
116
+ news.name,
117
+ news.name === undefined ? output.name : undefined,
118
+ );
119
+ if (name !== output.name) return { action: "replace", deleteFirst: true } as const;
120
+ }),
121
+ read: Effect.fn(function* ({ id, olds, output }) {
122
+ const name = yield* resolveName(id, olds?.name, output?.name);
123
+ const found =
124
+ (output ? yield* getById(output.regionId) : undefined) ?? (yield* findByName(name));
125
+ if (found === undefined) return undefined;
126
+ const attrs: RegionAttributes = output ?? {
127
+ regionId: found.id,
128
+ name: found.name,
129
+ proxyApiKey: undefined,
130
+ sshGatewayApiKey: undefined,
131
+ snapshotManagerUsername: undefined,
132
+ snapshotManagerPassword: undefined,
133
+ rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
134
+ };
135
+ return (yield* isOwnedName(id, found.name, output !== undefined)) ? attrs : Unowned(attrs);
136
+ }),
137
+ reconcile: Effect.fn(function* ({ id, news, output }) {
138
+ const name = yield* resolveName(id, news.name, output?.name);
139
+ const settings = {
140
+ proxyUrl: news.proxyUrl ?? null,
141
+ sshGatewayUrl: news.sshGatewayUrl ?? null,
142
+ snapshotManagerUrl: news.snapshotManagerUrl ?? null,
143
+ otelEndpoint: news.otelEndpoint ?? null,
144
+ buildContextBucket: news.buildContextBucket ?? null,
145
+ buildContextBucketRegion: news.buildContextBucketRegion ?? null,
146
+ buildContextRoleArn: news.buildContextRoleArn ?? null,
147
+ };
148
+ // Observe
149
+ const current =
150
+ (output ? yield* getById(output.regionId) : undefined) ?? (yield* findByName(name));
151
+ let attrs: RegionAttributes;
152
+ // Ensure
153
+ if (current === undefined) {
154
+ const created = yield* organizations.createRegion({ name, ...settings });
155
+ attrs = {
156
+ regionId: created.id,
157
+ name,
158
+ proxyApiKey: redact(created.proxyApiKey),
159
+ sshGatewayApiKey: redact(created.sshGatewayApiKey),
160
+ snapshotManagerUsername: created.snapshotManagerUsername ?? undefined,
161
+ snapshotManagerPassword: redact(created.snapshotManagerPassword),
162
+ rotated: {
163
+ proxyApiKey: news.rotate?.proxyApiKey ?? 0,
164
+ sshGatewayApiKey: news.rotate?.sshGatewayApiKey ?? 0,
165
+ snapshotManagerCredentials: news.rotate?.snapshotManagerCredentials ?? 0,
166
+ },
167
+ };
168
+ } else {
169
+ attrs = output ?? {
170
+ regionId: current.id,
171
+ name,
172
+ proxyApiKey: undefined,
173
+ sshGatewayApiKey: undefined,
174
+ snapshotManagerUsername: undefined,
175
+ snapshotManagerPassword: undefined,
176
+ rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
177
+ };
178
+ // Sync settings against the observed region.
179
+ const drift = (Object.keys(settings) as (keyof typeof settings)[]).some(
180
+ (k) => (current[k] ?? null) !== settings[k],
181
+ );
182
+ if (drift) yield* organizations.updateRegion({ id: current.id, ...settings });
183
+ }
184
+ // Sync rotations.
185
+ const want = news.rotate ?? {};
186
+ if ((want.proxyApiKey ?? 0) !== attrs.rotated.proxyApiKey) {
187
+ const { apiKey } = yield* organizations.regenerateProxyApiKey({ id: attrs.regionId });
188
+ attrs = {
189
+ ...attrs,
190
+ proxyApiKey: redact(apiKey),
191
+ rotated: { ...attrs.rotated, proxyApiKey: want.proxyApiKey ?? 0 },
192
+ };
193
+ }
194
+ if ((want.sshGatewayApiKey ?? 0) !== attrs.rotated.sshGatewayApiKey) {
195
+ const { apiKey } = yield* organizations.regenerateSshGatewayApiKey({
196
+ id: attrs.regionId,
197
+ });
198
+ attrs = {
199
+ ...attrs,
200
+ sshGatewayApiKey: redact(apiKey),
201
+ rotated: { ...attrs.rotated, sshGatewayApiKey: want.sshGatewayApiKey ?? 0 },
202
+ };
203
+ }
204
+ if ((want.snapshotManagerCredentials ?? 0) !== attrs.rotated.snapshotManagerCredentials) {
205
+ const creds = yield* organizations.regenerateSnapshotManagerCredentials({
206
+ id: attrs.regionId,
207
+ });
208
+ attrs = {
209
+ ...attrs,
210
+ snapshotManagerUsername: creds.username,
211
+ snapshotManagerPassword: redact(creds.password),
212
+ rotated: {
213
+ ...attrs.rotated,
214
+ snapshotManagerCredentials: want.snapshotManagerCredentials ?? 0,
215
+ },
216
+ };
217
+ }
218
+ return attrs;
219
+ }),
220
+ delete: Effect.fn(function* ({ output }) {
221
+ yield* organizations
222
+ .deleteRegion({ id: output.regionId })
223
+ .pipe(Effect.catchTag("RegionNotFound", () => Effect.void));
224
+ }),
225
+ }),
226
+ );