alchemy 0.22.4 → 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.
- package/lib/aws/bucket.d.ts.map +1 -1
- package/lib/aws/bucket.js +13 -12
- package/lib/aws/bucket.js.map +1 -1
- package/lib/aws/control/client.d.ts.map +1 -1
- package/lib/aws/control/client.js +5 -3
- package/lib/aws/control/client.js.map +1 -1
- package/lib/aws/control/resource.d.ts.map +1 -1
- package/lib/aws/control/resource.js +13 -4
- package/lib/aws/control/resource.js.map +1 -1
- package/lib/aws/control/types.d.ts +85129 -0
- package/lib/aws/function.d.ts.map +1 -1
- package/lib/aws/function.js +51 -42
- package/lib/aws/function.js.map +1 -1
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/policy-attachment.d.ts.map +1 -1
- package/lib/aws/policy-attachment.js +5 -4
- package/lib/aws/policy-attachment.js.map +1 -1
- package/lib/aws/policy.d.ts.map +1 -1
- package/lib/aws/policy.js +21 -20
- package/lib/aws/policy.js.map +1 -1
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +19 -15
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/retry.d.ts +5 -0
- package/lib/aws/retry.d.ts.map +1 -0
- package/lib/aws/retry.js +27 -0
- package/lib/aws/retry.js.map +1 -0
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +31 -30
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +27 -26
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/ssm-parameter.d.ts +150 -0
- package/lib/aws/ssm-parameter.d.ts.map +1 -0
- package/lib/aws/ssm-parameter.js +184 -0
- package/lib/aws/ssm-parameter.js.map +1 -0
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +9 -27
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/account-id.d.ts +1 -0
- package/lib/cloudflare/account-id.d.ts.map +1 -1
- package/lib/cloudflare/account-id.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +22 -2
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/index.d.ts +2 -1
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +2 -1
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +1 -0
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/route.d.ts +7 -0
- package/lib/cloudflare/route.d.ts.map +1 -1
- package/lib/cloudflare/route.js +44 -2
- package/lib/cloudflare/route.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -2
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +2 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +17 -8
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +7 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +2 -0
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +1 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/destroy.js +1 -1
- package/lib/destroy.js.map +1 -1
- package/lib/dns/godaddy.d.ts.map +1 -1
- package/lib/dns/godaddy.js +2 -1
- package/lib/dns/godaddy.js.map +1 -1
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +14 -2
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/github/secret.d.ts.map +1 -1
- package/lib/github/secret.js +1 -0
- package/lib/github/secret.js.map +1 -1
- package/lib/neon/api.d.ts.map +1 -1
- package/lib/neon/api.js +2 -1
- package/lib/neon/api.js.map +1 -1
- package/lib/runtime/shims.d.ts +6 -0
- package/lib/runtime/shims.d.ts.map +1 -0
- package/lib/runtime/shims.js +27 -0
- package/lib/runtime/shims.js.map +1 -0
- package/lib/sentry/api.d.ts.map +1 -1
- package/lib/sentry/api.js +2 -1
- package/lib/sentry/api.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -7
- package/lib/test/bun.js.map +1 -1
- package/lib/test/prune.d.ts +2 -1
- package/lib/test/prune.d.ts.map +1 -1
- package/lib/test/prune.js +29 -5
- package/lib/test/prune.js.map +1 -1
- package/lib/test/vitest.d.ts +92 -0
- package/lib/test/vitest.d.ts.map +1 -0
- package/lib/test/vitest.js +94 -0
- package/lib/test/vitest.js.map +1 -0
- package/lib/upstash/api.d.ts.map +1 -1
- package/lib/upstash/api.js +2 -1
- package/lib/upstash/api.js.map +1 -1
- package/lib/util/retry.js +2 -2
- package/lib/util/retry.js.map +1 -1
- package/lib/util/safe-fetch.d.ts +2 -0
- package/lib/util/safe-fetch.d.ts.map +1 -0
- package/lib/util/safe-fetch.js +48 -0
- package/lib/util/safe-fetch.js.map +1 -0
- package/lib/vercel/api.d.ts.map +1 -1
- package/lib/vercel/api.js +2 -1
- package/lib/vercel/api.js.map +1 -1
- package/lib/web/vitepress/process-front-matter-files.d.ts.map +1 -1
- package/lib/web/vitepress/process-front-matter-files.js +11 -7
- package/lib/web/vitepress/process-front-matter-files.js.map +1 -1
- package/package.json +10 -6
- package/src/alchemy.ts +2 -2
- package/src/aws/bucket.ts +55 -36
- package/src/aws/control/client.ts +13 -13
- package/src/aws/control/resource.ts +19 -3
- package/src/aws/function.ts +233 -178
- package/src/aws/index.ts +1 -0
- package/src/aws/policy-attachment.ts +15 -10
- package/src/aws/policy.ts +75 -54
- package/src/aws/queue.ts +53 -36
- package/src/aws/retry.ts +38 -0
- package/src/aws/role.ts +121 -90
- package/src/aws/ses.ts +101 -73
- package/src/aws/ssm-parameter.ts +315 -0
- package/src/aws/table.ts +61 -90
- package/src/cloudflare/account-id.ts +2 -0
- package/src/cloudflare/api.ts +26 -3
- package/src/cloudflare/index.ts +2 -1
- package/src/cloudflare/queue.ts +2 -0
- package/src/cloudflare/route.ts +86 -2
- package/src/cloudflare/worker-metadata.ts +1 -2
- package/src/cloudflare/worker.ts +21 -9
- package/src/cloudflare/workflow.ts +8 -0
- package/src/cloudflare/wrangler.json.ts +8 -1
- package/src/destroy.ts +1 -1
- package/src/dns/godaddy.ts +3 -1
- package/src/fs/file-system-state-store.ts +16 -2
- package/src/github/secret.ts +2 -0
- package/src/neon/api.ts +2 -1
- package/src/sentry/api.ts +2 -1
- package/src/test/bun.ts +1 -7
- package/src/test/prune.ts +31 -4
- package/src/test/vitest.ts +209 -0
- package/src/upstash/api.ts +2 -1
- package/src/util/retry.ts +3 -3
- package/src/util/safe-fetch.ts +61 -0
- package/src/vercel/api.ts +2 -1
- package/src/web/vitepress/process-front-matter-files.ts +15 -7
|
@@ -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 {
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
227
|
-
await
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
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
|
*
|
package/src/cloudflare/api.ts
CHANGED
|
@@ -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
|
|
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) =>
|
|
182
|
-
|
|
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 {}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -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,10 +33,10 @@ 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";
|
|
38
40
|
export { Workflow } from "./workflow.ts";
|
|
39
|
-
export * from "./version-metadata.ts";
|
|
40
41
|
export * from "./wrangler.json.ts";
|
|
41
42
|
export * from "./zone.ts";
|
package/src/cloudflare/queue.ts
CHANGED