alchemy 0.22.3 → 0.23.0

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 (178) hide show
  1. package/lib/aws/bucket.d.ts.map +1 -1
  2. package/lib/aws/bucket.js +13 -12
  3. package/lib/aws/bucket.js.map +1 -1
  4. package/lib/aws/control/client.d.ts.map +1 -1
  5. package/lib/aws/control/client.js +5 -3
  6. package/lib/aws/control/client.js.map +1 -1
  7. package/lib/aws/control/resource.d.ts.map +1 -1
  8. package/lib/aws/control/resource.js +13 -4
  9. package/lib/aws/control/resource.js.map +1 -1
  10. package/lib/aws/control/types.d.ts +85129 -0
  11. package/lib/aws/function.d.ts.map +1 -1
  12. package/lib/aws/function.js +51 -42
  13. package/lib/aws/function.js.map +1 -1
  14. package/lib/aws/index.d.ts +1 -0
  15. package/lib/aws/index.d.ts.map +1 -1
  16. package/lib/aws/index.js +1 -0
  17. package/lib/aws/index.js.map +1 -1
  18. package/lib/aws/policy-attachment.d.ts.map +1 -1
  19. package/lib/aws/policy-attachment.js +5 -4
  20. package/lib/aws/policy-attachment.js.map +1 -1
  21. package/lib/aws/policy.d.ts.map +1 -1
  22. package/lib/aws/policy.js +21 -20
  23. package/lib/aws/policy.js.map +1 -1
  24. package/lib/aws/queue.d.ts.map +1 -1
  25. package/lib/aws/queue.js +19 -15
  26. package/lib/aws/queue.js.map +1 -1
  27. package/lib/aws/retry.d.ts +5 -0
  28. package/lib/aws/retry.d.ts.map +1 -0
  29. package/lib/aws/retry.js +27 -0
  30. package/lib/aws/retry.js.map +1 -0
  31. package/lib/aws/role.d.ts.map +1 -1
  32. package/lib/aws/role.js +31 -30
  33. package/lib/aws/role.js.map +1 -1
  34. package/lib/aws/ses.d.ts.map +1 -1
  35. package/lib/aws/ses.js +27 -26
  36. package/lib/aws/ses.js.map +1 -1
  37. package/lib/aws/ssm-parameter.d.ts +150 -0
  38. package/lib/aws/ssm-parameter.d.ts.map +1 -0
  39. package/lib/aws/ssm-parameter.js +184 -0
  40. package/lib/aws/ssm-parameter.js.map +1 -0
  41. package/lib/aws/table.d.ts.map +1 -1
  42. package/lib/aws/table.js +9 -27
  43. package/lib/aws/table.js.map +1 -1
  44. package/lib/cloudflare/account-id.d.ts +1 -0
  45. package/lib/cloudflare/account-id.d.ts.map +1 -1
  46. package/lib/cloudflare/account-id.js.map +1 -1
  47. package/lib/cloudflare/api.d.ts.map +1 -1
  48. package/lib/cloudflare/api.js +22 -2
  49. package/lib/cloudflare/api.js.map +1 -1
  50. package/lib/cloudflare/bindings.d.ts +2 -1
  51. package/lib/cloudflare/bindings.d.ts.map +1 -1
  52. package/lib/cloudflare/bindings.js.map +1 -1
  53. package/lib/cloudflare/bound.d.ts +2 -1
  54. package/lib/cloudflare/bound.d.ts.map +1 -1
  55. package/lib/cloudflare/index.d.ts +2 -0
  56. package/lib/cloudflare/index.d.ts.map +1 -1
  57. package/lib/cloudflare/index.js +2 -0
  58. package/lib/cloudflare/index.js.map +1 -1
  59. package/lib/cloudflare/queue.d.ts.map +1 -1
  60. package/lib/cloudflare/queue.js +1 -0
  61. package/lib/cloudflare/queue.js.map +1 -1
  62. package/lib/cloudflare/route.d.ts +7 -0
  63. package/lib/cloudflare/route.d.ts.map +1 -1
  64. package/lib/cloudflare/route.js +44 -2
  65. package/lib/cloudflare/route.js.map +1 -1
  66. package/lib/cloudflare/version-metadata.d.ts +7 -0
  67. package/lib/cloudflare/version-metadata.d.ts.map +1 -0
  68. package/lib/cloudflare/version-metadata.js +7 -0
  69. package/lib/cloudflare/version-metadata.js.map +1 -0
  70. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  71. package/lib/cloudflare/worker-metadata.js +7 -2
  72. package/lib/cloudflare/worker-metadata.js.map +1 -1
  73. package/lib/cloudflare/worker.d.ts +2 -1
  74. package/lib/cloudflare/worker.d.ts.map +1 -1
  75. package/lib/cloudflare/worker.js +17 -8
  76. package/lib/cloudflare/worker.js.map +1 -1
  77. package/lib/cloudflare/workflow.d.ts +7 -0
  78. package/lib/cloudflare/workflow.d.ts.map +1 -1
  79. package/lib/cloudflare/workflow.js +2 -0
  80. package/lib/cloudflare/workflow.js.map +1 -1
  81. package/lib/cloudflare/wrangler.json.d.ts +7 -0
  82. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  83. package/lib/cloudflare/wrangler.json.js +14 -2
  84. package/lib/cloudflare/wrangler.json.js.map +1 -1
  85. package/lib/destroy.js +1 -1
  86. package/lib/destroy.js.map +1 -1
  87. package/lib/dns/godaddy.d.ts.map +1 -1
  88. package/lib/dns/godaddy.js +2 -1
  89. package/lib/dns/godaddy.js.map +1 -1
  90. package/lib/fs/file-system-state-store.d.ts.map +1 -1
  91. package/lib/fs/file-system-state-store.js +14 -2
  92. package/lib/fs/file-system-state-store.js.map +1 -1
  93. package/lib/fs/static-json-file.d.ts +1 -0
  94. package/lib/fs/static-json-file.d.ts.map +1 -1
  95. package/lib/fs/static-json-file.js +11 -8
  96. package/lib/fs/static-json-file.js.map +1 -1
  97. package/lib/github/secret.d.ts.map +1 -1
  98. package/lib/github/secret.js +1 -0
  99. package/lib/github/secret.js.map +1 -1
  100. package/lib/neon/api.d.ts.map +1 -1
  101. package/lib/neon/api.js +2 -1
  102. package/lib/neon/api.js.map +1 -1
  103. package/lib/runtime/shims.d.ts +6 -0
  104. package/lib/runtime/shims.d.ts.map +1 -0
  105. package/lib/runtime/shims.js +27 -0
  106. package/lib/runtime/shims.js.map +1 -0
  107. package/lib/sentry/api.d.ts.map +1 -1
  108. package/lib/sentry/api.js +2 -1
  109. package/lib/sentry/api.js.map +1 -1
  110. package/lib/test/bun.d.ts.map +1 -1
  111. package/lib/test/bun.js +1 -7
  112. package/lib/test/bun.js.map +1 -1
  113. package/lib/test/prune.d.ts +2 -1
  114. package/lib/test/prune.d.ts.map +1 -1
  115. package/lib/test/prune.js +29 -5
  116. package/lib/test/prune.js.map +1 -1
  117. package/lib/test/vitest.d.ts +92 -0
  118. package/lib/test/vitest.d.ts.map +1 -0
  119. package/lib/test/vitest.js +94 -0
  120. package/lib/test/vitest.js.map +1 -0
  121. package/lib/upstash/api.d.ts.map +1 -1
  122. package/lib/upstash/api.js +2 -1
  123. package/lib/upstash/api.js.map +1 -1
  124. package/lib/util/retry.js +2 -2
  125. package/lib/util/retry.js.map +1 -1
  126. package/lib/util/safe-fetch.d.ts +2 -0
  127. package/lib/util/safe-fetch.d.ts.map +1 -0
  128. package/lib/util/safe-fetch.js +48 -0
  129. package/lib/util/safe-fetch.js.map +1 -0
  130. package/lib/vercel/api.d.ts.map +1 -1
  131. package/lib/vercel/api.js +2 -1
  132. package/lib/vercel/api.js.map +1 -1
  133. package/lib/web/vitepress/process-front-matter-files.d.ts.map +1 -1
  134. package/lib/web/vitepress/process-front-matter-files.js +11 -7
  135. package/lib/web/vitepress/process-front-matter-files.js.map +1 -1
  136. package/package.json +12 -1
  137. package/src/alchemy.ts +2 -2
  138. package/src/aws/bucket.ts +55 -36
  139. package/src/aws/control/client.ts +13 -13
  140. package/src/aws/control/resource.ts +19 -3
  141. package/src/aws/function.ts +233 -178
  142. package/src/aws/index.ts +1 -0
  143. package/src/aws/policy-attachment.ts +15 -10
  144. package/src/aws/policy.ts +75 -54
  145. package/src/aws/queue.ts +53 -36
  146. package/src/aws/retry.ts +38 -0
  147. package/src/aws/role.ts +121 -90
  148. package/src/aws/ses.ts +101 -73
  149. package/src/aws/ssm-parameter.ts +315 -0
  150. package/src/aws/table.ts +61 -90
  151. package/src/cloudflare/account-id.ts +2 -0
  152. package/src/cloudflare/api.ts +26 -3
  153. package/src/cloudflare/bindings.ts +2 -0
  154. package/src/cloudflare/bound.ts +8 -5
  155. package/src/cloudflare/index.ts +2 -0
  156. package/src/cloudflare/queue.ts +2 -0
  157. package/src/cloudflare/route.ts +86 -2
  158. package/src/cloudflare/version-metadata.ts +6 -0
  159. package/src/cloudflare/worker-metadata.ts +6 -2
  160. package/src/cloudflare/worker.ts +21 -9
  161. package/src/cloudflare/workflow.ts +8 -0
  162. package/src/cloudflare/wrangler.json.ts +29 -3
  163. package/src/destroy.ts +1 -1
  164. package/src/dns/godaddy.ts +3 -1
  165. package/src/fs/file-system-state-store.ts +16 -2
  166. package/src/fs/static-json-file.ts +12 -8
  167. package/src/github/secret.ts +2 -0
  168. package/src/neon/api.ts +2 -1
  169. package/src/sentry/api.ts +2 -1
  170. package/src/test/bun.ts +1 -7
  171. package/src/test/prune.ts +31 -4
  172. package/src/test/vitest.ts +209 -0
  173. package/src/upstash/api.ts +2 -1
  174. package/src/util/retry.ts +3 -3
  175. package/src/util/safe-fetch.ts +61 -0
  176. package/src/vercel/api.ts +2 -1
  177. package/src/web/vitepress/process-front-matter-files.ts +15 -7
  178. package/lib/tsconfig.test.tsbuildinfo +0 -1
