alchemy 2.0.0-beta.2 → 2.0.0-beta.4

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 (77) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +171 -171
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.d.ts.map +1 -1
  5. package/lib/cli/index.js +167 -167
  6. package/lib/cli/index.js.map +1 -1
  7. package/package.json +54 -45
  8. package/src/AWS/AGENTS.md +10 -10
  9. package/src/AWS/Assets.ts +1 -1
  10. package/src/AWS/AuthProvider.ts +392 -0
  11. package/src/AWS/Credentials.ts +0 -1
  12. package/src/AWS/DynamoDB/Table.ts +63 -10
  13. package/src/AWS/EC2/VpcEndpoint.ts +4 -4
  14. package/src/AWS/EC2/hosted.ts +1 -1
  15. package/src/AWS/ECS/Task.ts +1 -1
  16. package/src/AWS/Kinesis/Stream.ts +44 -8
  17. package/src/AWS/Lambda/Function.ts +218 -10
  18. package/src/AWS/S3/Bucket.ts +26 -19
  19. package/src/AWS/S3/BucketNotifications.ts +1 -1
  20. package/src/AWS/SNS/Topic.ts +47 -10
  21. package/src/AWS/SQS/Queue.ts +66 -0
  22. package/src/Auth/AuthProvider.ts +33 -0
  23. package/src/Auth/Credentials.ts +56 -0
  24. package/src/Auth/Env.ts +45 -0
  25. package/src/Auth/Profile.ts +72 -0
  26. package/src/Auth/index.ts +2 -0
  27. package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
  28. package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
  29. package/src/Cloudflare/Container/Container.ts +122 -0
  30. package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
  31. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  32. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  33. package/src/Cloudflare/D1/D1Database.ts +23 -4
  34. package/src/Cloudflare/KV/KVNamespace.ts +25 -0
  35. package/src/Cloudflare/Providers.ts +1 -6
  36. package/src/Cloudflare/R2/R2Bucket.ts +45 -2
  37. package/src/Cloudflare/Website/StaticSite.ts +101 -15
  38. package/src/Cloudflare/Website/Vite.ts +86 -20
  39. package/src/Cloudflare/Workers/Assets.ts +170 -207
  40. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
  41. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  42. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  43. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
  44. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  45. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  46. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  47. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  48. package/src/Cloudflare/Workers/Worker.ts +572 -67
  49. package/src/Cloudflare/Workers/Workflow.ts +53 -11
  50. package/src/Cloudflare/Workers/index.ts +4 -1
  51. package/src/Construct.ts +2 -2
  52. package/src/GitHub/Comment.ts +224 -0
  53. package/src/GitHub/Secret.ts +257 -0
  54. package/src/GitHub/Variable.ts +166 -0
  55. package/src/GitHub/index.ts +3 -0
  56. package/src/Kubernetes/client.ts +2 -2
  57. package/src/Output.ts +1 -1
  58. package/src/Platform.ts +10 -5
  59. package/src/Provider.ts +1 -1
  60. package/src/Resource.ts +4 -4
  61. package/src/Test/Vitest.ts +4 -3
  62. package/src/Util/Clank.ts +77 -0
  63. package/src/Util/PlatformServices.ts +21 -0
  64. package/src/Util/dedent.ts +59 -0
  65. package/src/Util/index.ts +1 -0
  66. package/bin/alchemy-effect.js.map +0 -1
  67. package/src/Cloudflare/Workers/DurableObject.ts +0 -527
  68. package/src/Daemon/Client.ts +0 -116
  69. package/src/Daemon/Config.ts +0 -28
  70. package/src/Daemon/Errors.ts +0 -48
  71. package/src/Daemon/Lock.ts +0 -162
  72. package/src/Daemon/ProcessRegistry.ts +0 -284
  73. package/src/Daemon/RpcSchema.ts +0 -43
  74. package/src/Daemon/RpcServer.ts +0 -231
  75. package/src/Daemon/index.ts +0 -27
  76. package/src/Spawn.ts +0 -23
  77. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -0,0 +1,166 @@
