alchemy 0.3.1 → 0.4.1

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 (113) hide show
  1. package/README.md +8 -3
  2. package/lib/ai/document.d.ts +12 -1
  3. package/lib/ai/document.js +10 -1
  4. package/lib/ai/index.d.ts +0 -2
  5. package/lib/ai/index.js +0 -2
  6. package/lib/alchemy.d.ts +5 -0
  7. package/lib/alchemy.js +44 -1
  8. package/lib/apply.js +17 -10
  9. package/lib/aws/account-id.d.ts +4 -1
  10. package/lib/aws/account-id.js +1 -1
  11. package/lib/aws/credentials.d.ts +5 -0
  12. package/lib/aws/credentials.js +0 -0
  13. package/lib/aws/oidc/oidc-provider.js +2 -2
  14. package/lib/aws/role.js +35 -23
  15. package/lib/cloudflare/account-api-token.d.ts +168 -0
  16. package/lib/cloudflare/account-api-token.js +179 -0
  17. package/lib/cloudflare/account-id.d.ts +1 -0
  18. package/lib/cloudflare/account-id.js +0 -0
  19. package/lib/cloudflare/api.d.ts +38 -1
  20. package/lib/cloudflare/api.js +55 -4
  21. package/lib/cloudflare/assets.d.ts +74 -0
  22. package/lib/cloudflare/assets.js +73 -0
  23. package/lib/cloudflare/bindings.d.ts +6 -1
  24. package/lib/cloudflare/bindings.js +6 -0
  25. package/lib/cloudflare/bucket.d.ts +118 -3
  26. package/lib/cloudflare/bucket.js +269 -89
  27. package/lib/cloudflare/custom-domain.d.ts +75 -0
  28. package/lib/cloudflare/custom-domain.js +154 -0
  29. package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
  30. package/lib/cloudflare/index.d.ts +6 -1
  31. package/lib/cloudflare/index.js +6 -1
  32. package/lib/cloudflare/permission-groups.d.ts +78 -0
  33. package/lib/cloudflare/permission-groups.js +48 -0
  34. package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.js +55 -24
  36. package/lib/cloudflare/static-site.d.ts +17 -18
  37. package/lib/cloudflare/static-site.js +19 -10
  38. package/lib/cloudflare/worker.d.ts +14 -0
  39. package/lib/cloudflare/worker.js +150 -3
  40. package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
  41. package/lib/{util/encrypt.js → encrypt.js} +1 -1
  42. package/lib/fs/file-system-state-store.js +1 -1
  43. package/lib/fs/file.d.ts +18 -0
  44. package/lib/fs/file.js +6 -0
  45. package/lib/internal/{providers.d.ts → docs/providers.d.ts} +10 -4
  46. package/lib/internal/docs/providers.js +196 -0
  47. package/lib/secret.d.ts +3 -0
  48. package/lib/secret.js +13 -0
  49. package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
  50. package/lib/{util/serde.js → serde.js} +12 -4
  51. package/lib/test/bun.js +3 -2
  52. package/lib/util/sha256.d.ts +1 -0
  53. package/lib/util/sha256.js +4 -0
  54. package/lib/web/vitepress/config.d.ts +2 -1
  55. package/lib/web/vitepress/config.js +1 -0
  56. package/lib/web/vitepress/index.d.ts +1 -0
  57. package/lib/web/vitepress/index.js +1 -0
  58. package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
  59. package/lib/web/vitepress/process-front-matter-files.js +68 -0
  60. package/lib/web/vitepress/vitepress.js +3 -2
  61. package/package.json +8 -4
  62. package/src/ai/document.ts +26 -2
  63. package/src/ai/index.ts +0 -2
  64. package/src/alchemy.ts +56 -2
  65. package/src/apply.ts +21 -20
  66. package/src/aws/account-id.ts +6 -2
  67. package/src/aws/credentials.ts +6 -0
  68. package/src/aws/oidc/oidc-provider.ts +17 -17
  69. package/src/aws/role.ts +68 -54
  70. package/src/cloudflare/account-api-token.ts +365 -0
  71. package/src/cloudflare/account-id.ts +0 -0
  72. package/src/cloudflare/api.ts +88 -17
  73. package/src/cloudflare/assets.ts +155 -0
  74. package/src/cloudflare/bindings.ts +11 -2
  75. package/src/cloudflare/bucket.ts +493 -133
  76. package/src/cloudflare/custom-domain.ts +318 -0
  77. package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
  78. package/src/cloudflare/index.ts +6 -1
  79. package/src/cloudflare/permission-groups.ts +137 -0
  80. package/src/cloudflare/r2-rest-state-store.ts +87 -42
  81. package/src/cloudflare/static-site.ts +20 -32
  82. package/src/cloudflare/worker.ts +253 -5
  83. package/src/dns/import-dns.ts +4 -4
  84. package/src/{util/encrypt.ts → encrypt.ts} +7 -10
  85. package/src/fs/file-system-state-store.ts +5 -5
  86. package/src/fs/file.ts +29 -0
  87. package/src/internal/docs/providers.ts +281 -0
  88. package/src/secret.ts +16 -0
  89. package/src/{util/serde.ts → serde.ts} +16 -10
  90. package/src/test/bun.ts +8 -7
  91. package/src/util/sha256.ts +5 -0
  92. package/src/web/vitepress/config.ts +3 -1
  93. package/src/web/vitepress/index.ts +1 -0
  94. package/src/web/vitepress/process-front-matter-files.ts +98 -0
  95. package/src/web/vitepress/vitepress.ts +3 -2
  96. package/lib/ai/approve.d.ts +0 -99
  97. package/lib/ai/approve.js +0 -76
  98. package/lib/ai/review.d.ts +0 -122
  99. package/lib/ai/review.js +0 -101
  100. package/lib/internal/getting-started.d.ts +0 -21
  101. package/lib/internal/getting-started.js +0 -87
  102. package/lib/internal/index.d.ts +0 -3
  103. package/lib/internal/index.js +0 -3
  104. package/lib/internal/providers.js +0 -172
  105. package/lib/internal/tutorial.d.ts +0 -104
  106. package/lib/internal/tutorial.js +0 -251
  107. package/src/ai/approve.ts +0 -163
  108. package/src/ai/review.ts +0 -213
  109. package/src/internal/getting-started.ts +0 -115
  110. package/src/internal/index.ts +0 -3
  111. package/src/internal/providers.ts +0 -241
  112. package/src/internal/tutorial.ts +0 -392
  113. /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
