alchemy 2.0.0-beta.14 → 2.0.0-beta.16

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 (175) hide show
  1. package/bin/alchemy.js +2243 -1359
  2. package/bin/alchemy.js.map +1 -1
  3. package/bin/alchemy.ts +4 -0
  4. package/bin/commands/deploy.ts +0 -1
  5. package/bin/commands/login.ts +6 -3
  6. package/bin/commands/profile.ts +86 -0
  7. package/bin/commands/state.ts +8 -5
  8. package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
  9. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  10. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
  11. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
  12. package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
  13. package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
  14. package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
  15. package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
  16. package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
  17. package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
  18. package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
  19. package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
  20. package/lib/Agent/lsp/client.d.ts +2 -2
  21. package/lib/Cli/InkCLI.d.ts +0 -1
  22. package/lib/Cli/InkCLI.d.ts.map +1 -1
  23. package/lib/Cli/InkCLI.js +0 -5
  24. package/lib/Cli/InkCLI.js.map +1 -1
  25. package/lib/Cli/index.d.ts +0 -1
  26. package/lib/Cli/index.d.ts.map +1 -1
  27. package/lib/Cli/index.js +4 -1
  28. package/lib/Cli/index.js.map +1 -1
  29. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +57 -0
  30. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -0
  31. package/lib/Cloudflare/ApiToken/AccountApiToken.js +141 -0
  32. package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -0
  33. package/lib/Cloudflare/ApiToken/Common.d.ts +96 -0
  34. package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -0
  35. package/lib/Cloudflare/ApiToken/Common.js +61 -0
  36. package/lib/Cloudflare/ApiToken/Common.js.map +1 -0
  37. package/lib/Cloudflare/ApiToken/PermissionGroups.d.ts +1549 -0
  38. package/lib/Cloudflare/ApiToken/PermissionGroups.d.ts.map +1 -0
  39. package/lib/Cloudflare/ApiToken/PermissionGroups.js +1856 -0
  40. package/lib/Cloudflare/ApiToken/PermissionGroups.js.map +1 -0
  41. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +48 -0
  42. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -0
  43. package/lib/Cloudflare/ApiToken/UserApiToken.js +113 -0
  44. package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -0
  45. package/lib/Cloudflare/ApiToken/index.d.ts +5 -0
  46. package/lib/Cloudflare/ApiToken/index.d.ts.map +1 -0
  47. package/lib/Cloudflare/ApiToken/index.js +5 -0
  48. package/lib/Cloudflare/ApiToken/index.js.map +1 -0
  49. package/lib/Cloudflare/Artifacts/Artifacts.d.ts +101 -0
  50. package/lib/Cloudflare/Artifacts/Artifacts.d.ts.map +1 -0
  51. package/lib/Cloudflare/Artifacts/Artifacts.js +92 -0
  52. package/lib/Cloudflare/Artifacts/Artifacts.js.map +1 -0
  53. package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts +83 -0
  54. package/lib/Cloudflare/Artifacts/ArtifactsBinding.d.ts.map +1 -0
  55. package/lib/Cloudflare/Artifacts/ArtifactsBinding.js +65 -0
  56. package/lib/Cloudflare/Artifacts/ArtifactsBinding.js.map +1 -0
  57. package/lib/Cloudflare/Artifacts/index.d.ts +3 -0
  58. package/lib/Cloudflare/Artifacts/index.d.ts.map +1 -0
  59. package/lib/Cloudflare/Artifacts/index.js +3 -0
  60. package/lib/Cloudflare/Artifacts/index.js.map +1 -0
  61. package/lib/Cloudflare/Auth/AuthProvider.js +2 -2
  62. package/lib/Cloudflare/Auth/AuthProvider.js.map +1 -1
  63. package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
  64. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  65. package/lib/Cloudflare/Providers.js +6 -1
  66. package/lib/Cloudflare/Providers.js.map +1 -1
  67. package/lib/Cloudflare/StateStore/Api.d.ts +7 -8
  68. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  69. package/lib/Cloudflare/StateStore/Api.js +6 -4
  70. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  71. package/lib/Cloudflare/StateStore/State.d.ts +1 -1
  72. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  73. package/lib/Cloudflare/StateStore/Store.d.ts +2 -2
  74. package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
  75. package/lib/Cloudflare/StateStore/Store.js +6 -4
  76. package/lib/Cloudflare/StateStore/Store.js.map +1 -1
  77. package/lib/Cloudflare/StateStore/Token.d.ts +6 -4
  78. package/lib/Cloudflare/StateStore/Token.d.ts.map +1 -1
  79. package/lib/Cloudflare/StateStore/Token.js +8 -4
  80. package/lib/Cloudflare/StateStore/Token.js.map +1 -1
  81. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  82. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  83. package/lib/Cloudflare/Workers/InferEnv.js +0 -1
  84. package/lib/Cloudflare/Workers/InferEnv.js.map +1 -1
  85. package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
  86. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  87. package/lib/Cloudflare/Workers/Worker.js +25 -18
  88. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  89. package/lib/Cloudflare/index.d.ts +2 -0
  90. package/lib/Cloudflare/index.d.ts.map +1 -1
  91. package/lib/Cloudflare/index.js +2 -0
  92. package/lib/Cloudflare/index.js.map +1 -1
  93. package/lib/Deploy.d.ts +1 -1
  94. package/lib/Deploy.d.ts.map +1 -1
  95. package/lib/Destroy.d.ts +1 -1
  96. package/lib/Destroy.d.ts.map +1 -1
  97. package/lib/GitHub/AuthProvider.d.ts +37 -0
  98. package/lib/GitHub/AuthProvider.d.ts.map +1 -0
  99. package/lib/GitHub/AuthProvider.js +154 -0
  100. package/lib/GitHub/AuthProvider.js.map +1 -0
  101. package/lib/GitHub/Credentials.d.ts +29 -0
  102. package/lib/GitHub/Credentials.d.ts.map +1 -0
  103. package/lib/GitHub/Credentials.js +54 -0
  104. package/lib/GitHub/Credentials.js.map +1 -0
  105. package/lib/GitHub/Providers.d.ts +14 -0
  106. package/lib/GitHub/Providers.d.ts.map +1 -0
  107. package/lib/GitHub/Providers.js +15 -0
  108. package/lib/GitHub/Providers.js.map +1 -0
  109. package/lib/GitHub/Secret.d.ts +8 -10
  110. package/lib/GitHub/Secret.d.ts.map +1 -1
  111. package/lib/GitHub/Secret.js +16 -14
  112. package/lib/GitHub/Secret.js.map +1 -1
  113. package/lib/GitHub/Variable.d.ts +8 -10
  114. package/lib/GitHub/Variable.d.ts.map +1 -1
  115. package/lib/GitHub/Variable.js +12 -13
  116. package/lib/GitHub/Variable.js.map +1 -1
  117. package/lib/GitHub/index.d.ts +3 -0
  118. package/lib/GitHub/index.d.ts.map +1 -1
  119. package/lib/GitHub/index.js +3 -0
  120. package/lib/GitHub/index.js.map +1 -1
  121. package/lib/Output.d.ts.map +1 -1
  122. package/lib/Output.js +4 -0
  123. package/lib/Output.js.map +1 -1
  124. package/lib/Plan.d.ts.map +1 -1
  125. package/lib/Plan.js +4 -0
  126. package/lib/Plan.js.map +1 -1
  127. package/lib/Stack.d.ts +5 -4
  128. package/lib/Stack.d.ts.map +1 -1
  129. package/lib/Stack.js +27 -8
  130. package/lib/Stack.js.map +1 -1
  131. package/lib/State/HttpStateStore.js +1 -1
  132. package/lib/State/HttpStateStore.js.map +1 -1
  133. package/lib/Test/Bun.d.ts +2 -2
  134. package/lib/Test/Bun.d.ts.map +1 -1
  135. package/lib/Test/Bun.js +2 -4
  136. package/lib/Test/Bun.js.map +1 -1
  137. package/lib/Test/Vitest.d.ts +1 -1
  138. package/lib/Test/Vitest.d.ts.map +1 -1
  139. package/lib/Test/Vitest.js +11 -7
  140. package/lib/Test/Vitest.js.map +1 -1
  141. package/lib/cli/index.d.ts +2 -6
  142. package/lib/cli/index.d.ts.map +1 -1
  143. package/lib/cli/index.js +2 -43050
  144. package/lib/cli/index.js.map +1 -1
  145. package/package.json +5 -5
  146. package/src/Cli/InkCLI.tsx +0 -10
  147. package/src/Cli/index.ts +4 -1
  148. package/src/Cloudflare/ApiToken/AccountApiToken.ts +207 -0
  149. package/src/Cloudflare/ApiToken/Common.ts +153 -0
  150. package/src/Cloudflare/ApiToken/PermissionGroups.ts +1910 -0
  151. package/src/Cloudflare/ApiToken/UserApiToken.ts +166 -0
  152. package/src/Cloudflare/ApiToken/index.ts +4 -0
  153. package/src/Cloudflare/Artifacts/Artifacts.ts +139 -0
  154. package/src/Cloudflare/Artifacts/ArtifactsBinding.ts +188 -0
  155. package/src/Cloudflare/Artifacts/index.ts +2 -0
  156. package/src/Cloudflare/Auth/AuthProvider.ts +2 -2
  157. package/src/Cloudflare/Providers.ts +8 -0
  158. package/src/Cloudflare/StateStore/Api.ts +6 -4
  159. package/src/Cloudflare/StateStore/Store.ts +6 -4
  160. package/src/Cloudflare/StateStore/Token.ts +10 -4
  161. package/src/Cloudflare/Workers/InferEnv.ts +5 -4
  162. package/src/Cloudflare/Workers/Worker.ts +12 -1
  163. package/src/Cloudflare/index.ts +2 -0
  164. package/src/GitHub/AuthProvider.ts +309 -0
  165. package/src/GitHub/Credentials.ts +99 -0
  166. package/src/GitHub/Providers.ts +26 -0
  167. package/src/GitHub/Secret.ts +22 -24
  168. package/src/GitHub/Variable.ts +16 -23
  169. package/src/GitHub/index.ts +3 -0
  170. package/src/Output.ts +3 -0
  171. package/src/Plan.ts +3 -0
  172. package/src/Stack.ts +33 -7
  173. package/src/State/HttpStateStore.ts +1 -0
  174. package/src/Test/Bun.ts +2 -2
  175. package/src/Test/Vitest.ts +11 -7