1
+ import { Octokit } from "@octokit/rest";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Provider from "../Provider.ts";
4
+ import { Resource } from "../Resource.ts";
5
+
6
+ export interface VariableProps {
7
+ /**
8
+ * Repository owner (user or organization).
9
+ */
10
+ owner: string;
11
+
12
+ /**
13
+ * Repository name.
14
+ */
15
+ repository: string;
16
+
17
+ /**
18
+ * Variable name (e.g. `AWS_ROLE_ARN`).
19
+ */
20
+ name: string;
21
+
22
+ /**
23
+ * Variable value.
24
+ */
25
+ value: string;
26
+
27
+ /**
28
+ * GitHub API token. If not provided, falls back to
29
+ * `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
30
+ */
31
+ token?: string;
32
+ }
33
+
34
+ export interface Variable extends Resource<
35
+ "GitHub.Variable",
36
+ VariableProps,
37
+ {
38
+ /**
39
+ * ISO-8601 timestamp of the last update.
40
+ */
41
+ updatedAt: string;
42
+ }
43
+ > {}
44
+
45
+ /**
46
+ * A GitHub Actions repository variable.
47
+ *
48
+ * `Variable` manages the lifecycle of a plain-text configuration variable
49
+ * in GitHub Actions. Variables are visible in workflow logs and are
50
+ * suitable for non-sensitive configuration like region names, environment
51
+ * labels, or feature flags. For sensitive values, use `GitHub.Secret`
52
+ * instead.
53
+ *
54
+ * Authentication is resolved in order: explicit `token` prop,
55
+ * `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
56
+ * `repo` scope for private repositories or `public_repo` for public ones.
57
+ *
58
+ * @section Repository Variables
59
+ * Store variables accessible to all GitHub Actions workflows in the
60
+ * repository.
61
+ *
62
+ * @example Create a Repository Variable
63
+ * ```typescript
64
+ * yield* GitHub.Variable("aws-region", {
65
+ * owner: "my-org",
66
+ * repository: "my-repo",
67
+ * name: "AWS_REGION",
68
+ * value: "us-east-1",
69
+ * });
70
+ * ```
71
+ *
72
+ * @section Wiring with Other Resources
73
+ * Pass output attributes from other resources into GitHub variables so
74
+ * that CI workflows can reference them.
75
+ *
76
+ * @example Store a Worker URL for CI
77
+ * ```typescript
78
+ * const worker = yield* Cloudflare.Worker("Api", { ... });
79
+ *
80
+ * yield* GitHub.Variable("api-url", {
81
+ * owner: "my-org",
82
+ * repository: "my-repo",
83
+ * name: "API_URL",
84
+ * value: worker.url!,
85
+ * });
86
+ * ```
87
+ *
88
+ * @example Multiple Variables
89
+ * ```typescript
90
+ * yield* GitHub.Variable("region", {
91
+ * owner: "my-org",
92
+ * repository: "my-repo",
93
+ * name: "AWS_REGION",
94
+ * value: "us-east-1",
95
+ * });
96
+ *
97
+ * yield* GitHub.Variable("stage", {
98
+ * owner: "my-org",
99
+ * repository: "my-repo",
100
+ * name: "DEPLOY_STAGE",
101
+ * value: "production",
102
+ * });
103
+ * ```
104
+ */
105
+ export const Variable = Resource<Variable>("GitHub.Variable");
106
+
107
+ function resolveToken(props: VariableProps): string | undefined {
108
+ return (
109
+ props.token ?? process.env.GITHUB_ACCESS_TOKEN ?? process.env.GITHUB_TOKEN
110
+ );
111
+ }
112
+
113
+ function createClient(props: VariableProps): Octokit {
114
+ return new Octokit({ auth: resolveToken(props) });
115
+ }
116
+
117
+ export const VariableProvider = () =>
118
+ Provider.succeed(Variable, {
119
+ create: Effect.fn(function* ({ news }) {
120
+ const octokit = createClient(news);
121
+
122
+ yield* Effect.tryPromise(() =>
123
+ octokit.rest.actions.createRepoVariable({
124
+ owner: news.owner,
125
+ repo: news.repository,
126
+ name: news.name,
127
+ value: news.value,
128
+ }),
129
+ );
130
+
131
+ return { updatedAt: new Date().toISOString() };
132
+ }),
133
+
134
+ update: Effect.fn(function* ({ news }) {
135
+ const octokit = createClient(news);
136
+
137
+ yield* Effect.tryPromise(() =>
138
+ octokit.rest.actions.updateRepoVariable({
139
+ owner: news.owner,
140
+ repo: news.repository,
141
+ name: news.name,
142
+ value: news.value,
143
+ }),
144
+ );
145
+
146
+ return { updatedAt: new Date().toISOString() };
147
+ }),
148
+
149
+ delete: Effect.fn(function* ({ olds }) {
150
+ const octokit = createClient(olds);
151
+
152
+ yield* Effect.tryPromise(async () => {
153
+ try {
154
+ await octokit.rest.actions.deleteRepoVariable({
155
+ owner: olds.owner,
156
+ repo: olds.repository,
157
+ name: olds.name,
158
+ });
159
+ } catch (error: any) {
160
+ if (error.status !== 404) {
161
+ throw error;
162
+ }
163
+ }
164
+ });
165
+ }),
166
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./Comment.ts";
2
+ export * from "./Secret.ts";
3
+ export * from "./Variable.ts";
@@ -1,6 +1,6 @@
1
- import { AwsClient } from "aws4fetch";
2
1
  import { Credentials } from "@distilled.cloud/aws/Credentials";
3
2
  import { Region } from "@distilled.cloud/aws/Region";
3
+ import { AwsClient } from "aws4fetch";
4
4
  import * as Data from "effect/Data";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as Redacted from "effect/Redacted";
@@ -28,7 +28,7 @@ export interface KubernetesClusterConnection {
28
28
  certificateAuthorityData: string;
29
29
  }
30
30
 
31
- const fieldManager = "alchemy-effect";
31
+ const fieldManager = "alchemy";
32
32
 
33
33
  const createBearerToken = Effect.fn(function* (clusterName: string) {
34
34
  const credentials = yield* yield* Credentials;
package/src/Output.ts CHANGED
@@ -90,7 +90,7 @@ export abstract class BaseExpr<A = any, Req = any> implements Output<A, Req> {
90
90
  declare readonly A: A;
91
91
  declare readonly src: ResourceLike;
92
92
  declare readonly req: Req;
93
- // we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy-effect module imports
93
+ // we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy module imports
94
94
  constructor() {}
95
95
  as<T>(): Output<T, Req> {
96
96
  return this as any;
package/src/Platform.ts CHANGED
@@ -33,7 +33,7 @@ export type Main<Services = never> = void | {
33
33
  };
34
34
 
35
35
  export type Rpc<Shape> = {
36
- "~alchemy-effect/rpc": Shape;
36
+ "~alchemy/rpc": Shape;
37
37
  };
38
38
 
39
39
  // services provided to the Resource
@@ -220,10 +220,15 @@ export const Platform = <
220
220
  // return new Response("Hello, world!");
221
221
  // }
222
222
  // }
223
- resource(id, {
224
- ...props,
225
- isExternal: true,
226
- })
223
+ resource(
224
+ id,
225
+ Effect.isEffect(props)
226
+ ? Effect.map(props, (p: any) => ({ ...p, isExternal: true }))
227
+ : {
228
+ ...props,
229
+ isExternal: true,
230
+ },
231
+ )
227
232
  : Effect.flatMap(
228
233
  // this is a tagged resource
229
234
  Effect.serviceOption(cls.Self),
package/src/Provider.ts CHANGED
@@ -369,7 +369,7 @@ export const tryFindProviderByType: {
369
369
  const Tag = Provider<R>(resourceType) as Context.Service<Provider<R>, any>;
370
370
  const direct = yield* Effect.serviceOption(Tag);
371
371
  if (Option.isSome(direct)) {
372
- return direct.value;
372
+ return direct;
373
373
  }
374
374
 
375
375
  const context = yield* Effect.context<never>();
package/src/Resource.ts CHANGED
@@ -31,10 +31,10 @@ export type ResourceConstructor<R extends ResourceLike, Req = never> = {
31
31
  },
32
32
  ]
33
33
  ): Effect.Effect<R, never, Req>;
34
- // <PropsReq = never>(
35
- // id: string,
36
- // props: Effect.Effect<Input<R["Props"]>, never, PropsReq>,
37
- // ): Effect.Effect<R, never, PropsReq | Req>;
34
+ <PropsReq = never>(
35
+ id: string,
36
+ props: Effect.Effect<Input<R["Props"]>, never, PropsReq>,
37
+ ): Effect.Effect<R, never, PropsReq | Req>;
38
38
  };
39
39
 
40
40
  export type ResourceClassWithMethods<
@@ -1,6 +1,6 @@
1
1
  import type * as aws from "@distilled.cloud/aws";
2
2
  import * as cf from "@distilled.cloud/cloudflare";
3
- import { NodeServices } from "@effect/platform-node";
3
+ // import { NodeServices } from "@effect/platform-node";
4
4
  import { expect, it } from "@effect/vitest";
5
5
  import { Logger } from "effect";
6
6
  import * as Config from "effect/Config";
@@ -44,6 +44,7 @@ import * as Serverless from "../Serverless/index.ts";
44
44
  import * as Stack from "../Stack.ts";
45
45
  import * as Stage from "../Stage.ts";
46
46
  import * as State from "../State/index.ts";
47
+ import { PlatformServices } from "../Util/PlatformServices.ts";
47
48
  import { TestCli } from "./TestCli.ts";
48
49
 
49
50
  export const expectEmptyObject = () =>
@@ -90,7 +91,7 @@ const quietLogger = Logger.make(() => {
90
91
  });
91
92
 
92
93
  const platform = Layer.mergeAll(
93
- NodeServices.layer,
94
+ PlatformServices,
94
95
  FetchHttpClient.layer,
95
96
  Logger.layer([process.env.VERBOSE ? Logger.consolePretty() : quietLogger]),
96
97
  );
@@ -212,7 +213,7 @@ const runWithContext = <A, Err>(
212
213
  MinimumLogLevel,
213
214
  process.env.DEBUG ? "Debug" : "Info",
214
215
  ),
215
- Effect.provide(NodeServices.layer),
216
+ Effect.provide(PlatformServices),
216
217
  );
217
218
  };
218
219
 
@@ -0,0 +1,77 @@
1
+ import * as p from "@clack/prompts";
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import { ChildProcess } from "effect/unstable/process";
5
+
6
+ export class PromptCancelled extends Data.TaggedError("PromptCancelled") {}
7
+ export const retryIfCancelled = Effect.retry({
8
+ while: (e: unknown) => e instanceof PromptCancelled,
9
+ });
10
+
11
+ /**
12
+ * Wraps a clack prompt (which returns a `Promise<T | symbol>` where the
13
+ * symbol indicates user cancellation) in an Effect.
14
+ *
15
+ * Returns `undefined` if the user cancels (Ctrl+C / Escape).
16
+ *
17
+ * Uses `Effect.callback` so fiber interruption propagates via the abort
18
+ * signal to any async resources we own; the clack prompt itself is left
19
+ * to resolve — its result is ignored after interruption.
20
+ */
21
+ export const prompt = <T>(
22
+ fn: () => Promise<T | symbol>,
23
+ ): Effect.Effect<T, PromptCancelled> =>
24
+ Effect.callback<T, PromptCancelled>((resume, signal) => {
25
+ let settled = false;
26
+ fn().then(
27
+ (result) => {
28
+ if (settled || signal.aborted) return;
29
+ settled = true;
30
+ if (p.isCancel(result)) {
31
+ resume(Effect.fail(new PromptCancelled()));
32
+ } else {
33
+ resume(Effect.succeed(result as T));
34
+ }
35
+ },
36
+ (err) => {
37
+ if (settled || signal.aborted) return;
38
+ settled = true;
39
+ resume(Effect.die(err));
40
+ },
41
+ );
42
+ });
43
+
44
+ export const success = (str: string) => Effect.sync(() => p.log.success(str));
45
+ export const warn = (str: string) => Effect.sync(() => p.log.warn(str));
46
+ export const error = (str: string) => Effect.sync(() => p.log.error(str));
47
+ export const info = (str: string) => Effect.sync(() => p.log.info(str));
48
+ export const text = (opts: p.TextOptions) => prompt(() => p.text(opts));
49
+ export const password = (opts: p.PasswordOptions) =>
50
+ prompt(() => p.password(opts));
51
+ export const select = <Value>(opts: p.SelectOptions<Value>) =>
52
+ prompt(() => p.select<Value>(opts));
53
+ export const confirm = (opts: p.ConfirmOptions) =>
54
+ prompt(() => p.confirm(opts));
55
+ export const multiselect = <Value>(opts: p.MultiSelectOptions<Value>) =>
56
+ prompt(() => p.multiselect<Value>(opts));
57
+
58
+ /**
59
+ * Open a URL in the user's default browser.
60
+ *
61
+ * On Windows, uses rundll32's FileProtocolHandler — a built-in shim that
62
+ * opens URLs in the default browser. It accepts the URL as a direct
63
+ * argument (no shell, no quoting of `&`). cmd.exe `start` would treat
64
+ * `&` in OAuth URLs as a command separator, and `explorer.exe` treats
65
+ * its arg as a path.
66
+ */
67
+ export const openUrl = (url: string) =>
68
+ Effect.gen(function* () {
69
+ const [cmd, args] =
70
+ process.platform === "win32"
71
+ ? (["rundll32.exe", ["url.dll,FileProtocolHandler", url]] as const)
72
+ : process.platform === "darwin"
73
+ ? (["open", [url]] as const)
74
+ : (["xdg-open", [url]] as const);
75
+ const handle = yield* ChildProcess.make(cmd, [...args], { shell: false });
76
+ yield* handle.exitCode;
77
+ }).pipe(Effect.scoped);
@@ -0,0 +1,21 @@
1
+ import type { BunServices } from "@effect/platform-bun/BunServices";
2
+ import type { NodeServices } from "@effect/platform-node/NodeServices";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+
6
+ const isBun = typeof Bun !== "undefined";
7
+
8
+ export const PlatformServices: Layer.Layer<
9
+ NodeServices | BunServices,
10
+ never,
11
+ never
12
+ > = Effect.promise(() => {
13
+ if (isBun) {
14
+ return import("@effect/platform-bun").then(
15
+ (platform) => platform.BunServices.layer,
16
+ );
17
+ }
18
+ return import("@effect/platform-node").then(
19
+ (platform) => platform.NodeServices.layer,
20
+ );
21
+ }).pipe(Layer.unwrap);
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Tagged template literal for removing indentation from a block of text.
3
+ *
4
+ * If the first line is empty, it will be ignored.
5
+ */
6
+ export function dedent(
7
+ strings: TemplateStringsArray,
8
+ ...values: unknown[]
9
+ ): string;
10
+ export function dedent(text: string): string;
11
+ export function dedent(
12
+ stringsOrText: TemplateStringsArray | string,
13
+ ...values: unknown[]
14
+ ): string {
15
+ const raw =
16
+ typeof stringsOrText === "string"
17
+ ? stringsOrText
18
+ : String.raw({ raw: stringsOrText }, ...values);
19
+
20
+ let lines = raw.split("\n");
21
+
22
+ while (lines.length > 0 && lines[0].trim() === "") {
23
+ lines = lines.slice(1);
24
+ }
25
+
26
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
27
+ lines = lines.slice(0, lines.length - 1);
28
+ }
29
+
30
+ if (lines.length === 0) {
31
+ return "";
32
+ }
33
+
34
+ let minIndentLength = Number.POSITIVE_INFINITY;
35
+ for (const line of lines) {
36
+ if (line.trim() !== "") {
37
+ const indent = line.match(/^[ \t]*/)?.[0];
38
+ if (indent != null && indent.length < minIndentLength) {
39
+ minIndentLength = indent.length;
40
+ }
41
+ }
42
+ }
43
+
44
+ if (minIndentLength === Number.POSITIVE_INFINITY) {
45
+ return lines.join("\n");
46
+ }
47
+
48
+ lines = lines.map((line) => {
49
+ if (line.trim() === "") {
50
+ return line;
51
+ }
52
+ return line.startsWith(" ".repeat(minIndentLength)) ||
53
+ line.startsWith("\t".repeat(minIndentLength))
54
+ ? line.substring(minIndentLength)
55
+ : line;
56
+ });
57
+
58
+ return lines.join("\n");
59
+ }
package/src/Util/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./base32.ts";
3
3
  export * from "./camel.ts";
4
4
  export * from "./class.ts";
5
5
  export * from "./data.ts";
6
+ export * from "./dedent.ts";
6
7
  export * from "./instance.ts";
7
8
  export * from "./pointer.ts";
8
9
  export * from "./schema-to-type.ts";