alchemy 0.29.2 → 0.30.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/bin/alchemy.mjs +16086 -0
- package/bin/alchemy.ts +67 -0
- package/bin/create-alchemy.ts +1208 -0
- package/lib/cloudflare/api-error.d.ts.map +1 -1
- package/lib/cloudflare/api-error.js +2 -1
- package/lib/cloudflare/api-error.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +16 -3
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -7
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.d.ts +1 -0
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +16 -5
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/nuxt.d.ts.map +1 -1
- package/lib/cloudflare/nuxt.js +2 -1
- package/lib/cloudflare/nuxt.js.map +1 -1
- package/lib/cloudflare/redwood.d.ts.map +1 -1
- package/lib/cloudflare/redwood.js +1 -0
- package/lib/cloudflare/redwood.js.map +1 -1
- package/lib/cloudflare/secret.d.ts +4 -0
- package/lib/cloudflare/secret.d.ts.map +1 -1
- package/lib/cloudflare/secret.js +46 -7
- package/lib/cloudflare/secret.js.map +1 -1
- package/lib/cloudflare/secrets-store.d.ts +1 -7
- package/lib/cloudflare/secrets-store.d.ts.map +1 -1
- package/lib/cloudflare/secrets-store.js +2 -9
- package/lib/cloudflare/secrets-store.js.map +1 -1
- package/lib/cloudflare/worker-metadata.js +4 -4
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +6 -0
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +1 -0
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.js +6 -10
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/secret.d.ts +5 -0
- package/lib/secret.d.ts.map +1 -1
- package/lib/secret.js +1 -1
- package/lib/secret.js.map +1 -1
- package/lib/util/content-type.d.ts.map +1 -1
- package/lib/util/content-type.js +24 -20
- package/lib/util/content-type.js.map +1 -1
- package/package.json +11 -5
- package/src/cloudflare/api-error.ts +4 -1
- package/src/cloudflare/bindings.ts +17 -2
- package/src/cloudflare/bound.ts +15 -23
- package/src/cloudflare/bundle/bundle-worker.ts +26 -5
- package/src/cloudflare/nuxt.ts +2 -1
- package/src/cloudflare/redwood.ts +1 -0
- package/src/cloudflare/secret.ts +87 -7
- package/src/cloudflare/secrets-store.ts +3 -20
- package/src/cloudflare/worker-metadata.ts +4 -4
- package/src/cloudflare/worker.ts +8 -0
- package/src/cloudflare/wrangler.json.ts +6 -10
- package/src/secret.ts +7 -1
- package/src/util/content-type.ts +24 -20
package/src/cloudflare/secret.ts
CHANGED
|
@@ -74,6 +74,11 @@ export function isSecret(resource: Resource): resource is Secret {
|
|
|
74
74
|
export interface Secret
|
|
75
75
|
extends Resource<"cloudflare::Secret">,
|
|
76
76
|
Omit<_SecretProps, "delete"> {
|
|
77
|
+
/**
|
|
78
|
+
* The binding type for Cloudflare Workers
|
|
79
|
+
*/
|
|
80
|
+
type: "secrets_store_secret";
|
|
81
|
+
|
|
77
82
|
/**
|
|
78
83
|
* The name of the secret
|
|
79
84
|
*/
|
|
@@ -190,6 +195,7 @@ const _Secret = Resource(
|
|
|
190
195
|
await insertSecret(api, props.store.id, name, props.value);
|
|
191
196
|
|
|
192
197
|
return this({
|
|
198
|
+
type: "secrets_store_secret",
|
|
193
199
|
name,
|
|
194
200
|
storeId: props.store.id,
|
|
195
201
|
store: props.store,
|
|
@@ -209,24 +215,98 @@ export async function insertSecret(
|
|
|
209
215
|
secretName: string,
|
|
210
216
|
secretValue: AlchemySecret,
|
|
211
217
|
): Promise<void> {
|
|
212
|
-
|
|
218
|
+
// First try to create the secret
|
|
219
|
+
const createResponse = await api.post(
|
|
213
220
|
`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`,
|
|
214
221
|
[
|
|
215
222
|
{
|
|
216
223
|
name: secretName,
|
|
217
224
|
value: secretValue.unencrypted,
|
|
218
|
-
scopes: [],
|
|
225
|
+
scopes: ["workers"],
|
|
219
226
|
},
|
|
220
227
|
],
|
|
221
228
|
);
|
|
222
229
|
|
|
230
|
+
if (createResponse.ok) {
|
|
231
|
+
return; // Secret created successfully
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If creation failed, check if it's because the secret already exists
|
|
235
|
+
const createErrorData: any = await createResponse.json().catch(() => ({
|
|
236
|
+
errors: [{ message: createResponse.statusText }],
|
|
237
|
+
}));
|
|
238
|
+
|
|
239
|
+
const isAlreadyExists = createErrorData.errors?.some(
|
|
240
|
+
(error: any) =>
|
|
241
|
+
error.code === 10021 ||
|
|
242
|
+
error.message?.includes("secret_name_already_exists"),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (isAlreadyExists) {
|
|
246
|
+
// Secret already exists, find its ID and update it
|
|
247
|
+
const secretId = await getSecretId(api, storeId, secretName);
|
|
248
|
+
if (!secretId) {
|
|
249
|
+
throw new Error(`Secret '${secretName}' not found in store`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const updateResponse = await api.patch(
|
|
253
|
+
`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets/${secretId}`,
|
|
254
|
+
{
|
|
255
|
+
value: secretValue.unencrypted,
|
|
256
|
+
scopes: ["workers"],
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (!updateResponse.ok) {
|
|
261
|
+
const updateErrorData: any = await updateResponse.json().catch(() => ({
|
|
262
|
+
errors: [{ message: updateResponse.statusText }],
|
|
263
|
+
}));
|
|
264
|
+
const updateErrorMessage =
|
|
265
|
+
updateErrorData.errors?.[0]?.message || updateResponse.statusText;
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Error updating secret '${secretName}': ${updateErrorMessage}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Some other error occurred during creation
|
|
272
|
+
const createErrorMessage =
|
|
273
|
+
createErrorData.errors?.[0]?.message || createResponse.statusText;
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Error creating secret '${secretName}': ${createErrorMessage}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get the ID of a secret by its name
|
|
282
|
+
*/
|
|
283
|
+
async function getSecretId(
|
|
284
|
+
api: CloudflareApi,
|
|
285
|
+
storeId: string,
|
|
286
|
+
secretName: string,
|
|
287
|
+
): Promise<string | null> {
|
|
288
|
+
const response = await api.get(
|
|
289
|
+
`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`,
|
|
290
|
+
);
|
|
291
|
+
|
|
223
292
|
if (!response.ok) {
|
|
224
|
-
|
|
225
|
-
errors: [{ message: response.statusText }],
|
|
226
|
-
}));
|
|
227
|
-
const errorMessage = errorData.errors?.[0]?.message || response.statusText;
|
|
228
|
-
throw new Error(`Error creating secret '${secretName}': ${errorMessage}`);
|
|
293
|
+
return null;
|
|
229
294
|
}
|
|
295
|
+
|
|
296
|
+
const data = (await response.json()) as {
|
|
297
|
+
result: Array<{
|
|
298
|
+
id: string;
|
|
299
|
+
name: string;
|
|
300
|
+
created: string;
|
|
301
|
+
modified: string;
|
|
302
|
+
status: string;
|
|
303
|
+
}>;
|
|
304
|
+
success: boolean;
|
|
305
|
+
errors: any[];
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const secret = data.result.find((s) => s.name === secretName);
|
|
309
|
+
return secret?.id || null;
|
|
230
310
|
}
|
|
231
311
|
|
|
232
312
|
/**
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { Resource, ResourceKind } from "../resource.ts";
|
|
3
|
-
import { bind } from "../runtime/bind.ts";
|
|
4
3
|
import { secret, type Secret } from "../secret.ts";
|
|
5
4
|
import { handleApiError } from "./api-error.ts";
|
|
6
5
|
import {
|
|
@@ -8,7 +7,6 @@ import {
|
|
|
8
7
|
type CloudflareApi,
|
|
9
8
|
type CloudflareApiOptions,
|
|
10
9
|
} from "./api.ts";
|
|
11
|
-
import type { Bound } from "./bound.ts";
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Properties for creating or updating a Secrets Store
|
|
@@ -66,11 +64,6 @@ export interface SecretsStore<
|
|
|
66
64
|
S extends Record<string, Secret> | undefined = undefined,
|
|
67
65
|
> extends Resource<"cloudflare::SecretsStore">,
|
|
68
66
|
Omit<SecretsStoreProps<S>, "delete"> {
|
|
69
|
-
/**
|
|
70
|
-
* The binding type for Cloudflare Workers
|
|
71
|
-
*/
|
|
72
|
-
type: "secrets_store";
|
|
73
|
-
|
|
74
67
|
/**
|
|
75
68
|
* The unique identifier of the secrets store
|
|
76
69
|
*/
|
|
@@ -98,10 +91,6 @@ export interface SecretsStore<
|
|
|
98
91
|
modifiedAt: number;
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
export type SecretsStoreWithBinding<
|
|
102
|
-
S extends Record<string, Secret> | undefined = undefined,
|
|
103
|
-
> = SecretsStore<S> & Bound<SecretsStore<S>>;
|
|
104
|
-
|
|
105
94
|
/**
|
|
106
95
|
* A Cloudflare Secrets Store is a secure, centralized location for storing account-level secrets.
|
|
107
96
|
*
|
|
@@ -162,7 +151,7 @@ export async function SecretsStore<
|
|
|
162
151
|
>(
|
|
163
152
|
name: string,
|
|
164
153
|
props: SecretsStoreProps<S> = {} as SecretsStoreProps<S>,
|
|
165
|
-
): Promise<
|
|
154
|
+
): Promise<SecretsStore<S>> {
|
|
166
155
|
// Convert string values to Secret instances
|
|
167
156
|
const normalizedProps: SecretsStoreProps<S> = {
|
|
168
157
|
...props,
|
|
@@ -176,12 +165,7 @@ export async function SecretsStore<
|
|
|
176
165
|
: undefined,
|
|
177
166
|
};
|
|
178
167
|
|
|
179
|
-
|
|
180
|
-
const binding = await bind(store);
|
|
181
|
-
return {
|
|
182
|
-
...store,
|
|
183
|
-
get: binding.get,
|
|
184
|
-
} as SecretsStoreWithBinding<S>;
|
|
168
|
+
return _SecretsStore(name, normalizedProps);
|
|
185
169
|
}
|
|
186
170
|
|
|
187
171
|
const _SecretsStore = Resource("cloudflare::SecretsStore", async function <
|
|
@@ -254,7 +238,6 @@ const _SecretsStore = Resource("cloudflare::SecretsStore", async function <
|
|
|
254
238
|
}
|
|
255
239
|
|
|
256
240
|
return this({
|
|
257
|
-
type: "secrets_store",
|
|
258
241
|
id: storeId,
|
|
259
242
|
name: name,
|
|
260
243
|
secrets: props.secrets as S,
|
|
@@ -345,7 +328,7 @@ export async function insertSecrets<
|
|
|
345
328
|
const bulkPayload = batch.map(([name, secretValue]) => ({
|
|
346
329
|
name,
|
|
347
330
|
value: (secretValue as Secret).unencrypted,
|
|
348
|
-
scopes: [],
|
|
331
|
+
scopes: ["workers"],
|
|
349
332
|
}));
|
|
350
333
|
|
|
351
334
|
const bulkResponse = await api.post(
|
|
@@ -387,12 +387,12 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
387
387
|
name: bindingName,
|
|
388
388
|
bucket_name: binding.name,
|
|
389
389
|
});
|
|
390
|
-
} else if (binding.type === "
|
|
390
|
+
} else if (binding.type === "secrets_store_secret") {
|
|
391
391
|
meta.bindings.push({
|
|
392
|
-
type: "
|
|
392
|
+
type: "secrets_store_secret",
|
|
393
393
|
name: bindingName,
|
|
394
|
-
store_id: binding.
|
|
395
|
-
secret_name:
|
|
394
|
+
store_id: binding.storeId,
|
|
395
|
+
secret_name: binding.name,
|
|
396
396
|
});
|
|
397
397
|
} else if (binding.type === "assets") {
|
|
398
398
|
meta.bindings.push({
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -288,6 +288,13 @@ export interface EntrypointWorkerProps<
|
|
|
288
288
|
*/
|
|
289
289
|
noBundle?: boolean;
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Whether to upload source maps for the worker script.
|
|
293
|
+
*
|
|
294
|
+
* @default false
|
|
295
|
+
*/
|
|
296
|
+
uploadSourceMaps?: boolean;
|
|
297
|
+
|
|
291
298
|
/**
|
|
292
299
|
* Rules for adding additional files to the bundle.
|
|
293
300
|
*
|
|
@@ -847,6 +854,7 @@ export const _Worker = Resource(
|
|
|
847
854
|
props.script ??
|
|
848
855
|
(await bundleWorkerScript({
|
|
849
856
|
...props,
|
|
857
|
+
name: workerName,
|
|
850
858
|
compatibilityDate,
|
|
851
859
|
compatibilityFlags,
|
|
852
860
|
}));
|
|
@@ -596,16 +596,12 @@ function processBindings(
|
|
|
596
596
|
});
|
|
597
597
|
} else if (binding.type === "json") {
|
|
598
598
|
// TODO(sam): anything to do here? not sure wrangler.json supports this
|
|
599
|
-
} else if (binding.type === "
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
secret_name: secretName,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
}
|
|
599
|
+
} else if (binding.type === "secrets_store_secret") {
|
|
600
|
+
secretsStoreSecrets.push({
|
|
601
|
+
binding: bindingName,
|
|
602
|
+
store_id: binding.storeId,
|
|
603
|
+
secret_name: binding.name,
|
|
604
|
+
});
|
|
609
605
|
} else if (binding.type === "dispatch_namespace") {
|
|
610
606
|
dispatchNamespaces.push({
|
|
611
607
|
binding: bindingName,
|
package/src/secret.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { alchemy } from "./alchemy.ts";
|
|
2
2
|
|
|
3
|
+
declare global {
|
|
4
|
+
var __ALCHEMY_SECRETS__: {
|
|
5
|
+
[name: string]: Secret;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
// a global registry of all secrets that we will use when serializing an application
|
|
4
10
|
const globalSecrets: {
|
|
5
11
|
[name: string]: Secret;
|
|
6
|
-
} = {};
|
|
12
|
+
} = (globalThis.__ALCHEMY_SECRETS__ ??= {});
|
|
7
13
|
|
|
8
14
|
let i = 0;
|
|
9
15
|
function nextName() {
|
package/src/util/content-type.ts
CHANGED
|
@@ -2,34 +2,35 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
// Common MIME types
|
|
4
4
|
const mimeTypes: Record<string, string> = {
|
|
5
|
-
".html": "text/html",
|
|
6
|
-
".htm": "text/html",
|
|
7
5
|
".css": "text/css",
|
|
6
|
+
".eot": "application/vnd.ms-fontobject",
|
|
7
|
+
".gif": "image/gif",
|
|
8
|
+
".htm": "text/html",
|
|
9
|
+
".html": "text/html",
|
|
10
|
+
".ico": "image/x-icon",
|
|
11
|
+
".jpeg": "image/jpeg",
|
|
12
|
+
".jpg": "image/jpeg",
|
|
13
|
+
".js.map": "application/source-map",
|
|
8
14
|
".js": "application/javascript",
|
|
9
|
-
".mjs": "application/javascript+module",
|
|
10
15
|
".json": "application/json",
|
|
11
|
-
".xml": "application/xml",
|
|
12
|
-
".txt": "text/plain",
|
|
13
16
|
".md": "text/markdown",
|
|
17
|
+
".mjs": "application/javascript+module",
|
|
18
|
+
".mp3": "audio/mpeg",
|
|
19
|
+
".mp4": "video/mp4",
|
|
20
|
+
".otf": "font/otf",
|
|
21
|
+
".pdf": "application/pdf",
|
|
14
22
|
".png": "image/png",
|
|
15
|
-
".jpg": "image/jpeg",
|
|
16
|
-
".jpeg": "image/jpeg",
|
|
17
|
-
".gif": "image/gif",
|
|
18
|
-
".webp": "image/webp",
|
|
19
23
|
".svg": "image/svg+xml",
|
|
20
|
-
".ico": "image/x-icon",
|
|
21
|
-
".pdf": "application/pdf",
|
|
22
|
-
".zip": "application/zip",
|
|
23
|
-
".woff": "font/woff",
|
|
24
|
-
".woff2": "font/woff2",
|
|
25
24
|
".ttf": "font/ttf",
|
|
26
|
-
".
|
|
27
|
-
".eot": "application/vnd.ms-fontobject",
|
|
28
|
-
".mp4": "video/mp4",
|
|
29
|
-
".webm": "video/webm",
|
|
30
|
-
".mp3": "audio/mpeg",
|
|
31
|
-
".wav": "audio/wav",
|
|
25
|
+
".txt": "text/plain",
|
|
32
26
|
".wasm": "application/wasm",
|
|
27
|
+
".wav": "audio/wav",
|
|
28
|
+
".webm": "video/webm",
|
|
29
|
+
".webp": "image/webp",
|
|
30
|
+
".woff": "font/woff",
|
|
31
|
+
".woff2": "font/woff2",
|
|
32
|
+
".xml": "application/xml",
|
|
33
|
+
".zip": "application/zip",
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -39,5 +40,8 @@ const mimeTypes: Record<string, string> = {
|
|
|
39
40
|
* @returns The content type for the file
|
|
40
41
|
*/
|
|
41
42
|
export function getContentType(filePath: string): string | undefined {
|
|
43
|
+
if (filePath.endsWith(".js.map")) {
|
|
44
|
+
return mimeTypes[".js.map"];
|
|
45
|
+
}
|
|
42
46
|
return mimeTypes[path.extname(filePath).toLowerCase()];
|
|
43
47
|
}
|