@@ -0,0 +1,315 @@
1
+ import {
2
+ AddTagsToResourceCommand,
3
+ DeleteParameterCommand,
4
+ GetParameterCommand,
5
+ ParameterAlreadyExists,
6
+ ParameterNotFound,
7
+ PutParameterCommand,
8
+ SSMClient,
9
+ type Tag,
10
+ } from "@aws-sdk/client-ssm";
11
+ import type { Context } from "../context.ts";
12
+ import { Resource } from "../resource.ts";
13
+ import { type Secret, isSecret } from "../secret.ts";
14
+ import { ignore } from "../util/ignore.ts";
15
+
16
+ /**
17
+ * Base properties shared by all SSM Parameter types
18
+ */
19
+ interface SSMParameterBaseProps {
20
+ /**
21
+ * Name of the parameter
22
+ */
23
+ name: string;
24
+
25
+ /**
26
+ * Description of the parameter's purpose
27
+ */
28
+ description?: string;
29
+
30
+ /**
31
+ * KMS Key ID for SecureString parameters
32
+ * If not specified, uses the default KMS key for SSM
33
+ */
34
+ keyId?: string;
35
+
36
+ /**
37
+ * Parameter tier (Standard or Advanced)
38
+ * Default: "Standard"
39
+ */
40
+ tier?: "Standard" | "Advanced";
41
+
42
+ /**
43
+ * Policies to apply to the parameter (JSON string)
44
+ */
45
+ policies?: string;
46
+
47
+ /**
48
+ * Data type for String parameters
49
+ */
50
+ dataType?: "text" | "aws:ec2:image";
51
+
52
+ /**
53
+ * Resource tags for the parameter
54
+ */
55
+ tags?: Record<string, string>;
56
+ }
57
+
58
+ /**
59
+ * Properties for creating or updating an SSM Parameter
60
+ */
61
+ export type SSMParameterProps =
62
+ | (SSMParameterBaseProps & {
63
+ /**
64
+ * Type of parameter - SecureString for encrypted values
65
+ */
66
+ type: "SecureString";
67
+ /**
68
+ * Secret value that will be encrypted in AWS SSM and in alchemy state files
69
+ */
70
+ value: Secret;
71
+ })
72
+ | (SSMParameterBaseProps & {
73
+ /**
74
+ * Type of parameter - StringList for arrays of strings
75
+ */
76
+ type: "StringList";
77
+ /**
78
+ * Array of strings that will be stored as comma-separated values
79
+ */
80
+ value: string[];
81
+ })
82
+ | (SSMParameterBaseProps & {
83
+ /**
84
+ * Type of parameter - String for plain text values
85
+ * Default: "String"
86
+ */
87
+ type?: "String";
88
+ /**
89
+ * Plain text value of the parameter
90
+ */
91
+ value: string;
92
+ });
93
+
94
+ /**
95
+ * Output returned after SSM Parameter creation/update
96
+ */
97
+ export type SSMParameter = Resource<"ssm::Parameter"> & {
98
+ /**
99
+ * ARN of the parameter
100
+ */
101
+ arn: string;
102
+
103
+ /**
104
+ * Version of the parameter
105
+ */
106
+ version: number;
107
+
108
+ /**
109
+ * Last modified date
110
+ */
111
+ lastModifiedDate: Date;
112
+ } & SSMParameterProps;
113
+
114
+ /**
115
+ * AWS SSM Parameter Store Parameter Resource
116
+ *
117
+ * Creates and manages SSM parameters with support for different parameter types,
118
+ * encryption, and automatic tag management. Uses discriminated union types to
119
+ * ensure SecureString parameters always use Secret values and StringList uses arrays.
120
+ *
121
+ * @example
122
+ * // Create a basic string parameter
123
+ * const basicParam = await SSMParameter("app-config", {
124
+ * name: "/myapp/config/database-url",
125
+ * value: "postgresql://localhost:5432/myapp",
126
+ * description: "Database connection URL",
127
+ * tags: {
128
+ * Environment: "production",
129
+ * Application: "myapp"
130
+ * }
131
+ * });
132
+ *
133
+ * @example
134
+ * // Create a secure string parameter for secrets
135
+ * const secretParam = await SSMParameter("app-secret", {
136
+ * name: "/myapp/secrets/api-key",
137
+ * value: alchemy.secret("super-secret-api-key"),
138
+ * type: "SecureString",
139
+ * description: "Third-party API key",
140
+ * tags: {
141
+ * Environment: "production",
142
+ * Secret: "true"
143
+ * }
144
+ * });
145
+ *
146
+ * @example
147
+ * // Create a parameter with custom KMS key
148
+ * const encryptedParam = await SSMParameter("encrypted-config", {
149
+ * name: "/myapp/config/encrypted",
150
+ * value: alchemy.secret("sensitive-configuration-data"),
151
+ * type: "SecureString",
152
+ * keyId: "alias/myapp-kms-key",
153
+ * description: "Encrypted configuration data",
154
+ * tier: "Advanced",
155
+ * tags: {
156
+ * Environment: "production",
157
+ * Encrypted: "true"
158
+ * }
159
+ * });
160
+ *
161
+ * @example
162
+ * // Create a string list parameter
163
+ * const listParam = await SSMParameter("server-list", {
164
+ * name: "/myapp/config/servers",
165
+ * value: ["server1.example.com", "server2.example.com", "server3.example.com"],
166
+ * type: "StringList",
167
+ * description: "List of application servers",
168
+ * tags: {
169
+ * Environment: "production",
170
+ * Type: "configuration"
171
+ * }
172
+ * });
173
+ */
174
+ export const SSMParameter = Resource(
175
+ "ssm::Parameter",
176
+ async function (
177
+ this: Context<SSMParameter>,
178
+ _id: string,
179
+ props: SSMParameterProps,
180
+ ): Promise<SSMParameter> {
181
+ const client = new SSMClient({});
182
+
183
+ if (this.phase === "delete") {
184
+ try {
185
+ await ignore(ParameterNotFound.name, () =>
186
+ client.send(
187
+ new DeleteParameterCommand({
188
+ Name: props.name,
189
+ }),
190
+ ),
191
+ );
192
+ } catch (error: any) {
193
+ if (!(error instanceof ParameterNotFound)) {
194
+ throw error;
195
+ }
196
+ }
197
+
198
+ return this.destroy();
199
+ }
200
+
201
+ const parameterType = props.type || "String";
202
+
203
+ // Extract the actual value and handle type-specific conversions
204
+ let parameterValue: string;
205
+ if (isSecret(props.value)) {
206
+ parameterValue = props.value.unencrypted;
207
+ } else if (Array.isArray(props.value)) {
208
+ // Convert string array to comma-separated string for StringList
209
+ parameterValue = props.value.join(",");
210
+ } else {
211
+ parameterValue = props.value;
212
+ }
213
+
214
+ try {
215
+ // First, try to create the parameter without overwrite to include tags
216
+ try {
217
+ await client.send(
218
+ new PutParameterCommand({
219
+ Name: props.name,
220
+ Value: parameterValue,
221
+ Type: parameterType,
222
+ Description: props.description,
223
+ KeyId: props.keyId,
224
+ Tier: props.tier,
225
+ Policies: props.policies,
226
+ DataType: props.dataType,
227
+ Overwrite: false, // Don't overwrite, include tags
228
+ Tags: [
229
+ ...Object.entries(props.tags || {}).map(([Key, Value]) => ({
230
+ Key,
231
+ Value,
232
+ })),
233
+ {
234
+ Key: "alchemy_stage",
235
+ Value: this.stage,
236
+ },
237
+ {
238
+ Key: "alchemy_resource",
239
+ Value: this.id,
240
+ },
241
+ ],
242
+ }),
243
+ );
244
+ } catch (error: any) {
245
+ // If parameter already exists, update it with overwrite (no tags in this call)
246
+ if (error instanceof ParameterAlreadyExists) {
247
+ await client.send(
248
+ new PutParameterCommand({
249
+ Name: props.name,
250
+ Value: parameterValue,
251
+ Type: parameterType,
252
+ Description: props.description,
253
+ KeyId: props.keyId,
254
+ Tier: props.tier,
255
+ Policies: props.policies,
256
+ DataType: props.dataType,
257
+ Overwrite: true, // Overwrite existing, no tags
258
+ }),
259
+ );
260
+
261
+ // Update tags separately for existing parameters
262
+ const tags: Tag[] = [
263
+ ...Object.entries(props.tags || {}).map(([Key, Value]) => ({
264
+ Key,
265
+ Value,
266
+ })),
267
+ {
268
+ Key: "alchemy_stage",
269
+ Value: this.stage,
270
+ },
271
+ {
272
+ Key: "alchemy_resource",
273
+ Value: this.id,
274
+ },
275
+ ];
276
+
277
+ await client.send(
278
+ new AddTagsToResourceCommand({
279
+ ResourceType: "Parameter",
280
+ ResourceId: props.name,
281
+ Tags: tags,
282
+ }),
283
+ );
284
+ } else {
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ // Get the updated parameter
290
+ const parameter = await client.send(
291
+ new GetParameterCommand({
292
+ Name: props.name,
293
+ WithDecryption: true,
294
+ }),
295
+ );
296
+
297
+ if (!parameter?.Parameter) {
298
+ throw new Error(`Failed to create or update parameter ${props.name}`);
299
+ }
300
+
301
+ return this({
302
+ ...props,
303
+ arn: parameter.Parameter.ARN!,
304
+ version: parameter.Parameter.Version!,
305
+ lastModifiedDate: parameter.Parameter.LastModifiedDate!,
306
+ name: parameter.Parameter.Name ?? props.name,
307
+ value: props.value,
308
+ type: (parameter.Parameter.Type as any) ?? parameterType,
309
+ } as SSMParameter);
310
+ } catch (error: any) {
311
+ console.error(`Error creating/updating parameter ${props.name}:`, error);
312
+ throw error;
313
+ }
314
+ },
315
+ );
package/src/aws/table.ts CHANGED
@@ -3,15 +3,13 @@ import {
3
3
  DeleteTableCommand,
4
4
  DescribeTableCommand,
5
5
  DynamoDBClient,
6
- InternalServerError,
7
6
  type KeySchemaElement,
8
- ResourceInUseException,
9
7
  ResourceNotFoundException,
10
8
  } from "@aws-sdk/client-dynamodb";
11
9
  import type { Context } from "../context.ts";
12
10
  import { Resource } from "../resource.ts";
13
11
  import { ignore } from "../util/ignore.ts";
14
- import { withExponentialBackoff } from "../util/retry.ts";
12
+ import { retry } from "./retry.ts";
15
13
 
16
14
  /**
17
15
  * Properties for creating or updating a DynamoDB table
@@ -148,20 +146,15 @@ export const Table = Resource(
148
146
  const client = new DynamoDBClient({});
149
147
 
150
148
  if (this.phase === "delete") {
151
- await withExponentialBackoff(
152
- async () => {
153
- await ignore(ResourceNotFoundException.name, () =>
154
- client.send(
155
- new DeleteTableCommand({
156
- TableName: props.tableName,
157
- }),
158
- ),
159
- );
160
- },
161
- isRetryableError,
162
- 10, // Max attempts
163
- 200, // Initial delay in ms
164
- );
149
+ await retry(async () => {
150
+ await ignore(ResourceNotFoundException.name, () =>
151
+ client.send(
152
+ new DeleteTableCommand({
153
+ TableName: props.tableName,
154
+ }),
155
+ ),
156
+ );
157
+ });
165
158
 
166
159
  // Wait for table to be deleted
167
160
  let tableDeleted = false;
@@ -223,59 +216,54 @@ export const Table = Resource(
223
216
  });
224
217
  }
225
218
 
226
- // Attempt to create the table with exponential backoff for ResourceInUseException
227
- await withExponentialBackoff(
228
- async () => {
229
- try {
230
- // First check if table already exists
231
- const describeResponse = await client.send(
232
- new DescribeTableCommand({
233
- TableName: props.tableName,
234
- }),
235
- );
219
+ // Attempt to create the table with retry for retryable errors
220
+ await retry(async () => {
221
+ try {
222
+ // First check if table already exists
223
+ const describeResponse = await client.send(
224
+ new DescribeTableCommand({
225
+ TableName: props.tableName,
226
+ }),
227
+ );
236
228
 
237
- // If table exists and is ACTIVE, no need to create it
238
- if (describeResponse.Table?.TableStatus === "ACTIVE") {
239
- return;
240
- }
229
+ // If table exists and is ACTIVE, no need to create it
230
+ if (describeResponse.Table?.TableStatus === "ACTIVE") {
231
+ return;
232
+ }
241
233
 
242
- // If table exists but not ACTIVE, wait for it in the polling loop below
243
- if (describeResponse.Table) {
244
- return;
245
- }
246
- } catch (error) {
247
- if (error instanceof ResourceNotFoundException) {
248
- // Table doesn't exist, try to create it
249
- await client.send(
250
- new CreateTableCommand({
251
- TableName: props.tableName,
252
- AttributeDefinitions: attributeDefinitions,
253
- KeySchema: keySchema,
254
- BillingMode: props.billingMode || "PAY_PER_REQUEST",
255
- ProvisionedThroughput:
256
- props.billingMode === "PROVISIONED"
257
- ? {
258
- ReadCapacityUnits: props.readCapacity || 5,
259
- WriteCapacityUnits: props.writeCapacity || 5,
260
- }
261
- : undefined,
262
- Tags: props.tags
263
- ? Object.entries(props.tags).map(([Key, Value]) => ({
264
- Key,
265
- Value,
266
- }))
234
+ // If table exists but not ACTIVE, wait for it in the polling loop below
235
+ if (describeResponse.Table) {
236
+ return;
237
+ }
238
+ } catch (error) {
239
+ if (error instanceof ResourceNotFoundException) {
240
+ // Table doesn't exist, try to create it
241
+ await client.send(
242
+ new CreateTableCommand({
243
+ TableName: props.tableName,
244
+ AttributeDefinitions: attributeDefinitions,
245
+ KeySchema: keySchema,
246
+ BillingMode: props.billingMode || "PAY_PER_REQUEST",
247
+ ProvisionedThroughput:
248
+ props.billingMode === "PROVISIONED"
249
+ ? {
250
+ ReadCapacityUnits: props.readCapacity || 5,
251
+ WriteCapacityUnits: props.writeCapacity || 5,
252
+ }
267
253
  : undefined,
268
- }),
269
- );
270
- } else {
271
- throw error;
272
- }
254
+ Tags: props.tags
255
+ ? Object.entries(props.tags).map(([Key, Value]) => ({
256
+ Key,
257
+ Value,
258
+ }))
259
+ : undefined,
260
+ }),
261
+ );
262
+ } else {
263
+ throw error;
273
264
  }
274
- },
275
- isRetryableError,
276
- 10, // Max attempts
277
- 200, // Initial delay in ms
278
- );
265
+ }
266
+ });
279
267
 
280
268
  // Wait for table to be active with timeout
281
269
  let tableActive = false;
@@ -285,10 +273,13 @@ export const Table = Resource(
285
273
 
286
274
  while (!tableActive && retryCount < maxRetries) {
287
275
  try {
288
- const response = await client.send(
289
- new DescribeTableCommand({
290
- TableName: props.tableName,
291
- }),
276
+ const response = await retry(
277
+ async () =>
278
+ await client.send(
279
+ new DescribeTableCommand({
280
+ TableName: props.tableName,
281
+ }),
282
+ ),
292
283
  );
293
284
 
294
285
  tableActive = response.Table?.TableStatus === "ACTIVE";
@@ -323,23 +314,3 @@ export const Table = Resource(
323
314
  });
324
315
  },
325
316
  );
326
-
327
- const retryableErrors = [
328
- "ResourceInUseException",
329
- "ResourceNotFoundException",
330
- "InternalServerError",
331
- "ThrottlingException",
332
- "ProvisionedThroughputExceededException",
333
- "LimitExceededException",
334
- "RequestLimitExceeded",
335
- ];
336
-
337
- function isRetryableError(error: any) {
338
- return (
339
- error instanceof ResourceInUseException ||
340
- error instanceof InternalServerError ||
341
- retryableErrors.includes(error?.name) ||
342
- retryableErrors.includes(error?.code) ||
343
- error?.$metadata?.httpStatusCode === 500
344
- );
345
- }
@@ -5,6 +5,8 @@ import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
5
5
  */
6
6
  export interface GetCloudflareAccountIdOptions extends CloudflareApiOptions {}
7
7
 
8
+ export type AccountId = string;
9
+
8
10
  /**
9
11
  * Retrieves a Cloudflare Account ID for use with other Cloudflare resources.
10
12
  *
@@ -1,6 +1,7 @@
1
1
  import { alchemy } from "../alchemy.ts";
2
2
  import type { Secret } from "../secret.ts";
3
3
  import { withExponentialBackoff } from "../util/retry.ts";
4
+ import { safeFetch } from "../util/safe-fetch.ts";
4
5
  import {
5
6
  getCloudflareAuthHeaders,
6
7
  type CloudflareAuthOptions,
@@ -165,21 +166,35 @@ export class CloudflareApi {
165
166
  delete headers["Content-Type"];
166
167
  }
167
168
 
169
+ let forbidden = false;
170
+
168
171
  // Use withExponentialBackoff for automatic retry on network errors
169
172
  return withExponentialBackoff(
170
173
  async () => {
171
- const response = await fetch(`${this.baseUrl}${path}`, {
174
+ const response = await safeFetch(`${this.baseUrl}${path}`, {
172
175
  ...init,
173
176
  headers,
174
177
  });
175
178
  if (response.status.toString().startsWith("5")) {
176
179
  throw new InternalError("5xx error");
177
180
  }
181
+ if (response.status === 403 && !forbidden) {
182
+ // we occasionally get 403s from Cloudflare tha tare actually transient
183
+ // so, we will retry this at MOST once
184
+ forbidden = true;
185
+ throw new ForbiddenError();
186
+ }
187
+ if (response.status === 429) {
188
+ throw new TooManyRequestsError();
189
+ }
178
190
  return response;
179
191
  },
180
192
  // transient errors should be retried aggressively
181
- (error) => error instanceof InternalError,
182
- 5, // Maximum 5 attempts (1 initial + 4 retries)
193
+ (error) =>
194
+ error instanceof InternalError ||
195
+ error instanceof TooManyRequestsError ||
196
+ error instanceof ForbiddenError,
197
+ 10, // Maximum 10 attempts (1 initial + 9 retries)
183
198
  1000, // Start with 1s delay, will exponentially increase
184
199
  );
185
200
  }
@@ -250,3 +265,11 @@ export class CloudflareApi {
250
265
  }
251
266
 
252
267
  class InternalError extends Error {}
268
+
269
+ class TooManyRequestsError extends Error {
270
+ constructor() {
271
+ super(`Cloudflare Rate Limit Exceeded at ${new Date().toISOString()}`);
272
+ }
273
+ }
274
+
275
+ class ForbiddenError extends Error {}
@@ -21,6 +21,7 @@ import type { VectorizeIndexResource } from "./vectorize-index.ts";
21
21
  import type { WorkerStub } from "./worker-stub.ts";
22
22
  import type { Worker } from "./worker.ts";
23
23
  import type { Workflow } from "./workflow.ts";
24
+ import type { VersionMetadata } from "./version-metadata.ts";
24
25
 
25
26
  export type Bindings = {
26
27
  [bindingName: string]: Binding;
@@ -58,6 +59,7 @@ export type Binding =
58
59
  | WorkerStub
59
60
  | Workflow
60
61
  | BrowserRendering
62
+ | VersionMetadata
61
63
  | Self
62
64
  | Json;
63
65
 
@@ -15,6 +15,7 @@ import type { QueueResource as _Queue } from "./queue.ts";
15
15
  import type { VectorizeIndexResource as _VectorizeIndex } from "./vectorize-index.ts";
16
16
  import type { Worker as _Worker } from "./worker.ts";
17
17
  import type { Workflow as _Workflow } from "./workflow.ts";
18
+ import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts";
18
19
 
19
20
  export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
20
21
  infer O
@@ -61,8 +62,10 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
61
62
  ? Fetcher
62
63
  : T extends _Ai<infer M>
63
64
  ? Ai<M>
64
- : T extends Self
65
- ? Service
66
- : T extends Json<infer T>
67
- ? T
68
- : Service;
65
+ : T extends _VersionMetadata
66
+ ? Fetcher
67
+ : T extends Self
68
+ ? Service
69
+ : T extends Json<infer T>
70
+ ? T
71
+ : Service;
@@ -2,6 +2,7 @@ export * from "./account-api-token.ts";
2
2
  export * from "./account-id.ts";
3
3
  export * from "./ai-gateway.ts";
4
4
  export * from "./ai.ts";
5
+ export * from "./analytics-engine.ts";
5
6
  export * from "./api-error.ts";
6
7
  export * from "./api.ts";
7
8
  export * from "./assets.ts";
@@ -32,6 +33,7 @@ export * from "./route.ts";
32
33
  export * from "./tanstack-start.ts";
33
34
  export * from "./vectorize-index.ts";
34
35
  export * from "./vectorize-metadata-index.ts";
36
+ export * from "./version-metadata.ts";
35
37
  export * from "./vite.ts";
36
38
  export * from "./website.ts";
37
39
  export * from "./worker.ts";
@@ -469,6 +469,8 @@ export async function findQueueByName(
469
469
  }>;
470
470
  };
471
471
 
472
+ console.log(data);
473
+
472
474
  if (!data.success) {
473
475
  const errorMessage = data.errors?.[0]?.message || "Unknown error";
474
476
  throw new Error(`Failed to list queues: ${errorMessage}`);