package/README.md CHANGED
@@ -9,8 +9,10 @@ Resources are simple memoized async functions that can run in any JavaScript run
9
9
  ```ts
10
10
  import alchemy from "alchemy";
11
11
 
12
- await using app = alchemy("cloudflare-worker");
12
+ // initialize the app (with default state $USER)
13
+ const app = alchemy("cloudflare-worker");
13
14
 
15
+ // create a Cloudflare Worker
14
16
  export const worker = await Worker("worker", {
15
17
  name: "my-worker",
16
18
  entrypoint: "./src/index.ts",
@@ -18,10 +20,13 @@ export const worker = await Worker("worker", {
18
20
  COUNTER: counter,
19
21
  STORAGE: storage, // Bind the R2 bucket to the worker
20
22
  AUTH_STORE: authStore,
21
- GITHUB_CLIENT_ID: secret(process.env.GITHUB_CLIENT_ID),
22
- GITHUB_CLIENT_SECRET: secret(process.env.GITHUB_CLIENT_SECRET),
23
+ GITHUB_CLIENT_ID: alchemy.secret(process.env.GITHUB_CLIENT_ID),
24
+ GITHUB_CLIENT_SECRET: alchemy.secret(process.env.GITHUB_CLIENT_SECRET),
23
25
  },
24
26
  });
27
+
28
+ // finalize the alchemy app (triggering deletion of orphaned resources)
29
+ await app.finalize();
25
30
  ```
26
31
 
27
32
  # Features
