alchemy 0.1.13 → 0.1.15
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/account-id.d.ts +5 -0
- package/lib/aws/account-id.d.ts.map +1 -0
- package/lib/aws/account-id.js +10 -0
- package/lib/aws/account-id.js.map +1 -0
- 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/oidc/github-oidc-provider.d.ts +11 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.js +22 -0
- package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
- package/lib/aws/oidc/index.d.ts +3 -0
- package/lib/aws/oidc/index.d.ts.map +1 -0
- package/lib/aws/oidc/index.js +3 -0
- package/lib/aws/oidc/index.js.map +1 -0
- package/lib/aws/oidc/oidc-provider.d.ts +60 -0
- package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/oidc-provider.js +140 -0
- package/lib/aws/oidc/oidc-provider.js.map +1 -0
- package/lib/aws/queue.d.ts +24 -0
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +6 -1
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +49 -1
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +20 -28
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +113 -79
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +7 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +2 -5
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js +1 -6
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +3 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +28 -7
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/static-site.d.ts.map +1 -1
- package/lib/cloudflare/static-site.js +12 -30
- package/lib/cloudflare/static-site.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +6 -3
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +82 -41
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/github/client.d.ts +34 -0
- package/lib/github/client.d.ts.map +1 -0
- package/lib/github/client.js +91 -0
- package/lib/github/client.js.map +1 -0
- package/lib/github/index.d.ts +2 -0
- package/lib/github/index.d.ts.map +1 -0
- package/lib/github/index.js +2 -0
- package/lib/github/index.js.map +1 -0
- package/lib/github/secret.d.ts +46 -0
- package/lib/github/secret.d.ts.map +1 -0
- package/lib/github/secret.js +103 -0
- package/lib/github/secret.js.map +1 -0
- package/lib/utils/retry.d.ts +13 -0
- package/lib/utils/retry.d.ts.map +1 -0
- package/lib/utils/retry.js +32 -0
- package/lib/utils/retry.js.map +1 -0
- package/package.json +10 -3
- package/src/aws/account-id.ts +11 -0
- package/src/aws/index.ts +1 -0
- package/src/aws/oidc/github-oidc-provider.ts +34 -0
- package/src/aws/oidc/index.ts +2 -0
- package/src/aws/oidc/oidc-provider.ts +253 -0
- package/src/aws/queue.ts +23 -1
- package/src/aws/role.ts +69 -0
- package/src/aws/ses.ts +27 -31
- package/src/aws/table.ts +143 -90
- package/src/cloudflare/bindings.ts +8 -2
- package/src/cloudflare/durable-object-namespace.ts +4 -10
- package/src/cloudflare/kv-namespace.ts +45 -15
- package/src/cloudflare/static-site.ts +13 -32
- package/src/cloudflare/worker.ts +117 -62
- package/src/github/client.ts +115 -0
- package/src/github/index.ts +1 -0
- package/src/github/secret.ts +172 -0
- package/src/utils/retry.ts +37 -0
- package/README.md +0 -324
package/src/aws/ses.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AlreadyExistsException,
|
|
3
2
|
CreateConfigurationSetCommand,
|
|
4
3
|
CreateEmailIdentityCommand,
|
|
5
4
|
DeleteConfigurationSetCommand,
|
|
@@ -8,6 +7,11 @@ import {
|
|
|
8
7
|
GetConfigurationSetCommand,
|
|
9
8
|
GetEmailIdentityCommand,
|
|
10
9
|
NotFoundException,
|
|
10
|
+
PutConfigurationSetDeliveryOptionsCommand,
|
|
11
|
+
PutConfigurationSetReputationOptionsCommand,
|
|
12
|
+
PutConfigurationSetSendingOptionsCommand,
|
|
13
|
+
PutConfigurationSetSuppressionOptionsCommand,
|
|
14
|
+
PutConfigurationSetTrackingOptionsCommand,
|
|
11
15
|
PutEmailIdentityDkimAttributesCommand,
|
|
12
16
|
type ReputationOptions,
|
|
13
17
|
SESv2Client,
|
|
@@ -103,7 +107,7 @@ export interface SESOutput extends SESProps {
|
|
|
103
107
|
*/
|
|
104
108
|
export class SES extends Resource(
|
|
105
109
|
"aws::SES",
|
|
106
|
-
async (ctx: Context<SESOutput>, props: SESProps)
|
|
110
|
+
async (ctx: Context<SESOutput>, props: SESProps) => {
|
|
107
111
|
// Create SES client
|
|
108
112
|
const client = new SESv2Client({});
|
|
109
113
|
|
|
@@ -138,10 +142,7 @@ export class SES extends Resource(
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
// Return empty output for delete
|
|
141
|
-
return
|
|
142
|
-
...props,
|
|
143
|
-
id,
|
|
144
|
-
};
|
|
145
|
+
return;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
// Created resources
|
|
@@ -154,66 +155,68 @@ export class SES extends Resource(
|
|
|
154
155
|
if (props.configurationSetName) {
|
|
155
156
|
try {
|
|
156
157
|
// Check if configuration set exists
|
|
157
|
-
let
|
|
158
|
+
let configSetExists = false;
|
|
158
159
|
try {
|
|
159
|
-
|
|
160
|
+
await client.send(
|
|
160
161
|
new GetConfigurationSetCommand({
|
|
161
162
|
ConfigurationSetName: props.configurationSetName,
|
|
162
163
|
}),
|
|
163
164
|
);
|
|
165
|
+
configSetExists = true;
|
|
164
166
|
} catch (error) {
|
|
165
167
|
if (error instanceof NotFoundException) {
|
|
166
|
-
|
|
168
|
+
configSetExists = false;
|
|
167
169
|
} else {
|
|
168
170
|
throw error;
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
if (
|
|
173
|
-
//
|
|
174
|
-
// since there's no unified UpdateConfigurationSetCommand
|
|
175
|
-
|
|
174
|
+
if (configSetExists) {
|
|
175
|
+
// Update existing configuration set using appropriate update commands
|
|
176
176
|
if (props.sendingOptions) {
|
|
177
177
|
await client.send(
|
|
178
|
-
new
|
|
178
|
+
new PutConfigurationSetSendingOptionsCommand({
|
|
179
179
|
ConfigurationSetName: props.configurationSetName,
|
|
180
|
-
|
|
180
|
+
SendingEnabled: props.sendingOptions.SendingEnabled,
|
|
181
181
|
}),
|
|
182
182
|
);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
if (props.reputationOptions) {
|
|
186
186
|
await client.send(
|
|
187
|
-
new
|
|
187
|
+
new PutConfigurationSetReputationOptionsCommand({
|
|
188
188
|
ConfigurationSetName: props.configurationSetName,
|
|
189
|
-
|
|
189
|
+
ReputationMetricsEnabled:
|
|
190
|
+
props.reputationOptions.ReputationMetricsEnabled,
|
|
190
191
|
}),
|
|
191
192
|
);
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
if (props.trackingOptions) {
|
|
195
196
|
await client.send(
|
|
196
|
-
new
|
|
197
|
+
new PutConfigurationSetTrackingOptionsCommand({
|
|
197
198
|
ConfigurationSetName: props.configurationSetName,
|
|
198
|
-
|
|
199
|
+
CustomRedirectDomain:
|
|
200
|
+
props.trackingOptions.CustomRedirectDomain,
|
|
199
201
|
}),
|
|
200
202
|
);
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
if (props.suppressionOptions) {
|
|
204
206
|
await client.send(
|
|
205
|
-
new
|
|
207
|
+
new PutConfigurationSetSuppressionOptionsCommand({
|
|
206
208
|
ConfigurationSetName: props.configurationSetName,
|
|
207
|
-
|
|
209
|
+
SuppressedReasons: props.suppressionOptions.SuppressedReasons,
|
|
208
210
|
}),
|
|
209
211
|
);
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
if (props.deliveryOptions) {
|
|
213
215
|
await client.send(
|
|
214
|
-
new
|
|
216
|
+
new PutConfigurationSetDeliveryOptionsCommand({
|
|
215
217
|
ConfigurationSetName: props.configurationSetName,
|
|
216
|
-
|
|
218
|
+
TlsPolicy: props.deliveryOptions.TlsPolicy,
|
|
219
|
+
SendingPoolName: props.deliveryOptions.SendingPoolName,
|
|
217
220
|
}),
|
|
218
221
|
);
|
|
219
222
|
}
|
|
@@ -241,14 +244,7 @@ export class SES extends Resource(
|
|
|
241
244
|
configurationSetArn = `arn:aws:ses:${process.env.AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:configuration-set/${props.configurationSetName}`;
|
|
242
245
|
}
|
|
243
246
|
} catch (error: any) {
|
|
244
|
-
|
|
245
|
-
// If it already exists but we couldn't get it (permissions issue), continue
|
|
246
|
-
console.warn(
|
|
247
|
-
`Configuration set ${props.configurationSetName} already exists but cannot be retrieved`,
|
|
248
|
-
);
|
|
249
|
-
} else {
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
247
|
+
throw error;
|
|
252
248
|
}
|
|
253
249
|
}
|
|
254
250
|
|
package/src/aws/table.ts
CHANGED
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
DeleteTableCommand,
|
|
4
4
|
DescribeTableCommand,
|
|
5
5
|
DynamoDBClient,
|
|
6
|
+
InternalServerError,
|
|
6
7
|
type KeySchemaElement,
|
|
7
8
|
ResourceInUseException,
|
|
8
9
|
ResourceNotFoundException,
|
|
9
10
|
} from "@aws-sdk/client-dynamodb";
|
|
10
11
|
import { ignore } from "../error";
|
|
11
12
|
import { type Context, Resource } from "../resource";
|
|
13
|
+
import { withExponentialBackoff } from "../utils/retry";
|
|
12
14
|
|
|
13
15
|
export interface TableProps {
|
|
14
16
|
tableName: string;
|
|
@@ -38,21 +40,33 @@ export class Table extends Resource(
|
|
|
38
40
|
async (
|
|
39
41
|
ctx: Context<TableOutput>,
|
|
40
42
|
props: TableProps,
|
|
41
|
-
): Promise<TableOutput> => {
|
|
43
|
+
): Promise<TableOutput | void> => {
|
|
42
44
|
const client = new DynamoDBClient({});
|
|
43
45
|
|
|
44
46
|
if (ctx.event === "delete") {
|
|
45
|
-
await
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
await withExponentialBackoff(
|
|
48
|
+
async () => {
|
|
49
|
+
await ignore(ResourceNotFoundException.name, () =>
|
|
50
|
+
client.send(
|
|
51
|
+
new DeleteTableCommand({
|
|
52
|
+
TableName: props.tableName,
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
(error) =>
|
|
58
|
+
error instanceof ResourceInUseException ||
|
|
59
|
+
error instanceof InternalServerError,
|
|
60
|
+
10, // Max attempts
|
|
61
|
+
200, // Initial delay in ms
|
|
51
62
|
);
|
|
52
63
|
|
|
53
64
|
// Wait for table to be deleted
|
|
54
65
|
let tableDeleted = false;
|
|
55
|
-
|
|
66
|
+
let retryCount = 0;
|
|
67
|
+
const maxRetries = 60; // Wait up to 60 seconds
|
|
68
|
+
|
|
69
|
+
while (!tableDeleted && retryCount < maxRetries) {
|
|
56
70
|
try {
|
|
57
71
|
await client.send(
|
|
58
72
|
new DescribeTableCommand({
|
|
@@ -60,7 +74,10 @@ export class Table extends Resource(
|
|
|
60
74
|
}),
|
|
61
75
|
);
|
|
62
76
|
// If we get here, table still exists
|
|
63
|
-
|
|
77
|
+
retryCount++;
|
|
78
|
+
// Increasing delay for each retry with some jitter
|
|
79
|
+
const delay = Math.min(1000 * (1 + 0.1 * Math.random()), 5000);
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
64
81
|
} catch (error) {
|
|
65
82
|
if (error instanceof ResourceNotFoundException) {
|
|
66
83
|
tableDeleted = true;
|
|
@@ -70,103 +87,139 @@ export class Table extends Resource(
|
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
if (!tableDeleted) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Timed out waiting for table ${props.tableName} to be deleted`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return;
|
|
79
97
|
} else {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const keySchema: KeySchemaElement[] = [
|
|
89
|
-
{
|
|
90
|
-
AttributeName: props.partitionKey.name,
|
|
91
|
-
KeyType: "HASH",
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
if (props.sortKey) {
|
|
96
|
-
attributeDefinitions.push({
|
|
97
|
-
AttributeName: props.sortKey.name,
|
|
98
|
-
AttributeType: props.sortKey.type,
|
|
99
|
-
});
|
|
100
|
-
keySchema.push({
|
|
101
|
-
AttributeName: props.sortKey.name,
|
|
102
|
-
KeyType: "RANGE",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
98
|
+
// Setup for table creation
|
|
99
|
+
const attributeDefinitions = [
|
|
100
|
+
{
|
|
101
|
+
AttributeName: props.partitionKey.name,
|
|
102
|
+
AttributeType: props.partitionKey.type,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
106
|
+
const keySchema: KeySchemaElement[] = [
|
|
107
|
+
{
|
|
108
|
+
AttributeName: props.partitionKey.name,
|
|
109
|
+
KeyType: "HASH",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
if (props.sortKey) {
|
|
114
|
+
attributeDefinitions.push({
|
|
115
|
+
AttributeName: props.sortKey.name,
|
|
116
|
+
AttributeType: props.sortKey.type,
|
|
117
|
+
});
|
|
118
|
+
keySchema.push({
|
|
119
|
+
AttributeName: props.sortKey.name,
|
|
120
|
+
KeyType: "RANGE",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Attempt to create the table with exponential backoff for ResourceInUseException
|
|
125
|
+
await withExponentialBackoff(
|
|
126
|
+
async () => {
|
|
127
|
+
try {
|
|
128
|
+
// First check if table already exists
|
|
129
|
+
const describeResponse = await client.send(
|
|
130
|
+
new DescribeTableCommand({
|
|
131
|
+
TableName: props.tableName,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// If table exists and is ACTIVE, no need to create it
|
|
136
|
+
if (describeResponse.Table?.TableStatus === "ACTIVE") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// If table exists but not ACTIVE, wait for it in the polling loop below
|
|
141
|
+
if (describeResponse.Table) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error instanceof ResourceNotFoundException) {
|
|
146
|
+
// Table doesn't exist, try to create it
|
|
147
|
+
await client.send(
|
|
148
|
+
new CreateTableCommand({
|
|
149
|
+
TableName: props.tableName,
|
|
150
|
+
AttributeDefinitions: attributeDefinitions,
|
|
151
|
+
KeySchema: keySchema,
|
|
152
|
+
BillingMode: props.billingMode || "PAY_PER_REQUEST",
|
|
153
|
+
ProvisionedThroughput:
|
|
154
|
+
props.billingMode === "PROVISIONED"
|
|
155
|
+
? {
|
|
156
|
+
ReadCapacityUnits: props.readCapacity || 5,
|
|
157
|
+
WriteCapacityUnits: props.writeCapacity || 5,
|
|
158
|
+
}
|
|
159
|
+
: undefined,
|
|
160
|
+
Tags: props.tags
|
|
161
|
+
? Object.entries(props.tags).map(([Key, Value]) => ({
|
|
162
|
+
Key,
|
|
163
|
+
Value,
|
|
164
|
+
}))
|
|
165
|
+
: undefined,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
(error) => error instanceof ResourceInUseException,
|
|
174
|
+
10, // Max attempts
|
|
175
|
+
200, // Initial delay in ms
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Wait for table to be active with timeout
|
|
179
|
+
let tableActive = false;
|
|
180
|
+
let tableDescription;
|
|
181
|
+
let retryCount = 0;
|
|
182
|
+
const maxRetries = 60; // Wait up to 60 seconds
|
|
127
183
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let tableDescription;
|
|
131
|
-
while (!tableActive) {
|
|
184
|
+
while (!tableActive && retryCount < maxRetries) {
|
|
185
|
+
try {
|
|
132
186
|
const response = await client.send(
|
|
133
187
|
new DescribeTableCommand({
|
|
134
188
|
TableName: props.tableName,
|
|
135
189
|
}),
|
|
136
190
|
);
|
|
191
|
+
|
|
137
192
|
tableActive = response.Table?.TableStatus === "ACTIVE";
|
|
138
193
|
if (tableActive) {
|
|
139
194
|
tableDescription = response.Table;
|
|
140
195
|
} else {
|
|
141
|
-
|
|
196
|
+
retryCount++;
|
|
197
|
+
// Increasing delay for each retry with some jitter
|
|
198
|
+
const delay = Math.min(1000 * (1 + 0.1 * Math.random()), 5000);
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
retryCount++;
|
|
203
|
+
if (!(error instanceof ResourceNotFoundException)) {
|
|
204
|
+
throw error;
|
|
142
205
|
}
|
|
206
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
143
207
|
}
|
|
208
|
+
}
|
|
144
209
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
streamArn: tableDescription!.LatestStreamArn,
|
|
150
|
-
tableId: tableDescription!.TableId!,
|
|
151
|
-
};
|
|
152
|
-
} catch (error) {
|
|
153
|
-
if (error instanceof ResourceInUseException) {
|
|
154
|
-
// Table already exists, get its details
|
|
155
|
-
const response = await client.send(
|
|
156
|
-
new DescribeTableCommand({
|
|
157
|
-
TableName: props.tableName,
|
|
158
|
-
}),
|
|
159
|
-
);
|
|
160
|
-
return {
|
|
161
|
-
...props,
|
|
162
|
-
id: props.tableName,
|
|
163
|
-
arn: response.Table!.TableArn!,
|
|
164
|
-
streamArn: response.Table!.LatestStreamArn,
|
|
165
|
-
tableId: response.Table!.TableId!,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
throw error;
|
|
210
|
+
if (!tableActive) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Timed out waiting for table ${props.tableName} to become active`,
|
|
213
|
+
);
|
|
169
214
|
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
...props,
|
|
218
|
+
id: props.tableName,
|
|
219
|
+
arn: tableDescription!.TableArn!,
|
|
220
|
+
streamArn: tableDescription!.LatestStreamArn,
|
|
221
|
+
tableId: tableDescription!.TableId!,
|
|
222
|
+
};
|
|
170
223
|
}
|
|
171
224
|
},
|
|
172
225
|
) {}
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/
|
|
5
5
|
*/
|
|
6
6
|
import type { DurableObjectNamespace } from "./durable-object-namespace";
|
|
7
|
+
import type { KVNamespace } from "./kv-namespace";
|
|
8
|
+
import type { Worker } from "./worker";
|
|
9
|
+
|
|
10
|
+
export type Bindings = {
|
|
11
|
+
[bindingName: string]: Binding;
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
15
|
+
* L2 Binding Resources.
|
|
10
16
|
*/
|
|
11
|
-
export type
|
|
17
|
+
export type Binding = DurableObjectNamespace | KVNamespace | Worker | string;
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
20
|
* Union type for all Worker binding types (API spec)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Resolved } from "../output";
|
|
2
2
|
|
|
3
3
|
export interface DurableObjectNamespaceInput {
|
|
4
|
-
bindingName: string;
|
|
5
4
|
className: string;
|
|
6
5
|
scriptName?: string | undefined;
|
|
7
6
|
environment?: string | undefined;
|
|
@@ -10,19 +9,16 @@ export interface DurableObjectNamespaceInput {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export function isDurableObjectNamespace(
|
|
13
|
-
binding:
|
|
14
|
-
): binding is DurableObjectNamespace {
|
|
12
|
+
binding: any,
|
|
13
|
+
): binding is Resolved<DurableObjectNamespace> {
|
|
15
14
|
return (
|
|
16
|
-
binding.type === "durable_object_namespace"
|
|
17
|
-
typeof (binding as any).bindingName === "string"
|
|
15
|
+
typeof binding === "object" && binding.type === "durable_object_namespace"
|
|
18
16
|
);
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export class DurableObjectNamespace implements DurableObjectNamespaceInput {
|
|
22
20
|
public readonly type = "durable_object_namespace" as const;
|
|
23
21
|
// alias for bindingName to be consistent with other bindings
|
|
24
|
-
public readonly name: string;
|
|
25
|
-
public readonly bindingName: string;
|
|
26
22
|
public readonly className: string;
|
|
27
23
|
public readonly scriptName?: string | undefined;
|
|
28
24
|
public readonly environment?: string | undefined;
|
|
@@ -33,8 +29,6 @@ export class DurableObjectNamespace implements DurableObjectNamespaceInput {
|
|
|
33
29
|
public readonly id: string,
|
|
34
30
|
input: DurableObjectNamespaceInput,
|
|
35
31
|
) {
|
|
36
|
-
this.bindingName = input.bindingName;
|
|
37
|
-
this.name = this.bindingName;
|
|
38
32
|
this.className = input.className;
|
|
39
33
|
this.scriptName = input.scriptName;
|
|
40
34
|
this.environment = input.environment;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { Resolved } from "../output";
|
|
1
2
|
import { type Context, Resource } from "../resource";
|
|
3
|
+
import { withExponentialBackoff } from "../utils/retry";
|
|
2
4
|
import { createCloudflareApi } from "./api";
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -51,6 +53,7 @@ export interface KVPair {
|
|
|
51
53
|
* Output returned after KV Namespace creation/update
|
|
52
54
|
*/
|
|
53
55
|
export interface KVNamespaceOutput extends KVNamespaceProps {
|
|
56
|
+
type: "kv_namespace";
|
|
54
57
|
/**
|
|
55
58
|
* The ID of the namespace
|
|
56
59
|
*/
|
|
@@ -67,6 +70,14 @@ export interface KVNamespaceOutput extends KVNamespaceProps {
|
|
|
67
70
|
modifiedAt: number;
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
export function isKVNamespace(
|
|
74
|
+
resource: any,
|
|
75
|
+
): resource is Resolved<KVNamespace> {
|
|
76
|
+
return (
|
|
77
|
+
resource && typeof resource === "object" && resource.type === "kv_namespace"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
export class KVNamespace extends Resource(
|
|
71
82
|
"cloudflare::KVNamespace",
|
|
72
83
|
async (ctx: Context<KVNamespaceOutput>, props: KVNamespaceProps) => {
|
|
@@ -76,7 +87,6 @@ export class KVNamespace extends Resource(
|
|
|
76
87
|
if (ctx.event === "delete") {
|
|
77
88
|
// For delete operations, we need to check if the namespace ID exists in the output
|
|
78
89
|
const namespaceId = ctx.output?.id;
|
|
79
|
-
|
|
80
90
|
if (namespaceId) {
|
|
81
91
|
// Delete KV namespace
|
|
82
92
|
const deleteResponse = await api.delete(
|
|
@@ -87,9 +97,8 @@ export class KVNamespace extends Resource(
|
|
|
87
97
|
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
88
98
|
errors: [{ message: deleteResponse.statusText }],
|
|
89
99
|
}));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
errorData.errors?.[0]?.message || deleteResponse.statusText,
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Error deleting KV namespace '${props.title}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
93
102
|
);
|
|
94
103
|
}
|
|
95
104
|
}
|
|
@@ -162,18 +171,38 @@ export class KVNamespace extends Resource(
|
|
|
162
171
|
return item;
|
|
163
172
|
});
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
try {
|
|
175
|
+
await withExponentialBackoff(
|
|
176
|
+
async () => {
|
|
177
|
+
const bulkResponse = await api.put(
|
|
178
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
179
|
+
bulkPayload,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (!bulkResponse.ok) {
|
|
183
|
+
const errorData: any = await bulkResponse
|
|
184
|
+
.json()
|
|
185
|
+
.catch(() => ({
|
|
186
|
+
errors: [{ message: bulkResponse.statusText }],
|
|
187
|
+
}));
|
|
188
|
+
const errorMessage =
|
|
189
|
+
errorData.errors?.[0]?.message || bulkResponse.statusText;
|
|
190
|
+
|
|
191
|
+
// Throw error to trigger retry
|
|
192
|
+
throw new Error(`Error writing KV batch: ${errorMessage}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return bulkResponse;
|
|
196
|
+
},
|
|
197
|
+
(error) => {
|
|
198
|
+
// Retry on "namespace not found" errors as they're likely propagation delays
|
|
199
|
+
return error.message?.includes("not found");
|
|
200
|
+
},
|
|
201
|
+
5, // 5 retry attempts
|
|
202
|
+
1000, // Start with 1 second delay
|
|
176
203
|
);
|
|
204
|
+
} catch (error: any) {
|
|
205
|
+
console.warn(error.message);
|
|
177
206
|
}
|
|
178
207
|
}
|
|
179
208
|
}
|
|
@@ -182,6 +211,7 @@ export class KVNamespace extends Resource(
|
|
|
182
211
|
|
|
183
212
|
// Construct the output
|
|
184
213
|
const output: KVNamespaceOutput = {
|
|
214
|
+
type: "kv_namespace",
|
|
185
215
|
id: namespaceId,
|
|
186
216
|
title: props.title,
|
|
187
217
|
values: props.values,
|
|
@@ -7,7 +7,7 @@ import type { BundleProps } from "../esbuild";
|
|
|
7
7
|
import type { Input } from "../input";
|
|
8
8
|
import { type Context, Resource } from "../resource";
|
|
9
9
|
import { createCloudflareApi } from "./api";
|
|
10
|
-
import type {
|
|
10
|
+
import type { Bindings } from "./bindings";
|
|
11
11
|
import { generateAssetManifest } from "./generate-asset-manifest";
|
|
12
12
|
import { KVNamespace } from "./kv-namespace";
|
|
13
13
|
import { uploadAssetManifest } from "./upload-asset-manifest";
|
|
@@ -276,12 +276,12 @@ export class StaticSite extends Resource(
|
|
|
276
276
|
const api = await createCloudflareApi();
|
|
277
277
|
|
|
278
278
|
// Step 1: Create or get the KV namespace for assets
|
|
279
|
-
const
|
|
279
|
+
const kv = new KVNamespace("assets", {
|
|
280
280
|
title: `${siteName}-assets`,
|
|
281
281
|
});
|
|
282
282
|
|
|
283
283
|
const [{ id: kvNamespaceId }, assetManifest] = await Promise.all([
|
|
284
|
-
apply(
|
|
284
|
+
apply(kv, {
|
|
285
285
|
quiet: ctx.quiet,
|
|
286
286
|
}),
|
|
287
287
|
generateAssetManifest(props.dir),
|
|
@@ -291,39 +291,20 @@ export class StaticSite extends Resource(
|
|
|
291
291
|
await uploadAssetManifest(api, kvNamespaceId, assetManifest);
|
|
292
292
|
|
|
293
293
|
// Prepare the bindings for the worker
|
|
294
|
-
const bindings: Input<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
{
|
|
301
|
-
name: "INDEX_PAGE",
|
|
302
|
-
type: "plain_text",
|
|
303
|
-
text: indexPage,
|
|
304
|
-
},
|
|
305
|
-
];
|
|
306
|
-
|
|
307
|
-
// Add error page binding if specified
|
|
308
|
-
if (props.errorPage) {
|
|
309
|
-
bindings.push({
|
|
310
|
-
name: "ERROR_PAGE",
|
|
311
|
-
type: "plain_text",
|
|
312
|
-
text: props.errorPage,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
294
|
+
const bindings: Input<Bindings> = {
|
|
295
|
+
ASSETS: kv,
|
|
296
|
+
INDEX_PAGE: indexPage,
|
|
297
|
+
// Add error page binding if specified
|
|
298
|
+
...(props.errorPage ? { ERROR_PAGE: props.errorPage } : {}),
|
|
299
|
+
};
|
|
315
300
|
|
|
316
301
|
const routes: Record<string, string> = {};
|
|
317
302
|
// Create backend worker if configured
|
|
318
303
|
if (props.routes) {
|
|
319
|
-
for (const [path,
|
|
304
|
+
for (const [path, worker] of Object.entries(props.routes)) {
|
|
320
305
|
// @ts-ignore - TODO: need to use Resolved<Worker> to get the string ...
|
|
321
|
-
routes[path] =
|
|
322
|
-
bindings.
|
|
323
|
-
type: "service",
|
|
324
|
-
name: `ROUTE_${entrypoint.id}`,
|
|
325
|
-
service: entrypoint.id,
|
|
326
|
-
});
|
|
306
|
+
routes[path] = worker.id;
|
|
307
|
+
bindings[`ROUTE_${worker.id}`] = worker;
|
|
327
308
|
}
|
|
328
309
|
}
|
|
329
310
|
|
|
@@ -366,7 +347,7 @@ export class StaticSite extends Resource(
|
|
|
366
347
|
},
|
|
367
348
|
},
|
|
368
349
|
// place a dependency on the namespace
|
|
369
|
-
|
|
350
|
+
kv.id,
|
|
370
351
|
);
|
|
371
352
|
|
|
372
353
|
// Get current timestamp
|