@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,111 @@
1
+ import {
2
+ Credentials,
3
+ DEFAULT_ANALYTICS_BASE_URL,
4
+ DEFAULT_TOOLBOX_BASE_URL,
5
+ } from "@distilled.cloud/daytona/Credentials";
6
+ import * as Binding from "alchemy/Binding";
7
+ import * as Output from "alchemy/Output";
8
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
9
+ import { createInternalTags } from "alchemy/Tags";
10
+ import * as Effect from "effect/Effect";
11
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
12
+ import type * as HttpClient from "effect/http/HttpClient";
13
+ import * as Layer from "effect/Layer";
14
+ import { ApiKey, type Permission } from "./ApiKey.ts";
15
+ import { toLabels } from "./internal/Labels.ts";
16
+
17
+ /**
18
+ * How a Daytona binding reaches the API from any host.
19
+ *
20
+ * Daytona authorizes with API keys whose permissions are checked per kind
21
+ * of object, so each binding mints its own {@link ApiKey} for its host at
22
+ * deploy, with only the permissions it needs, and the host reads the key
23
+ * back at runtime through `RuntimeContext`. The deployer's key never
24
+ * leaves the deploying machine, and the host works on any runtime
25
+ * (Cloudflare Workers, Lambda, containers) because the key travels as a
26
+ * plain secret, not a cloud identity.
27
+ */
28
+ export interface DaytonaAccess {
29
+ /**
30
+ * Run Daytona operations with the host's minted key. Provides
31
+ * `Credentials` and an `HttpClient`; the result requires only
32
+ * `RuntimeContext`.
33
+ */
34
+ readonly authorize: <A, E, R>(
35
+ effect: Effect.Effect<A, E, R>,
36
+ ) => Effect.Effect<A, E, Exclude<R, Credentials | HttpClient.HttpClient> | RuntimeContext>;
37
+ /** The organization the key acts on. */
38
+ readonly organizationId: Effect.Effect<string, never, RuntimeContext>;
39
+ }
40
+
41
+ /**
42
+ * Resolve once, when a binding's layer is built: returns a function that
43
+ * mints (at deploy) and reads back (at runtime) an API key for the current
44
+ * host, named after the host and `purpose`, with `permissions`.
45
+ */
46
+ export const makeAccess = Effect.gen(function* () {
47
+ const Key = yield* ApiKey;
48
+ return Effect.fn(function* (purpose: string, permissions: Permission[]) {
49
+ const host = yield* Binding.Host;
50
+ const key = yield* Key(`${host?.LogicalId ?? ""}${purpose}ApiKey`, { permissions });
51
+ const value = yield* key.value;
52
+ const apiBaseUrl = yield* key.apiBaseUrl;
53
+ const organizationId = yield* key.organizationId;
54
+ const authorize = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
55
+ Effect.gen(function* () {
56
+ const config = {
57
+ apiKey: yield* value,
58
+ apiBaseUrl: yield* apiBaseUrl,
59
+ analyticsBaseUrl: DEFAULT_ANALYTICS_BASE_URL,
60
+ toolboxBaseUrl: DEFAULT_TOOLBOX_BASE_URL,
61
+ organizationId: yield* organizationId,
62
+ };
63
+ return yield* effect.pipe(
64
+ Effect.provide(
65
+ Layer.mergeAll(
66
+ Layer.succeed(Credentials, Effect.succeed(config)),
67
+ FetchHttpClient.layer,
68
+ ),
69
+ ),
70
+ );
71
+ });
72
+ return {
73
+ authorize,
74
+ organizationId,
75
+ } satisfies DaytonaAccess;
76
+ });
77
+ });
78
+
79
+ /**
80
+ * The labels a runtime binding puts on what it creates: the host's
81
+ * `alchemy::stack`, `alchemy::stage` and `alchemy::id`, as Daytona label
82
+ * keys (`alchemy.stack`, `alchemy.stage`, `alchemy.id`; Daytona doesn't
83
+ * accept `::` in a key). Computed at deploy from the host's identity and
84
+ * bound into the host, so the runtime reads them back rather than
85
+ * computing them.
86
+ */
87
+ export const makeOwnerLabels = Effect.gen(function* () {
88
+ const host = yield* Binding.Host;
89
+ const id = host?.LogicalId ?? "";
90
+ const owner = Output.fromEffect(createInternalTags(id)) as unknown as {
91
+ bind(key: string): Effect.Effect<unknown>;
92
+ };
93
+ const tags = yield* owner.bind(`${id}DaytonaOwner`);
94
+ return Effect.map(
95
+ tags as Effect.Effect<Record<string, string> | undefined, never, RuntimeContext>,
96
+ (owner) => toLabels(owner),
97
+ );
98
+ });
99
+
100
+ /** The owner labels as a description, for objects that only have one. */
101
+ export const ownerDescription = (labels: Record<string, string>) =>
102
+ `${labels["alchemy.stack"]}/${labels["alchemy.stage"]}/${labels["alchemy.id"]}`;
103
+
104
+ /** User labels with the owner labels on top: a user label never overwrites `alchemy.*`. */
105
+ export const withOwnerLabels = (
106
+ user: Record<string, string> | undefined,
107
+ owner: Record<string, string>,
108
+ ) => ({
109
+ ...Object.fromEntries(Object.entries(user ?? {}).filter(([k]) => !k.startsWith("alchemy."))),
110
+ ...owner,
111
+ });
@@ -0,0 +1,181 @@
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 type * as Redacted from "effect/Redacted";
8
+ import { declarable } from "./internal/Declared.ts";
9
+ import { isOwnedName, resolveName } from "./internal/Names.ts";
10
+ import {
11
+ currentOrganizationId,
12
+ movesOrganization,
13
+ organizationIdFor,
14
+ type OrganizationRef,
15
+ } from "./internal/Organization.ts";
16
+ import { resolved, type Ref } from "./internal/Ref.ts";
17
+ import type { RoleRef } from "./OrganizationInvitation.ts";
18
+ import type { Providers } from "./Providers.ts";
19
+
20
+ export interface IdentityProviderProps {
21
+ /**
22
+ * Organization to act on, e.g. a `Daytona.Organization`. Defaults to the
23
+ * credentials' organization. Changing it replaces the resource.
24
+ */
25
+ organization?: Ref<OrganizationRef>;
26
+ /** Display name. If omitted, generated from the stack, stage and logical ID. */
27
+ name?: string;
28
+ /** OIDC issuer URL. */
29
+ issuerUrl: string;
30
+ /** OIDC client ID. */
31
+ clientId: string;
32
+ /** OIDC client secret. Updated in place. */
33
+ clientSecret: string | Redacted.Redacted<string>;
34
+ /** Scopes to request. @default "openid email profile" */
35
+ scope?: string;
36
+ /** Email domains that sign in through this provider. */
37
+ emailDomains: string[];
38
+ /** Accept any email domain. @default false */
39
+ allowAnyDomain?: boolean;
40
+ /** Trust the provider's `email_verified` claim. @default false */
41
+ trustEmailVerified?: boolean;
42
+ /** Custom roles new members get. */
43
+ defaultRoles?: Ref<RoleRef>[];
44
+ /** Whether sign-in through it is enabled. @default true */
45
+ enabled?: boolean;
46
+ }
47
+
48
+ export interface IdentityProviderAttributes {
49
+ /** Identity provider ID. */
50
+ identityProviderId: string;
51
+ /** Organization ID. */
52
+ organizationId: string;
53
+ /** Display name. */
54
+ name: string;
55
+ /** Email domains. */
56
+ emailDomains: string[];
57
+ /** Whether it is enabled. */
58
+ enabled: boolean;
59
+ }
60
+
61
+ export type IdentityProvider = Resource<
62
+ "Daytona.IdentityProvider",
63
+ IdentityProviderProps,
64
+ IdentityProviderAttributes,
65
+ never,
66
+ Providers
67
+ >;
68
+
69
+ /**
70
+ * An OIDC identity provider for single sign-on into the Daytona
71
+ * organization. Needs an organization with SSO enabled (an enterprise
72
+ * plan); other organizations reject it.
73
+ *
74
+ * ### Single Sign-On
75
+ * **Example:** Sign in with your company's OIDC provider
76
+ * ```typescript
77
+ * export const Sso = Daytona.IdentityProvider("Sso", {
78
+ * issuerUrl: "https://login.example.com",
79
+ * clientId: "daytona",
80
+ * clientSecret: Config.Redacted("OIDC_CLIENT_SECRET"),
81
+ * emailDomains: ["example.com"],
82
+ * defaultRoles: [Developer],
83
+ * });
84
+ * ```
85
+ *
86
+ * @resource
87
+ * @product Organization
88
+ */
89
+ export const IdentityProvider = declarable<IdentityProvider>()(
90
+ Resource<IdentityProvider>("Daytona.IdentityProvider"),
91
+ );
92
+
93
+ const toAttributes = (
94
+ organizationId: string,
95
+ p: { id: string; name: string; emailDomains: string[]; enabled: boolean },
96
+ ): IdentityProviderAttributes => ({
97
+ identityProviderId: p.id,
98
+ organizationId,
99
+ name: p.name,
100
+ emailDomains: p.emailDomains,
101
+ enabled: p.enabled,
102
+ });
103
+
104
+ const roleIds = (roles: IdentityProviderProps["defaultRoles"]) =>
105
+ Effect.succeed((roles ?? []).map((r) => resolved(r).roleId));
106
+
107
+ export const IdentityProviderProvider = () =>
108
+ Provider.effect(
109
+ IdentityProvider,
110
+ Effect.succeed({
111
+ stables: ["identityProviderId", "organizationId"],
112
+ list: Effect.fn(function* () {
113
+ const organizationId = yield* currentOrganizationId;
114
+ const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
115
+ return all.map((p) => toAttributes(organizationId, p));
116
+ }),
117
+ diff: Effect.fn(function* ({ news, output }) {
118
+ if (!isResolved(news) || output === undefined) return undefined;
119
+ if (yield* movesOrganization(news.organization, output))
120
+ return { action: "replace" } as const;
121
+ }),
122
+ read: Effect.fn(function* ({ id, olds, output }) {
123
+ const organizationId = output?.organizationId ?? (yield* currentOrganizationId);
124
+ const name = yield* resolveName(id, olds?.name, output?.name);
125
+ const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
126
+ const found =
127
+ all.find((p) => p.id === output?.identityProviderId) ?? all.find((p) => p.name === name);
128
+ if (found === undefined) return undefined;
129
+ const attrs = toAttributes(organizationId, found);
130
+ return (yield* isOwnedName(id, found.name, output !== undefined)) ? attrs : Unowned(attrs);
131
+ }),
132
+ reconcile: Effect.fn(function* ({ id, news, output }) {
133
+ const organizationId = yield* organizationIdFor(news.organization);
134
+ const name = yield* resolveName(id, news.name, output?.name);
135
+ const desired = {
136
+ name,
137
+ emailDomains: news.emailDomains,
138
+ allowAnyDomain: news.allowAnyDomain ?? false,
139
+ trustEmailVerified: news.trustEmailVerified ?? false,
140
+ defaultAssignedRoleIds: yield* roleIds(news.defaultRoles),
141
+ enabled: news.enabled ?? true,
142
+ };
143
+ const oidcConfig = {
144
+ issuerUrl: news.issuerUrl,
145
+ clientId: news.clientId,
146
+ clientSecret: news.clientSecret,
147
+ scope: news.scope,
148
+ };
149
+ // Observe
150
+ const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
151
+ const current =
152
+ all.find((p) => p.id === output?.identityProviderId) ?? all.find((p) => p.name === name);
153
+ // Ensure
154
+ if (current === undefined) {
155
+ const created = yield* organizations.createOrganizationIdentityProvider({
156
+ organizationId,
157
+ oidcConfig,
158
+ ...desired,
159
+ });
160
+ return toAttributes(organizationId, created);
161
+ }
162
+ // Sync — the client secret can't be read back, so the full
163
+ // configuration is written on every deploy.
164
+ const updated = yield* organizations.updateOrganizationIdentityProvider({
165
+ organizationId,
166
+ id: current.id,
167
+ oidcConfig,
168
+ ...desired,
169
+ });
170
+ return toAttributes(organizationId, updated);
171
+ }),
172
+ delete: Effect.fn(function* ({ output }) {
173
+ yield* organizations
174
+ .deleteOrganizationIdentityProvider({
175
+ organizationId: output.organizationId,
176
+ id: output.identityProviderId,
177
+ })
178
+ .pipe(Effect.catchTag("IdentityProviderNotFound", () => Effect.void));
179
+ }),
180
+ }),
181
+ );
@@ -0,0 +1,122 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import type { Sandbox } from "./Sandbox.ts";
6
+ import type { Volume } from "./Volume.ts";
7
+
8
+ /**
9
+ * A volume a mount names: a `Daytona.Volume` (or the reference
10
+ * `MountVolume` returns), or a runtime handle from `CreateVolume`.
11
+ */
12
+ export interface MountableVolume {
13
+ readonly volumeId: string | Effect.Effect<string, never, RuntimeContext>;
14
+ }
15
+
16
+ /** A volume mounted at a path: an entry of `mounts`. */
17
+ export interface VolumeMount {
18
+ /** The volume. */
19
+ readonly volume: MountableVolume;
20
+ /** Absolute path in the sandbox. */
21
+ readonly path: string;
22
+ /** Mount only this directory of the volume. */
23
+ readonly subpath?: string;
24
+ }
25
+
26
+ export interface MountVolumeOptions {
27
+ /** Absolute path in the sandbox. */
28
+ readonly path: string;
29
+ /** Mount only this directory of the volume. */
30
+ readonly subpath?: string;
31
+ }
32
+
33
+ /** The volume ID behind a mount entry. */
34
+ export const volumeIdOf = (volume: MountableVolume) =>
35
+ typeof volume.volumeId === "string" ? Effect.succeed(volume.volumeId) : volume.volumeId;
36
+
37
+ const isSandboxHost = (value: unknown): value is Sandbox =>
38
+ typeof value === "object" &&
39
+ value !== null &&
40
+ (value as { Type?: string }).Type === "Daytona.Sandbox";
41
+
42
+ /**
43
+ * Mount a {@link Volume} at a path.
44
+ *
45
+ * Inside a sandbox's program, the volume is mounted into that sandbox:
46
+ * it joins the sandbox's `mounts`. A mount change replaces the sandbox,
47
+ * since Daytona only mounts volumes when it creates one. Inside any other
48
+ * host (a Worker, a Lambda), the binding resolves the volume and returns a
49
+ * mount reference to pass to `CreateSandbox`'s `create({ mounts })`.
50
+ *
51
+ * The reference has the same fields as a `mounts` entry: `volume`,
52
+ * `path` and `subpath`. Daytona has no per-volume access control, so the
53
+ * binding mints no credentials.
54
+ *
55
+ * ### Mounting a Volume in a Program
56
+ * **Example:** Serve the files of a volume
57
+ * ```typescript
58
+ * export default class Agent extends Daytona.Sandbox<Agent>()(
59
+ * "Agent",
60
+ * { main: import.meta.url, public: true },
61
+ * Effect.gen(function* () {
62
+ * const data = yield* Daytona.MountVolume(Data, { path: "/data" });
63
+ * const fs = yield* FileSystem.FileSystem;
64
+ * return {
65
+ * fetch: Effect.gen(function* () {
66
+ * return yield* HttpServerResponse.json(yield* fs.readDirectory(data.path));
67
+ * }),
68
+ * };
69
+ * }).pipe(Effect.provide(Daytona.MountVolumeLive)),
70
+ * ) {}
71
+ * ```
72
+ *
73
+ * ### Mounting a Volume in Sandboxes You Create
74
+ * **Example:** A shared cache plus each user's home
75
+ * ```typescript
76
+ * const cache = yield* Daytona.MountVolume(Cache, { path: "/cache" });
77
+ * const sandboxes = yield* Daytona.CreateSandbox(Base);
78
+ * const volumes = yield* Daytona.CreateVolume({ prefix: "user-" });
79
+ * return {
80
+ * fetch: Effect.gen(function* () {
81
+ * const home = yield* volumes.getOrCreate({ name: `user-${user}` });
82
+ * const sandbox = yield* sandboxes.create({
83
+ * mounts: [cache, { volume: home, path: "/home/daytona/work" }],
84
+ * });
85
+ * return yield* HttpServerResponse.json(yield* sandbox.exec("ls /cache"));
86
+ * }),
87
+ * };
88
+ * ```
89
+ *
90
+ * @binding
91
+ * @product Volume
92
+ */
93
+ export interface MountVolume extends Binding.Service<
94
+ MountVolume,
95
+ "Daytona.MountVolume",
96
+ (volume: Volume, options: MountVolumeOptions) => Effect.Effect<VolumeMount>
97
+ > {}
98
+
99
+ export const MountVolume = Binding.Service<MountVolume>("Daytona.MountVolume");
100
+
101
+ /** {@link MountVolume}; works on any host. */
102
+ export const MountVolumeLive = Layer.effect(
103
+ MountVolume,
104
+ Effect.succeed(
105
+ Effect.fn(function* (volume: Volume, options: MountVolumeOptions) {
106
+ const volumeId = yield* volume.volumeId;
107
+ if (!globalThis.__ALCHEMY_RUNTIME__) {
108
+ const host = yield* Binding.Host;
109
+ if (isSandboxHost(host)) {
110
+ yield* host.bind`Daytona.MountVolume(${volume})`({
111
+ mounts: [{ volumeId: volume.volumeId, path: options.path, subpath: options.subpath }],
112
+ });
113
+ }
114
+ }
115
+ return {
116
+ volume: { volumeId },
117
+ path: options.path,
118
+ subpath: options.subpath,
119
+ } satisfies VolumeMount;
120
+ }),
121
+ ),
122
+ );
@@ -0,0 +1,27 @@
1
+ import { makeOAuthClient } from "alchemy/Auth/OAuthFlow";
2
+
3
+ export { OAuthCredentials, OAuthError, type DeviceAuthorization } from "alchemy/Auth/OAuthFlow";
4
+
5
+ /**
6
+ * The Daytona CLI's public WorkOS client (no secret), published by Daytona's
7
+ * `GET /api/config` under `oidc.cli`. `daytona login` uses the same device
8
+ * flow. A user session can call what an organization API key can't: the
9
+ * organization, role, invitation and API key endpoints.
10
+ */
11
+ export const OAUTH_CLIENT_ID = "client_01M392QZWMYD01SA05BPEYQ84V";
12
+
13
+ export const OAUTH_ENDPOINTS = {
14
+ deviceAuthorization: "https://api.auth.daytona.io/user_management/authorize/device",
15
+ token: "https://api.auth.daytona.io/user_management/authenticate",
16
+ };
17
+
18
+ const client = makeOAuthClient({
19
+ clientId: OAUTH_CLIENT_ID,
20
+ endpoints: OAUTH_ENDPOINTS,
21
+ auth: { kind: "pkce" },
22
+ // WorkOS answers on api.auth.daytona.io but serves the device page on
23
+ // auth.daytona.io.
24
+ verificationHosts: ["auth.daytona.io"],
25
+ });
26
+
27
+ export const { deviceAuthorize, deviceToken, refresh, usesCurrentClient } = client;
@@ -0,0 +1,121 @@
1
+ import * as organizations from "@distilled.cloud/daytona/organizations";
2
+ import { isResolved } from "alchemy/Diff";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Effect from "effect/Effect";
6
+ import { resolveName } from "./internal/Names.ts";
7
+ import type { Providers } from "./Providers.ts";
8
+
9
+ export interface OrganizationProps {
10
+ /** Organization name. If omitted, generated from the stack, stage and logical ID. */
11
+ name?: string;
12
+ /**
13
+ * Region new sandboxes go to by default. Daytona sets it once, so changing
14
+ * it replaces the organization.
15
+ */
16
+ defaultRegionId: string;
17
+ }
18
+
19
+ export interface OrganizationAttributes {
20
+ /** Organization ID. */
21
+ organizationId: string;
22
+ /** Name. */
23
+ name: string;
24
+ /** Default region. */
25
+ defaultRegionId: string | undefined;
26
+ }
27
+
28
+ export type Organization = Resource<
29
+ "Daytona.Organization",
30
+ OrganizationProps,
31
+ OrganizationAttributes,
32
+ never,
33
+ Providers
34
+ >;
35
+
36
+ /**
37
+ * A new Daytona organization, owned by the signed-in user. Creating
38
+ * organizations needs a user login, not an organization API key: an API
39
+ * key already belongs to exactly one organization. To configure the
40
+ * organization your key belongs to, use {@link OrganizationSettings}.
41
+ *
42
+ * ### Creating an Organization
43
+ * **Example:** A separate organization for a team
44
+ * ```typescript
45
+ * export const Team = Daytona.Organization("Team", { defaultRegionId: "us" });
46
+ * ```
47
+ *
48
+ * @resource
49
+ * @product Organization
50
+ */
51
+ export const Organization = Resource<Organization>("Daytona.Organization");
52
+
53
+ const toAttributes = (o: organizations.Organization): OrganizationAttributes => ({
54
+ organizationId: o.id,
55
+ name: o.name,
56
+ defaultRegionId: o.defaultRegionId,
57
+ });
58
+
59
+ export const OrganizationProvider = () =>
60
+ Provider.effect(
61
+ Organization,
62
+ Effect.succeed({
63
+ stables: ["organizationId"],
64
+ nuke: { skip: true },
65
+ list: () => Effect.succeed([]),
66
+ diff: Effect.fn(function* ({ id, news, output }) {
67
+ if (!isResolved(news) || output === undefined) return undefined;
68
+ const name = yield* resolveName(
69
+ id,
70
+ news.name,
71
+ news.name === undefined ? output.name : undefined,
72
+ );
73
+ // Reconcile finds an organization by name, so the old one must go
74
+ // first or the replacement would adopt it.
75
+ if (
76
+ name !== output.name ||
77
+ (output.defaultRegionId !== undefined && news.defaultRegionId !== output.defaultRegionId)
78
+ ) {
79
+ return { action: "replace", deleteFirst: true } as const;
80
+ }
81
+ }),
82
+ read: Effect.fn(function* ({ output }) {
83
+ if (output === undefined) return undefined;
84
+ return yield* organizations.getOrganization({ organizationId: output.organizationId }).pipe(
85
+ Effect.map(toAttributes),
86
+ Effect.catchTag("OrganizationNotFound", () => Effect.undefined),
87
+ );
88
+ }),
89
+ reconcile: Effect.fn(function* ({ id, news, output }) {
90
+ const name = yield* resolveName(id, news.name, output?.name);
91
+ // Observe
92
+ let current = output
93
+ ? yield* organizations
94
+ .getOrganization({ organizationId: output.organizationId })
95
+ .pipe(Effect.catchTag("OrganizationNotFound", () => Effect.undefined))
96
+ : (yield* organizations.listOrganizations({})).find((o) => o.name === name);
97
+ // Ensure
98
+ if (current === undefined) {
99
+ current = yield* organizations.createOrganization({
100
+ name,
101
+ defaultRegionId: news.defaultRegionId,
102
+ });
103
+ }
104
+ // Sync: Daytona only accepts a default region while none is set
105
+ // (an adopted organization); changing one replaces (see diff).
106
+ if (current.defaultRegionId === undefined) {
107
+ yield* organizations.setOrganizationDefaultRegion({
108
+ organizationId: current.id,
109
+ defaultRegionId: news.defaultRegionId,
110
+ });
111
+ current = yield* organizations.getOrganization({ organizationId: current.id });
112
+ }
113
+ return toAttributes(current);
114
+ }),
115
+ delete: Effect.fn(function* ({ output }) {
116
+ yield* organizations
117
+ .deleteOrganization({ organizationId: output.organizationId })
118
+ .pipe(Effect.catchTag("OrganizationNotFound", () => Effect.void));
119
+ }),
120
+ }),
121
+ );