@@ -10,8 +10,10 @@ import { type ModelConfig } from "./client";
10
10
  export interface DocumentProps {
11
11
  /**
12
12
  * Title of the document
13
+ *
14
+ * @default id
13
15
  */
14
- title: string;
16
+ title?: string;
15
17
  /**
16
18
  * Optional path to the markdown document
17
19
  * If provided, document will be written to this path
@@ -72,11 +74,20 @@ export interface DocumentProps {
72
74
  * @default 10000
73
75
  */
74
76
  maxTokens?: number;
77
+ /**
78
+ * Freeze the document after creation (do not re-generate on updates)
79
+ * @default false
80
+ */
81
+ freeze?: boolean;
75
82
  }
76
83
  /**
77
84
  * A markdown document that can be created, updated, and deleted
78
85
  */
79
86
  export interface Document extends DocumentProps, Resource<"docs::Document"> {
87
+ /**
88
+ * The title of the document
89
+ */
90
+ title: string;
80
91
  /**
81
92
  * Content of the document
82
93
  */
@@ -99,6 +99,14 @@ export const Document = Resource("docs::Document", async function (id, props) {
99
99
  if (this.phase === "delete") {
100
100
  return this.destroy();
101
101
  }
102
+ if (this.phase === "update" && props.freeze) {
103
+ if (props.path) {
104
+ const filePath = props.path;
105
+ const fileId = `${id}-file`;
106
+ await StaticTextFile(fileId, filePath, this.output.content);
107
+ }
108
+ return this(this.output);
109
+ }
102
110
  // Use provided system prompt or default
103
111
  const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
104
112
  // Generate initial content with rate limit retry
@@ -137,7 +145,7 @@ export const Document = Resource("docs::Document", async function (id, props) {
137
145
  });
138
146
  const retryResult = extractMarkdownContent(retryText);
139
147
  if (retryResult.error) {
140
- throw new Error(`Failed to generate valid markdown content: ${retryResult.error}`);
148
+ throw new Error(`Failed to generate valid markdown content: ${retryResult.error}\n${retryText}`);
141
149
  }
142
150
  content = retryResult.content;
143
151
  }
@@ -151,6 +159,7 @@ export const Document = Resource("docs::Document", async function (id, props) {
151
159
  ],
152
160
  createdAt: this.output?.createdAt || Date.now(),
153
161
  updatedAt: Date.now(),
162
+ title: props.title || id,
154
163
  };
155
164
  // Write file if path is provided
156
165
  if (props.path) {
package/lib/ai/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * from "./approve";
2
1
  export * from "./ark";
3
2
  export * from "./astro-file";
4
3
  export * from "./css-file";
@@ -6,7 +5,6 @@ export * from "./data";
6
5
  export * from "./document";
7
6
  export * from "./html-file";
8
7
  export * from "./json-file";
9
- export * from "./review";
10
8
  export * from "./typescript-file";
11
9
  export * from "./vue-file";
12
10
  export * from "./yaml-file";
package/lib/ai/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export * from "./approve";
2
1
  export * from "./ark";
3
2
  export * from "./astro-file";
4
3
  export * from "./css-file";
@@ -6,7 +5,6 @@ export * from "./data";
6
5
  export * from "./document";
7
6
  export * from "./html-file";
8
7
  export * from "./json-file";
9
- export * from "./review";
10
8
  export * from "./typescript-file";
11
9
  export * from "./vue-file";
12
10
  export * from "./yaml-file";
package/lib/alchemy.d.ts CHANGED
@@ -31,6 +31,10 @@ export interface Alchemy {
31
31
  scope: typeof scope;
32
32
  run: typeof run;
33
33
  destroy: typeof destroy;
34
+ /**
35
+ * Get an environment variable and error if it's not set.
36
+ */
37
+ env: typeof env;
34
38
  /**
35
39
  * Creates an encrypted secret that can be safely stored in state files.
36
40
  * Requires a password to be set either globally in the application options
@@ -146,4 +150,5 @@ declare function run<T>(...args: [id: string, fn: (this: Scope, scope: Scope) =>
146
150
  options: AlchemyOptions,
147
151
  fn: (this: Scope, scope: Scope) => Promise<T>
148
152
  ]): Promise<T>;
153
+ export declare function env<T = string>(name: string, value?: T | undefined, error?: string): Promise<T>;
149
154
  export {};
package/lib/alchemy.js CHANGED
@@ -13,11 +13,17 @@ export const alchemy = _alchemy;
13
13
  function _alchemy(...args) {
14
14
  if (typeof args[0] === "string") {
15
15
  const [appName, options] = args;
16
- return scope(undefined, {
16
+ const root = scope(undefined, {
17
17
  ...options,
18
18
  appName,
19
19
  stage: options?.stage,
20
20
  });
21
+ if (options?.phase === "destroy") {
22
+ return destroy(root).finally(() => {
23
+ process.exit(0);
24
+ });
25
+ }
26
+ return root;
21
27
  }
22
28
  else {
23
29
  const [template, ...values] = args;
@@ -64,6 +70,22 @@ function _alchemy(...args) {
64
70
  else if (Array.isArray(value)) {
65
71
  return (await Promise.all(value.map(async (value, i) => `${i}. ${await resolve(value)}`))).join("\n");
66
72
  }
73
+ else if (typeof value === "object" &&
74
+ typeof value.path === "string") {
75
+ if (typeof value.content === "string") {
76
+ appendices[value.path] = value.content;
77
+ return `[${path.basename(value.path)}](${value.path})`;
78
+ }
79
+ else {
80
+ appendices[value.path] = await fs.readFile(value.path, "utf-8");
81
+ return `[${path.basename(value.path)}](${value.path})`;
82
+ }
83
+ }
84
+ else if (typeof value === "object") {
85
+ return (await Promise.all(Object.entries(value).map(async ([key, value]) => {
86
+ return `* ${key}: ${await resolve(value)}`;
87
+ }))).join("\n");
88
+ }
67
89
  else {
68
90
  // TODO: support other types
69
91
  console.log(value);
@@ -102,6 +124,7 @@ _alchemy.destroy = destroy;
102
124
  _alchemy.run = run;
103
125
  _alchemy.scope = scope;
104
126
  _alchemy.secret = secret;
127
+ _alchemy.env = env;
105
128
  /**
106
129
  * Enter a new scope synchronously.
107
130
  *
@@ -165,3 +188,23 @@ async function run(...args) {
165
188
  await scope.finalize();
166
189
  }
167
190
  }
191
+ export async function env(name, value, error) {
192
+ if (value !== undefined) {
193
+ return value;
194
+ }
195
+ else if (typeof process !== undefined) {
196
+ // we are in a node environment
197
+ return process.env[name];
198
+ }
199
+ else {
200
+ // we are in a browser environment
201
+ try {
202
+ const { env } = await import("cloudflare:workers");
203
+ if (name in env) {
204
+ return env[name];
205
+ }
206
+ }
207
+ catch (error) { }
208
+ }
209
+ throw new Error(error ?? `Environment variable ${name} is not set`);
210
+ }
package/lib/apply.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { alchemy } from "./alchemy";
2
2
  import { context } from "./context";
3
3
  import { PROVIDERS, } from "./resource";
4
- import { Secret } from "./secret";
5
- import { serialize } from "./util/serde";
4
+ import { serialize } from "./serde";
6
5
  export async function apply(resource, props, options) {
7
6
  const scope = resource.Scope;
8
7
  try {
@@ -36,20 +35,27 @@ export async function apply(resource, props, options) {
36
35
  const alwaysUpdate = options?.alwaysUpdate ?? provider.options?.alwaysUpdate ?? false;
37
36
  // Skip update if inputs haven't changed and resource is in a stable state
38
37
  if (state.status === "created" || state.status === "updated") {
39
- const oldProps = JSON.stringify(state.props, (_, value) => value instanceof Secret
40
- ? {
41
- "@secret": value.unencrypted,
42
- }
43
- : value, 2);
44
- const newProps = JSON.stringify(await serialize(scope, props, {
38
+ const oldProps = await serialize(scope, state.props, {
39
+ encrypt: false,
40
+ });
41
+ const newProps = await serialize(scope, props, {
45
42
  encrypt: false,
46
- }), null, 2);
47
- if (oldProps === newProps && alwaysUpdate !== true) {
43
+ });
44
+ if (JSON.stringify(oldProps) === JSON.stringify(newProps) &&
45
+ alwaysUpdate !== true) {
48
46
  if (!quiet) {
49
47
  // console.log(`Skip: "${resource.FQN}" (no changes)`);
50
48
  }
51
49
  return state.output;
52
50
  }
51
+ const diff = await import("diff");
52
+ const yaml = await import("yaml");
53
+ const old = yaml.stringify(oldProps);
54
+ const news = yaml.stringify(newProps);
55
+ const patch = diff.createPatch(resource.ID, old, news);
56
+ // console.log(patch);
57
+ // if (resource.ID === "what-is-alchemy") {
58
+ // }
53
59
  }
54
60
  const phase = state.status === "creating" ? "create" : "update";
55
61
  state.status = phase === "create" ? "creating" : "updating";
@@ -98,6 +104,7 @@ export async function apply(resource, props, options) {
98
104
  return output;
99
105
  }
100
106
  catch (error) {
107
+ console.error(new Error().stack);
101
108
  scope.fail();
102
109
  throw error;
103
110
  }
@@ -1,4 +1,7 @@
1
+ export type AccountId = string & {
2
+ readonly __brand: "AccountId";
3
+ };
1
4
  /**
2
5
  * Helper to get the current AWS account ID
3
6
  */
4
- export declare function getAccountId(): Promise<string>;
7
+ export declare function AccountId(): Promise<AccountId>;
@@ -3,7 +3,7 @@ const sts = new STSClient({});
3
3
  /**
4
4
  * Helper to get the current AWS account ID
5
5
  */
6
- export async function getAccountId() {
6
+ export async function AccountId() {
7
7
  const identity = await sts.send(new GetCallerIdentityCommand({}));
8
8
  return identity.Account;
9
9
  }
@@ -0,0 +1,5 @@
1
+ import type { Secret } from "../secret";
2
+ export interface AwsCredentials {
3
+ accessKeyId: Secret;
4
+ secretAccessKey: Secret;
5
+ }
File without changes
@@ -1,6 +1,6 @@
1
1
  import { CreateOpenIDConnectProviderCommand, DeleteOpenIDConnectProviderCommand, GetOpenIDConnectProviderCommand, GetRoleCommand, IAMClient, UpdateAssumeRolePolicyCommand, } from "@aws-sdk/client-iam";
2
2
  import { Resource } from "../../resource";
3
- import { getAccountId } from "../account-id";
3
+ import { AccountId } from "../account-id";
4
4
  /**
5
5
  * AWS OIDC Provider Resource for GitHub Actions
6
6
  *
@@ -91,7 +91,7 @@ export const OIDCProvider = Resource("aws::OIDCProvider", async function (id, pr
91
91
  catch (error) {
92
92
  if (error.name === "EntityAlreadyExistsException") {
93
93
  // Provider exists, use its ARN
94
- providerArn = `arn:aws:iam::${await getAccountId()}:oidc-provider/${url}`;
94
+ providerArn = `arn:aws:iam::${await AccountId()}:oidc-provider/${url}`;
95
95
  }
96
96
  else {
97
97
  throw error;
package/lib/aws/role.js CHANGED
@@ -118,38 +118,50 @@ import { ignore } from "../util/ignore";
118
118
  export const Role = Resource("iam::Role", async function (id, props) {
119
119
  const client = new IAMClient({});
120
120
  if (this.phase === "delete") {
121
- // Delete any inline policies first
122
- if (props.policies) {
123
- for (const policy of props.policies) {
124
- await ignore(NoSuchEntityException.name, () => client.send(new DeleteRolePolicyCommand({
125
- RoleName: props.roleName,
126
- PolicyName: policy.policyName,
127
- })));
128
- }
129
- }
130
- // We need to detach managed policies before deleting the role
131
- // First, get all attached policies
132
121
  try {
133
- const attachedPoliciesResponse = await client.send(new ListAttachedRolePoliciesCommand({
134
- RoleName: props.roleName,
135
- }));
136
- // Detach all managed policies
137
- const attachedPolicies = attachedPoliciesResponse.AttachedPolicies || [];
138
- for (const policy of attachedPolicies) {
139
- await client.send(new DetachRolePolicyCommand({
122
+ // Delete any inline policies first
123
+ if (props.policies) {
124
+ for (const policy of props.policies) {
125
+ await ignore(NoSuchEntityException.name, () => client.send(new DeleteRolePolicyCommand({
126
+ RoleName: props.roleName,
127
+ PolicyName: policy.policyName,
128
+ })));
129
+ }
130
+ }
131
+ // We need to detach managed policies before deleting the role
132
+ // First, get all attached policies
133
+ try {
134
+ const attachedPoliciesResponse = await client.send(new ListAttachedRolePoliciesCommand({
140
135
  RoleName: props.roleName,
141
- PolicyArn: policy.PolicyArn,
142
136
  }));
137
+ // Detach all managed policies
138
+ const attachedPolicies = attachedPoliciesResponse.AttachedPolicies || [];
139
+ for (const policy of attachedPolicies) {
140
+ await ignore(NoSuchEntityException.name, () => client.send(new DetachRolePolicyCommand({
141
+ RoleName: props.roleName,
142
+ PolicyArn: policy.PolicyArn,
143
+ })));
144
+ }
143
145
  }
146
+ catch (error) {
147
+ if (error.name !== NoSuchEntityException.name) {
148
+ throw error;
149
+ }
150
+ // Role doesn't exist, no need to continue with detaching policies
151
+ }
152
+ // Try to delete the role, ignoring if it doesn't exist
153
+ await ignore(NoSuchEntityException.name, () => client.send(new DeleteRoleCommand({
154
+ RoleName: props.roleName,
155
+ })));
144
156
  }
145
157
  catch (error) {
158
+ // If we get any other error besides NoSuchEntityException, log it but don't fail
159
+ // This ensures the resource is still marked as destroyed
146
160
  if (error.name !== NoSuchEntityException.name) {
147
- throw error;
161
+ console.error(`Error deleting role ${props.roleName}:`, error);
148
162
  }
149
163
  }
150
- await ignore(NoSuchEntityException.name, () => client.send(new DeleteRoleCommand({
151
- RoleName: props.roleName,
152
- })));
164
+ // Always return destroyed state regardless of any errors
153
165
  return this.destroy();
154
166
  }
155
167
  const assumeRolePolicyDocument = JSON.stringify(props.assumeRolePolicy);
@@ -0,0 +1,168 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { Secret } from "../secret";
4
+ /**
5
+ * Permission group for a token policy
6
+ */
7
+ export interface TokenPolicyPermissionGroup {
8
+ /**
9
+ * ID of the permission group
10
+ */
11
+ id: string;
12
+ /**
13
+ * Optional metadata for the permission group
14
+ */
15
+ meta?: Record<string, any>;
16
+ }
17
+ /**
18
+ * Policy that defines what the token can access
19
+ */
20
+ export interface TokenPolicy {
21
+ /**
22
+ * Effect of the policy
23
+ */
24
+ effect: "allow" | "deny";
25
+ /**
26
+ * Permission groups to include in the policy
27
+ */
28
+ permissionGroups: TokenPolicyPermissionGroup[];
29
+ /**
30
+ * Resources the policy applies to
31
+ */
32
+ resources: Record<string, string>;
33
+ }
34
+ /**
35
+ * Condition for token usage (e.g., IP restrictions)
36
+ */
37
+ export interface TokenCondition {
38
+ /**
39
+ * IP address conditions
40
+ */
41
+ requestIp?: {
42
+ /**
43
+ * IP ranges to allow
44
+ */
45
+ in?: string[];
46
+ /**
47
+ * IP ranges to deny
48
+ */
49
+ notIn?: string[];
50
+ };
51
+ }
52
+ /**
53
+ * Properties for creating or updating an Account API Token
54
+ */
55
+ export interface AccountApiTokenProps {
56
+ /**
57
+ * Name of the token
58
+ */
59
+ name: string;
60
+ /**
61
+ * Policies that define what the token can access
62
+ */
63
+ policies: TokenPolicy[];
64
+ /**
65
+ * Optional expiration date for the token (ISO format)
66
+ */
67
+ expiresOn?: string;
68
+ /**
69
+ * Optional "not before" date (token is not valid before this date) (ISO format)
70
+ */
71
+ notBefore?: string;
72
+ /**
73
+ * Optional conditions for token use (like IP restrictions)
74
+ */
75
+ condition?: TokenCondition;
76
+ }
77
+ /**
78
+ * Output returned after Account API Token creation/update
79
+ */
80
+ export interface AccountApiToken extends Resource<"cloudflare::AccountApiToken">, AccountApiTokenProps {
81
+ /**
82
+ * The ID of the token
83
+ *
84
+ * Equiv. to ACCESS_KEY_ID
85
+ */
86
+ id: string;
87
+ /**
88
+ * Status of the token
89
+ */
90
+ status: string;
91
+ /**
92
+ * Actual token value (only available on creation)
93
+ * Stored as a Secret for security
94
+ *
95
+ * Equiv. to SECRET_ACCESS_KEY
96
+ */
97
+ value?: Secret;
98
+ /**
99
+ * Access key ID for the token
100
+ *
101
+ * An alias of {@link id}
102
+ */
103
+ accessKeyId: string;
104
+ /**
105
+ * Secret access key for the token
106
+ *
107
+ * The SHA-256 hash of the token {@link value}
108
+ *
109
+ * @see https://developers.cloudflare.com/r2/api/tokens/#get-s3-api-credentials-from-an-api-token
110
+ */
111
+ secretAccessKey: string;
112
+ }
113
+ /**
114
+ * Creates a Cloudflare Account API Token with specified permissions.
115
+ *
116
+ * @see https://developers.cloudflare.com/api/resources/accounts/subresources/tokens/methods/create/
117
+ *
118
+ * @example
119
+ * // First, fetch all permission groups
120
+ * const permissions = await PermissionGroups("cloudflare-permissions", {
121
+ * accountId: cfAccountId,
122
+ * });
123
+ *
124
+ * // Create a token with read-only permissions for specific zones
125
+ * const readOnlyToken = await AccountApiToken("readonly-token", {
126
+ * name: "Readonly Zone Token",
127
+ * policies: [
128
+ * {
129
+ * effect: "allow",
130
+ * permissionGroups: [
131
+ * { id: permissions["Zone Read"].id },
132
+ * { id: permissions["Analytics Read"].id }
133
+ * ],
134
+ * resources: {
135
+ * "com.cloudflare.api.account.zone.22b1de5f1c0e4b3ea97bb1e963b06a43": "*",
136
+ * "com.cloudflare.api.account.zone.eb78d65290b24279ba6f44721b3ea3c4": "*"
137
+ * }
138
+ * }
139
+ * ],
140
+ * expiresOn: "2024-12-31T23:59:59Z"
141
+ * });
142
+ *
143
+ * @example
144
+ * // Create a token with time and IP restrictions
145
+ * const restrictedToken = await AccountApiToken("restricted-token", {
146
+ * name: "Restricted Access Token",
147
+ * policies: [
148
+ * {
149
+ * effect: "allow",
150
+ * permissionGroups: [
151
+ * { id: permissions["Worker Routes Edit"].id }
152
+ * ],
153
+ * resources: {
154
+ * "com.cloudflare.api.account.worker.route.*": "*"
155
+ * }
156
+ * }
157
+ * ],
158
+ * notBefore: "2023-01-01T00:00:00Z",
159
+ * expiresOn: "2023-12-31T23:59:59Z",
160
+ * condition: {
161
+ * requestIp: {
162
+ * in: ["192.168.1.0/24", "10.0.0.0/8"],
163
+ * notIn: ["192.168.1.100/32"]
164
+ * }
165
+ * }
166
+ * });
167
+ */
168
+ export declare const AccountApiToken: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<AccountApiToken>, id: string, props: AccountApiTokenProps) => Promise<AccountApiToken>);