@@ -0,0 +1,166 @@
1
+ import * as user from "@distilled.cloud/cloudflare/user";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Redacted from "effect/Redacted";
4
+ import { isResolved } from "../../Diff.ts";
5
+ import { createPhysicalName } from "../../PhysicalName.ts";
6
+ import * as Provider from "../../Provider.ts";
7
+ import { Resource } from "../../Resource.ts";
8
+ import type { Providers } from "../Providers.ts";
9
+ import {
10
+ buildConditionPayload,
11
+ conditionFingerprint,
12
+ policyFingerprint,
13
+ resolvePolicies,
14
+ type ApiTokenProps,
15
+ } from "./Common.ts";
16
+
17
+ export type UserApiToken = Resource<
18
+ "Cloudflare.UserApiToken",
19
+ ApiTokenProps,
20
+ {
21
+ tokenId: string;
22
+ name: string;
23
+ status: "active" | "disabled" | "expired";
24
+ /**
25
+ * The plaintext token value. Cloudflare returns this only once, on
26
+ * creation, so we persist it here for downstream consumers.
27
+ */
28
+ value: Redacted.Redacted<string>;
29
+ },
30
+ never,
31
+ Providers
32
+ >;
33
+
34
+ /**
35
+ * A Cloudflare user-owned API token (`POST /user/tokens`).
36
+ *
37
+ * User-owned tokens are tied to the authenticated user's identity. They can
38
+ * be created by any authenticated user (including OAuth-derived sessions
39
+ * from `alchemy login`) without needing the account-level
40
+ * `API Tokens > Write` permission, but they are also revoked if the user
41
+ * leaves the account.
42
+ *
43
+ * For CI tokens, prefer {@link AccountApiToken} so the token survives
44
+ * personnel changes.
45
+ *
46
+ * Policy `resources` are passed through verbatim — no `accountId` rewriting
47
+ * is performed because user tokens aren't bound to a single account.
48
+ *
49
+ * @section Creating a Token
50
+ * @example A token bound to the authenticated user
51
+ * ```typescript
52
+ * const token = yield* Cloudflare.UserApiToken("personal-token", {
53
+ * name: "my-personal-token",
54
+ * policies: [
55
+ * {
56
+ * effect: "allow",
57
+ * permissionGroups: ["Workers Scripts Read"],
58
+ * resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
59
+ * },
60
+ * ],
61
+ * });
62
+ * ```
63
+ */
64
+ export const UserApiToken = Resource<UserApiToken>("Cloudflare.UserApiToken");
65
+
66
+ type UserApiTokenAttributes = UserApiToken["Attributes"];
67
+
68
+ const resolveName = (id: string, name: string | undefined) =>
69
+ Effect.gen(function* () {
70
+ return name ?? (yield* createPhysicalName({ id }));
71
+ });
72
+
73
+ export const UserApiTokenProvider = () =>
74
+ Provider.effect(
75
+ UserApiToken,
76
+ Effect.gen(function* () {
77
+ const createToken = yield* user.createToken;
78
+ const updateToken = yield* user.updateToken;
79
+ const deleteToken = yield* user.deleteToken;
80
+ const getToken = yield* user.getToken;
81
+
82
+ const buildAttributes = (
83
+ tokenData: {
84
+ id?: string | null;
85
+ name?: string | null;
86
+ status?: "active" | "disabled" | "expired" | null;
87
+ },
88
+ value: Redacted.Redacted<string>,
89
+ ): UserApiTokenAttributes => ({
90
+ tokenId: tokenData.id ?? "",
91
+ name: tokenData.name ?? "",
92
+ status: tokenData.status ?? "active",
93
+ value,
94
+ });
95
+
96
+ return {
97
+ stables: ["tokenId"],
98
+ diff: Effect.fn(function* ({ id, olds, news, output }) {
99
+ if (!isResolved(news)) return undefined;
100
+ const oldName = output?.name ?? (yield* resolveName(id, olds?.name));
101
+ const newName = yield* resolveName(id, news.name);
102
+ const oldPolicyFp = policyFingerprint(
103
+ resolvePolicies(olds?.policies ?? [], olds?.accountId),
104
+ );
105
+ const newPolicyFp = policyFingerprint(
106
+ resolvePolicies(news.policies, news.accountId),
107
+ );
108
+ const oldCondFp = conditionFingerprint(olds?.condition);
109
+ const newCondFp = conditionFingerprint(news.condition);
110
+ if (
111
+ oldName !== newName ||
112
+ oldPolicyFp !== newPolicyFp ||
113
+ oldCondFp !== newCondFp ||
114
+ (olds?.expiresOn ?? undefined) !== (news.expiresOn ?? undefined) ||
115
+ (olds?.notBefore ?? undefined) !== (news.notBefore ?? undefined)
116
+ ) {
117
+ return { action: "update" } as const;
118
+ }
119
+ }),
120
+ create: Effect.fn(function* ({ id, news }) {
121
+ const name = yield* resolveName(id, news.name);
122
+ const policies = resolvePolicies(news.policies, news.accountId);
123
+ const result = yield* createToken({
124
+ name,
125
+ policies,
126
+ condition: buildConditionPayload(news.condition),
127
+ expiresOn: news.expiresOn,
128
+ notBefore: news.notBefore,
129
+ });
130
+ if (!result.value) {
131
+ return yield* Effect.die(
132
+ `Cloudflare did not return a value for token "${name}".`,
133
+ );
134
+ }
135
+ return buildAttributes(result, Redacted.make(result.value));
136
+ }),
137
+ update: Effect.fn(function* ({ id, news, output }) {
138
+ const name = yield* resolveName(id, news.name);
139
+ const policies = resolvePolicies(news.policies, news.accountId);
140
+ const result = yield* updateToken({
141
+ tokenId: output.tokenId,
142
+ name,
143
+ policies,
144
+ condition: buildConditionPayload(news.condition),
145
+ expiresOn: news.expiresOn,
146
+ notBefore: news.notBefore,
147
+ });
148
+ // Cloudflare doesn't return the value on update; preserve the
149
+ // value captured at creation.
150
+ return buildAttributes(result, output.value);
151
+ }),
152
+ delete: Effect.fn(function* ({ output }) {
153
+ yield* deleteToken({ tokenId: output.tokenId }).pipe(
154
+ Effect.catchTag("TokenNotFound", () => Effect.void),
155
+ );
156
+ }),
157
+ read: Effect.fn(function* ({ output }) {
158
+ if (!output?.tokenId) return undefined;
159
+ return yield* getToken({ tokenId: output.tokenId }).pipe(
160
+ Effect.map((token) => buildAttributes(token, output.value)),
161
+ Effect.catchTag("TokenNotFound", () => Effect.succeed(undefined)),
162
+ );
163
+ }),
164
+ };
165
+ }),
166
+ );
@@ -0,0 +1,4 @@
1
+ export * from "./AccountApiToken.ts";
2
+ export * from "./Common.ts";
3
+ export * from "./PermissionGroups.ts";
4
+ export * from "./UserApiToken.ts";
@@ -0,0 +1,139 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { Stack } from "../../Stack.ts";
3
+ import { Stage } from "../../Stage.ts";
4
+ import { ArtifactsBinding } from "./ArtifactsBinding.ts";
5
+
6
+ type ArtifactsTypeId = typeof ArtifactsTypeId;
7
+ const ArtifactsTypeId = "Cloudflare.Artifacts" as const;
8
+
9
+ /**
10
+ * Cloudflare validation: 3–63 chars, lowercase alphanumeric and hyphens, must
11
+ * start and end with a lowercase alphanumeric character.
12
+ */
13
+ const ARTIFACTS_NAMESPACE_REGEX = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])?$/;
14
+
15
+ export class InvalidArtifactsNamespaceError extends Error {
16
+ readonly _tag = "InvalidArtifactsNamespaceError" as const;
17
+ constructor(public readonly namespace: string) {
18
+ super(
19
+ `Invalid artifacts namespace name '${namespace}'. Must be 3-63 characters, start and end with a lowercase alphanumeric character, and contain only lowercase alphanumeric characters and hyphens.`,
20
+ );
21
+ }
22
+ }
23
+
24
+ export type ArtifactsProps = {
25
+ /**
26
+ * Cloudflare namespace name. Namespaces are implicit on Cloudflare — the
27
+ * first repo created against this name conjures the namespace.
28
+ *
29
+ * Must be 3–63 lowercase alphanumeric characters or hyphens, and must start
30
+ * and end with a lowercase alphanumeric character. If omitted, a unique
31
+ * physical name is generated from the resource's logical id, app, and stage.
32
+ */
33
+ namespace?: string;
34
+ };
35
+
36
+ /**
37
+ * Marker for a Cloudflare Artifacts namespace binding.
38
+ *
39
+ * Artifacts namespaces are implicit (created on first repo write) and require
40
+ * no deploy-time provisioning, so this is a pure binding marker rather than
41
+ * a full Resource. The Worker provider sees this object in `bindings: { ... }`
42
+ * and emits the corresponding `{ type: "artifacts", name, namespace }` binding
43
+ * to the script.
44
+ */
45
+ export type Artifacts = {
46
+ kind: ArtifactsTypeId;
47
+ name: string;
48
+ namespace: string;
49
+ };
50
+
51
+ export const isArtifacts = (value: unknown): value is Artifacts =>
52
+ typeof value === "object" &&
53
+ value !== null &&
54
+ "kind" in value &&
55
+ (value as Artifacts).kind === ArtifactsTypeId;
56
+
57
+ /**
58
+ * A Cloudflare Artifacts namespace — the top-level container for Git-compatible
59
+ * versioned repositories. See the
60
+ * {@link https://blog.cloudflare.com/artifacts-git-for-agents-beta/ | Artifacts launch post}
61
+ * and {@link https://developers.cloudflare.com/artifacts/concepts/namespaces/ | Namespaces docs}.
62
+ *
63
+ * Namespaces on Cloudflare are **implicit**: there is no `POST /namespaces`
64
+ * endpoint. The namespace is conjured the first time a repo is created against
65
+ * it (either via the REST API or the Worker binding). Because of that, the
66
+ * Alchemy "resource" is a thin binding marker — there is nothing to provision
67
+ * at deploy time. Repos themselves are typically created at runtime through
68
+ * the bound `Artifacts` API.
69
+ *
70
+ * @section Declaring a Namespace
71
+ * @example Default namespace (a unique physical name is generated)
72
+ * ```typescript
73
+ * const Repos = Cloudflare.Artifacts("Repos");
74
+ * ```
75
+ *
76
+ * @example Override the namespace name (must be lowercase, 3–63 chars)
77
+ * ```typescript
78
+ * const Repos = Cloudflare.Artifacts("Repos", { namespace: "starter-repos" });
79
+ * ```
80
+ *
81
+ * @section Binding to a Worker
82
+ * @example Wiring it into a Worker
83
+ * ```typescript
84
+ * export const Worker = Cloudflare.Worker("Worker", {
85
+ * main: "./src/worker.ts",
86
+ * bindings: { Repos },
87
+ * });
88
+ *
89
+ * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
90
+ * // { Repos: Artifacts }
91
+ * ```
92
+ *
93
+ * @example Async-style worker
94
+ * ```typescript
95
+ * export default {
96
+ * async fetch(request: Request, env: WorkerEnv) {
97
+ * const repo = await env.Repos.create("starter-repo");
98
+ * return Response.json({ remote: repo.remote, token: repo.token });
99
+ * },
100
+ * };
101
+ * ```
102
+ *
103
+ * @example Effect-style worker
104
+ * ```typescript
105
+ * const artifacts = yield* Cloudflare.Artifacts.bind(Repos);
106
+ * const repo = yield* artifacts.create("starter-repo", {
107
+ * setDefaultBranch: "main",
108
+ * });
109
+ * ```
110
+ */
111
+ export const Artifacts: {
112
+ (
113
+ name: string,
114
+ props?: ArtifactsProps,
115
+ ): Effect.Effect<Artifacts, never, Stack | Stage>;
116
+ /**
117
+ * Bind a Cloudflare Artifacts namespace to the surrounding Worker, returning
118
+ * an Effect-native client. See {@link ArtifactsBinding}.
119
+ */
120
+ bind: typeof ArtifactsBinding.bind;
121
+ } = Object.assign(
122
+ Effect.fn(function* (name: string, props?: ArtifactsProps) {
123
+ const namespace = props?.namespace
124
+ ? props.namespace
125
+ : name.toLocaleLowerCase();
126
+ if (!ARTIFACTS_NAMESPACE_REGEX.test(namespace)) {
127
+ return yield* Effect.die(new InvalidArtifactsNamespaceError(namespace));
128
+ }
129
+ return {
130
+ kind: ArtifactsTypeId,
131
+ name,
132
+ namespace,
133
+ } satisfies Artifacts;
134
+ }),
135
+ {
136
+ bind: (...args: Parameters<typeof ArtifactsBinding.bind>) =>
137
+ ArtifactsBinding.bind(...args),
138
+ },
139
+ );
@@ -0,0 +1,188 @@
1
+ import * as Data from "effect/Data";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Binding from "../../Binding.ts";
5
+ import type { ResourceLike } from "../../Resource.ts";
6
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
7
+ import { type Artifacts as ArtifactsLike } from "./Artifacts.ts";
8
+
9
+ export class ArtifactsError extends Data.TaggedError("ArtifactsError")<{
10
+ message: string;
11
+ cause: Error;
12
+ }> {}
13
+
14
+ export type Scope = "read" | "write";
15
+
16
+ export type ArtifactsCreateOptions = {
17
+ readOnly?: boolean;
18
+ description?: string;
19
+ setDefaultBranch?: string;
20
+ };
21
+
22
+ export type ArtifactsImportOptions = {
23
+ source: { url: string; branch?: string; depth?: number };
24
+ target: {
25
+ name: string;
26
+ opts?: { description?: string; readOnly?: boolean };
27
+ };
28
+ };
29
+
30
+ export type ArtifactsListOptions = {
31
+ limit?: number;
32
+ cursor?: string;
33
+ };
34
+
35
+ export type ArtifactsForkOptions = {
36
+ description?: string;
37
+ readOnly?: boolean;
38
+ defaultBranchOnly?: boolean;
39
+ };
40
+
41
+ /**
42
+ * Effect-native handle to a single Artifacts repo. Wraps the runtime
43
+ * {@link ArtifactsRepo} so each method returns an Effect.
44
+ */
45
+ export interface ArtifactsRepoClient {
46
+ /** Underlying Cloudflare runtime handle. */
47
+ raw: ArtifactsRepo;
48
+ createToken(
49
+ scope?: Scope,
50
+ ttl?: number,
51
+ ): Effect.Effect<ArtifactsCreateTokenResult, ArtifactsError>;
52
+ listTokens(): Effect.Effect<ArtifactsTokenListResult, ArtifactsError>;
53
+ revokeToken(tokenOrId: string): Effect.Effect<boolean, ArtifactsError>;
54
+ fork(
55
+ name: string,
56
+ opts?: ArtifactsForkOptions,
57
+ ): Effect.Effect<ArtifactsCreateRepoResult, ArtifactsError>;
58
+ }
59
+
60
+ /**
61
+ * Effect-native client for a Cloudflare Artifacts namespace binding.
62
+ *
63
+ * Wraps the runtime {@link Artifacts} binding so each method returns an
64
+ * Effect tagged with {@link ArtifactsError}. Use
65
+ * `Cloudflare.ArtifactsBinding.bind(Repos)` inside a Worker's init phase.
66
+ */
67
+ export interface ArtifactsClient {
68
+ /** Effect resolving to the raw Cloudflare runtime binding. */
69
+ raw: Effect.Effect<Artifacts, never, WorkerEnvironment>;
70
+ create(
71
+ name: string,
72
+ opts?: ArtifactsCreateOptions,
73
+ ): Effect.Effect<
74
+ ArtifactsCreateRepoResult,
75
+ ArtifactsError,
76
+ WorkerEnvironment
77
+ >;
78
+ /** Look up an existing repo by name. Fails with `ArtifactsError` if missing. */
79
+ get(
80
+ name: string,
81
+ ): Effect.Effect<ArtifactsRepoClient, ArtifactsError, WorkerEnvironment>;
82
+ list(
83
+ opts?: ArtifactsListOptions,
84
+ ): Effect.Effect<ArtifactsRepoListResult, ArtifactsError, WorkerEnvironment>;
85
+ delete(
86
+ name: string,
87
+ ): Effect.Effect<boolean, ArtifactsError, WorkerEnvironment>;
88
+ import(
89
+ opts: ArtifactsImportOptions,
90
+ ): Effect.Effect<
91
+ ArtifactsCreateRepoResult,
92
+ ArtifactsError,
93
+ WorkerEnvironment
94
+ >;
95
+ }
96
+
97
+ export class ArtifactsBinding extends Binding.Service<
98
+ ArtifactsBinding,
99
+ (artifacts: ArtifactsLike) => Effect.Effect<ArtifactsClient>
100
+ >()("Cloudflare.Artifacts.Binding") {}
101
+
102
+ export const ArtifactsBindingLive = Layer.effect(
103
+ ArtifactsBinding,
104
+ Effect.gen(function* () {
105
+ const Policy = yield* ArtifactsBindingPolicy;
106
+
107
+ return Effect.fn(function* (artifacts: ArtifactsLike) {
108
+ yield* Policy(artifacts);
109
+ const env = WorkerEnvironment.asEffect();
110
+ const raw = env.pipe(
111
+ Effect.map(
112
+ (env) => (env as Record<string, Artifacts>)[artifacts.name]!,
113
+ ),
114
+ );
115
+
116
+ const use = <T>(
117
+ fn: (raw: Artifacts) => Promise<T>,
118
+ ): Effect.Effect<T, ArtifactsError, WorkerEnvironment> =>
119
+ raw.pipe(Effect.flatMap((raw) => tryPromise(() => fn(raw))));
120
+
121
+ return {
122
+ raw,
123
+ create: (name, opts) => use((raw) => raw.create(name, opts)),
124
+ get: (name) =>
125
+ use((raw) => raw.get(name)).pipe(
126
+ Effect.flatMap((repo) =>
127
+ repo == null
128
+ ? Effect.fail(
129
+ new ArtifactsError({
130
+ message: `Artifacts repo '${name}' not found`,
131
+ cause: new Error("not_found"),
132
+ }),
133
+ )
134
+ : Effect.succeed(wrapRepo(repo as ArtifactsRepo)),
135
+ ),
136
+ ),
137
+ list: (opts) => use((raw) => raw.list(opts)),
138
+ delete: (name) => use((raw) => raw.delete(name)),
139
+ import: (opts) => use((raw) => raw.import(opts)),
140
+ } satisfies ArtifactsClient;
141
+ });
142
+ }),
143
+ );
144
+
145
+ export class ArtifactsBindingPolicy extends Binding.Policy<
146
+ ArtifactsBindingPolicy,
147
+ (artifacts: ArtifactsLike) => Effect.Effect<void>
148
+ >()("Cloudflare.Artifacts.Binding") {}
149
+
150
+ export const ArtifactsBindingPolicyLive = ArtifactsBindingPolicy.layer.succeed(
151
+ Effect.fn(function* (host: ResourceLike, artifacts: ArtifactsLike) {
152
+ if (isWorker(host)) {
153
+ yield* host.bind(artifacts.name, {
154
+ bindings: [
155
+ {
156
+ type: "artifacts",
157
+ name: artifacts.name,
158
+ namespace: artifacts.namespace,
159
+ } as any,
160
+ ],
161
+ });
162
+ } else {
163
+ return yield* Effect.die(
164
+ new Error(`ArtifactsBinding does not support runtime '${host.Type}'`),
165
+ );
166
+ }
167
+ }),
168
+ );
169
+
170
+ const tryPromise = <T>(
171
+ fn: () => Promise<T>,
172
+ ): Effect.Effect<T, ArtifactsError> =>
173
+ Effect.tryPromise({
174
+ try: fn,
175
+ catch: (error: any) =>
176
+ new ArtifactsError({
177
+ message: error?.message ?? "Unknown error",
178
+ cause: error,
179
+ }),
180
+ });
181
+
182
+ const wrapRepo = (raw: ArtifactsRepo): ArtifactsRepoClient => ({
183
+ raw,
184
+ createToken: (scope, ttl) => tryPromise(() => raw.createToken(scope, ttl)),
185
+ listTokens: () => tryPromise(() => raw.listTokens()),
186
+ revokeToken: (tokenOrId) => tryPromise(() => raw.revokeToken(tokenOrId)),
187
+ fork: (name, opts) => tryPromise(() => raw.fork(name, opts)),
188
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./Artifacts.ts";
2
+ export * from "./ArtifactsBinding.ts";
@@ -46,8 +46,8 @@ const options: Array<{
46
46
  },
47
47
  {
48
48
  value: "stored",
49
- label: "Stored",
50
- hint: "stored in ~/.alchemy/credentials",
49
+ label: "API Token or API Key",
50
+ hint: "enter credentials interactively, stored in ~/.alchemy/credentials",
51
51
  },
52
52
  ];
53
53
 
@@ -3,6 +3,8 @@ import { Command } from "../Build/Command.ts";
3
3
  import * as Build from "../Build/index.ts";
4
4
  import * as Provider from "../Provider.ts";
5
5
  import { Random, RandomProvider } from "../Random.ts";
6
+ import * as ApiToken from "./ApiToken/index.ts";
7
+ import * as Artifacts from "./Artifacts/index.ts";
6
8
  import { CloudflareAuth } from "./Auth/AuthProvider.ts";
7
9
  import * as CloudflareEnvironment from "./CloudflareEnvironment.ts";
8
10
  import * as Containers from "./Container/index.ts";
@@ -30,6 +32,9 @@ export const providers = () =>
30
32
  Layer.effect(
31
33
  Providers,
32
34
  Provider.collection([
35
+ ApiToken.AccountApiToken,
36
+ ApiToken.UserApiToken,
37
+ Artifacts.ArtifactsBindingPolicy,
33
38
  Command,
34
39
  Containers.Container,
35
40
  D1.D1ConnectionPolicy,
@@ -53,6 +58,9 @@ export const providers = () =>
53
58
  ).pipe(
54
59
  Layer.provide(
55
60
  Layer.mergeAll(
61
+ ApiToken.AccountApiTokenProvider(),
62
+ ApiToken.UserApiTokenProvider(),
63
+ Artifacts.ArtifactsBindingPolicyLive,
56
64
  Containers.ContainerProvider(),
57
65
  D1.D1ConnectionPolicyLive,
58
66
  D1.DatabaseProvider(),
@@ -1,4 +1,3 @@
1
- import * as Cloudflare from "alchemy/Cloudflare";
2
1
  import {
3
2
  BearerTokenValidator,
4
3
  StateApi,
@@ -13,12 +12,15 @@ import * as HttpRouter from "effect/unstable/http/HttpRouter";
13
12
  import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
14
13
  import * as HttpApiError from "effect/unstable/httpapi/HttpApiError";
15
14
  import crypto from "node:crypto";
15
+ import { SecretBindingLive } from "../SecretsStore/SecretBinding.ts";
16
+ import * as Secret from "../SecretsStore/Secret.ts";
17
+ import { Worker } from "../Workers/Worker.ts";
16
18
  import Store from "./Store.ts";
17
19
  import { AuthToken } from "./Token.ts";
18
20
 
19
21
  export const STATE_STORE_SCRIPT_NAME = "alchemy-state-store" as const;
20
22
 
21
- export default Cloudflare.Worker(
23
+ export default Worker(
22
24
  "Api",
23
25
  {
24
26
  name: STATE_STORE_SCRIPT_NAME,
@@ -30,7 +32,7 @@ export default Cloudflare.Worker(
30
32
  },
31
33
  },
32
34
  Effect.gen(function* () {
33
- const secret = yield* Cloudflare.Secret.bind(AuthToken);
35
+ const secret = yield* Secret.Secret.bind(AuthToken);
34
36
  const store = yield* Store;
35
37
 
36
38
  const bearerTokenValidator = Layer.effect(
@@ -126,7 +128,7 @@ export default Cloudflare.Worker(
126
128
  HttpRouter.toHttpEffect,
127
129
  ),
128
130
  };
129
- }).pipe(Effect.provide(Layer.mergeAll(Cloudflare.SecretBindingLive))),
131
+ }).pipe(Effect.provide(Layer.mergeAll(SecretBindingLive))),
130
132
  );
131
133
 
132
134
  /**
@@ -1,22 +1,24 @@
1
- import * as Cloudflare from "alchemy/Cloudflare";
2
1
  import type { ReplacedResourceState, ResourceState } from "alchemy/State";
3
2
  import { encodeState } from "alchemy/State";
4
3
  import * as Effect from "effect/Effect";
5
4
  import { pipe } from "effect/Function";
5
+ import * as Secret from "../SecretsStore/Secret.ts";
6
+ import { DurableObjectNamespace } from "../Workers/DurableObjectNamespace.ts";
7
+ import { DurableObjectState } from "../Workers/DurableObjectState.ts";
6
8
  import { EncryptionKey } from "./Token.ts";
7
9
 
8
- export default class Store extends Cloudflare.DurableObjectNamespace<Store>()(
10
+ export default class Store extends DurableObjectNamespace<Store>()(
9
11
  "Store",
10
12
  Effect.gen(function* () {
11
13
  // Outer (class-level) phase — resolve the binding factory once.
12
14
  // The actual secret read happens inside each DO instance below,
13
15
  // since `SecretClient.get()` needs the per-instance worker env.
14
- const encryptionSecret = yield* Cloudflare.Secret.bind(EncryptionKey);
16
+ const encryptionSecret = yield* Secret.Secret.bind(EncryptionKey);
15
17
 
16
18
  return Effect.gen(function* () {
17
19
  // Inner (per-instance) phase — set up storage and the AES key.
18
20
  // The key is imported once per DO boot and reused thereafter.
19
- const doState = yield* Cloudflare.DurableObjectState;
21
+ const doState = yield* DurableObjectState;
20
22
  const storage = doState.storage;
21
23
 
22
24
  const keyHex = yield* encryptionSecret.get().pipe(Effect.orDie);
@@ -1,13 +1,14 @@
1
1
  import { Random } from "alchemy";
2
- import * as Cloudflare from "alchemy/Cloudflare";
3
2
  import * as Effect from "effect/Effect";
3
+ import * as Secret from "../SecretsStore/Secret.ts";
4
+ import { SecretsStore } from "../SecretsStore/SecretsStore.ts";
4
5
 
5
6
  /**
6
7
  * The account-wide Secrets Store that backs every secret used by the
7
8
  * state store worker. `SecretsStore` adopts the single store that
8
9
  * already exists on the account, or creates one if none exists.
9
10
  */
10
- export const Store = Cloudflare.SecretsStore("StateStoreSecrets");
11
+ export const Store = SecretsStore("StateStoreSecrets");
11
12
 
12
13
  /**
13
14
  * The randomly generated bearer token value. Generated once on create
@@ -30,7 +31,8 @@ export const AuthTokenSecretName = "AlchemyStateStoreToken" as const;
30
31
  export const AuthToken = Effect.gen(function* () {
31
32
  const store = yield* Store;
32
33
  const random = yield* TokenValue;
33
- return yield* Cloudflare.Secret(AuthTokenSecretName, {
34
+ return yield* Secret.Secret(AuthTokenSecretName, {
35
+ name: AuthTokenSecretName,
34
36
  store,
35
37
  value: random.text,
36
38
  });
@@ -46,6 +48,9 @@ export const EncryptionKeyValue = Random("StateStoreEncryptionKeyValue", {
46
48
  bytes: 32,
47
49
  });
48
50
 
51
+ export const EncryptionKeySecretName =
52
+ "AlchemyStateStoreEncryptionKey" as const;
53
+
49
54
  /**
50
55
  * The encryption key secret. The raw hex-encoded bytes live inside
51
56
  * Cloudflare's Secrets Store; the Durable Object binds to it at
@@ -55,7 +60,8 @@ export const EncryptionKeyValue = Random("StateStoreEncryptionKeyValue", {
55
60
  export const EncryptionKey = Effect.gen(function* () {
56
61
  const store = yield* Store;
57
62
  const random = yield* EncryptionKeyValue;
58
- return yield* Cloudflare.Secret("StateStoreEncryptionKey", {
63
+ return yield* Secret.Secret("StateStoreEncryptionKey", {
64
+ name: EncryptionKeySecretName,
59
65
  store,
60
66
  value: random.text,
61
67
  });
@@ -1,5 +1,4 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- // import type * as cf from "@cloudflare/workers-types";
3
2
 
4
3
  import type * as Effect from "effect/Effect";
5
4
  import type { UnwrapEffect } from "../../Util/effect.ts";
@@ -24,6 +23,8 @@ type GetBindingType<T> = T extends Cloudflare.Assets
24
23
  ? KVNamespace
25
24
  : T extends Cloudflare.Queue
26
25
  ? Queue<unknown>
27
- : T extends Cloudflare.DurableObjectNamespaceLike
28
- ? DurableObjectNamespace<Exclude<T["Shape"], undefined>>
29
- : never;
26
+ : T extends Cloudflare.Artifacts
27
+ ? Artifacts
28
+ : T extends Cloudflare.DurableObjectNamespaceLike
29
+ ? DurableObjectNamespace<Exclude<T["Shape"], undefined>>
30
+ : never;