alchemy 0.29.3 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +24 -4
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -2
- 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/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.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/queue.d.ts +24 -0
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +24 -0
- package/lib/cloudflare/queue.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-key.d.ts +120 -0
- package/lib/cloudflare/secret-key.d.ts.map +1 -0
- package/lib/cloudflare/secret-key.js +92 -0
- package/lib/cloudflare/secret-key.js.map +1 -0
- package/lib/cloudflare/secret.d.ts +9 -5
- package/lib/cloudflare/secret.d.ts.map +1 -1
- package/lib/cloudflare/secret.js +13 -5
- package/lib/cloudflare/secret.js.map +1 -1
- package/lib/cloudflare/secrets-store.d.ts +3 -0
- package/lib/cloudflare/secrets-store.d.ts.map +1 -1
- package/lib/cloudflare/secrets-store.js +3 -3
- package/lib/cloudflare/secrets-store.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +11 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +36 -2
- 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 +3 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/secret.d.ts +5 -5
- package/lib/secret.d.ts.map +1 -1
- package/lib/secret.js.map +1 -1
- package/lib/serde.d.ts +5 -1
- package/lib/serde.d.ts.map +1 -1
- package/lib/serde.js +9 -2
- package/lib/serde.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 +34 -2
- package/src/cloudflare/bound.ts +36 -33
- package/src/cloudflare/bundle/bundle-worker.ts +26 -5
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/nuxt.ts +2 -1
- package/src/cloudflare/queue.ts +24 -0
- package/src/cloudflare/redwood.ts +1 -0
- package/src/cloudflare/secret-key.ts +143 -0
- package/src/cloudflare/secret.ts +55 -41
- package/src/cloudflare/secrets-store.ts +4 -5
- package/src/cloudflare/worker-metadata.ts +10 -0
- package/src/cloudflare/worker.ts +38 -2
- package/src/cloudflare/wrangler.json.ts +2 -0
- package/src/secret.ts +7 -7
- package/src/serde.ts +40 -20
- package/src/util/content-type.ts +24 -20
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecretKey binding for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* A data class that represents a secret key binding with cryptographic properties.
|
|
5
|
+
* Keys are securely stored and exposed as CryptoKey objects for use with the Web Crypto API.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Create an RSA signing key from a JWK format:
|
|
9
|
+
* const keyPair = await crypto.subtle.generateKey(
|
|
10
|
+
* {
|
|
11
|
+
* name: "RSA-PSS",
|
|
12
|
+
* modulusLength: 2048,
|
|
13
|
+
* publicExponent: new Uint8Array([1, 0, 1]),
|
|
14
|
+
* hash: "SHA-256",
|
|
15
|
+
* },
|
|
16
|
+
* true,
|
|
17
|
+
* ["sign", "verify"]
|
|
18
|
+
* );
|
|
19
|
+
*
|
|
20
|
+
* const privateKeyJwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
21
|
+
*
|
|
22
|
+
* const signingKey = new SecretKey({
|
|
23
|
+
* algorithm: { name: "RSA-PSS", hash: "SHA-256" },
|
|
24
|
+
* format: "jwk",
|
|
25
|
+
* usages: ["sign"],
|
|
26
|
+
* key_jwk: secret(privateKeyJwk),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Create an AES encryption key from raw bytes:
|
|
31
|
+
* const aesKey = await crypto.subtle.generateKey(
|
|
32
|
+
* { name: "AES-GCM", length: 256 },
|
|
33
|
+
* true,
|
|
34
|
+
* ["encrypt", "decrypt"]
|
|
35
|
+
* );
|
|
36
|
+
*
|
|
37
|
+
* const keyData = await crypto.subtle.exportKey("raw", aesKey);
|
|
38
|
+
* const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(keyData)));
|
|
39
|
+
*
|
|
40
|
+
* const encryptionKey = new SecretKey({
|
|
41
|
+
* algorithm: { name: "AES-GCM" },
|
|
42
|
+
* format: "raw",
|
|
43
|
+
* usages: ["encrypt", "decrypt"],
|
|
44
|
+
* key_base64: secret(keyBase64),
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Create an ECDSA key from PKCS#8 format:
|
|
49
|
+
* const ecdsaKey = new SecretKey({
|
|
50
|
+
* algorithm: { name: "ECDSA", namedCurve: "P-256" },
|
|
51
|
+
* format: "pkcs8",
|
|
52
|
+
* usages: ["sign"],
|
|
53
|
+
* key_base64: secret("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg..."),
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Create an HMAC key for message authentication:
|
|
58
|
+
* const hmacKey = await crypto.subtle.generateKey(
|
|
59
|
+
* { name: "HMAC", hash: "SHA-256" },
|
|
60
|
+
* true,
|
|
61
|
+
* ["sign", "verify"]
|
|
62
|
+
* );
|
|
63
|
+
*
|
|
64
|
+
* const keyData = await crypto.subtle.exportKey("raw", hmacKey);
|
|
65
|
+
* const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(keyData)));
|
|
66
|
+
*
|
|
67
|
+
* const macKey = new SecretKey({
|
|
68
|
+
* algorithm: { name: "HMAC", hash: "SHA-256" },
|
|
69
|
+
* format: "raw",
|
|
70
|
+
* usages: ["sign", "verify"],
|
|
71
|
+
* key_base64: secret(keyBase64),
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* @see https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/
|
|
75
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#keyusages
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
import type { Secret } from "../secret.ts";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Data format of the key
|
|
82
|
+
*/
|
|
83
|
+
export type SecretKeyFormat = "raw" | "pkcs8" | "spki" | "jwk";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Allowed operations with the key
|
|
87
|
+
*/
|
|
88
|
+
export type SecretKeyUsage =
|
|
89
|
+
| "encrypt"
|
|
90
|
+
| "decrypt"
|
|
91
|
+
| "sign"
|
|
92
|
+
| "verify"
|
|
93
|
+
| "deriveKey"
|
|
94
|
+
| "deriveBits"
|
|
95
|
+
| "wrapKey"
|
|
96
|
+
| "unwrapKey";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* SecretKey binding properties
|
|
100
|
+
*/
|
|
101
|
+
export interface SecretKeyProps {
|
|
102
|
+
/**
|
|
103
|
+
* Algorithm-specific key parameters
|
|
104
|
+
*/
|
|
105
|
+
algorithm: unknown;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Data format of the key
|
|
109
|
+
*/
|
|
110
|
+
format: SecretKeyFormat;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Allowed operations with the key
|
|
114
|
+
*/
|
|
115
|
+
usages: SecretKeyUsage[];
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Base64-encoded key data. Required if format is "raw", "pkcs8", or "spki"
|
|
119
|
+
*/
|
|
120
|
+
key_base64?: Secret;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Key data in JSON Web Key format. Required if format is "jwk"
|
|
124
|
+
*/
|
|
125
|
+
key_jwk?: Secret<JsonWebKey>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export class SecretKey {
|
|
129
|
+
readonly type = "secret_key";
|
|
130
|
+
readonly algorithm: unknown;
|
|
131
|
+
readonly format: SecretKeyFormat;
|
|
132
|
+
readonly usages: SecretKeyUsage[];
|
|
133
|
+
readonly key_base64?: Secret;
|
|
134
|
+
readonly key_jwk?: Secret<JsonWebKey>;
|
|
135
|
+
|
|
136
|
+
constructor(props: SecretKeyProps) {
|
|
137
|
+
this.algorithm = props.algorithm;
|
|
138
|
+
this.format = props.format;
|
|
139
|
+
this.usages = props.usages;
|
|
140
|
+
this.key_base64 = props.key_base64;
|
|
141
|
+
this.key_jwk = props.key_jwk;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/cloudflare/secret.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type CloudflareApi,
|
|
11
11
|
type CloudflareApiOptions,
|
|
12
12
|
} from "./api.ts";
|
|
13
|
-
import
|
|
13
|
+
import { findSecretsStoreByName, SecretsStore } from "./secrets-store.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Properties for creating or updating a Secret in a Secrets Store (internal interface)
|
|
@@ -18,8 +18,10 @@ import type { SecretsStore } from "./secrets-store.ts";
|
|
|
18
18
|
interface _SecretProps extends CloudflareApiOptions {
|
|
19
19
|
/**
|
|
20
20
|
* The secrets store to add this secret to
|
|
21
|
+
*
|
|
22
|
+
* @default - the default-secret-store
|
|
21
23
|
*/
|
|
22
|
-
store
|
|
24
|
+
store?: SecretsStore<any>;
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* The secret value to store (must be an AlchemySecret for security)
|
|
@@ -41,8 +43,10 @@ interface _SecretProps extends CloudflareApiOptions {
|
|
|
41
43
|
export interface SecretProps extends CloudflareApiOptions {
|
|
42
44
|
/**
|
|
43
45
|
* The secrets store to add this secret to
|
|
46
|
+
*
|
|
47
|
+
* @default - the default-secret-store
|
|
44
48
|
*/
|
|
45
|
-
store
|
|
49
|
+
store?: SecretsStore<any>;
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* The secret value to store
|
|
@@ -71,39 +75,38 @@ export function isSecret(resource: Resource): resource is Secret {
|
|
|
71
75
|
return resource[ResourceKind] === "cloudflare::Secret";
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
78
|
+
export type Secret = Resource<"cloudflare::Secret"> &
|
|
79
|
+
Omit<_SecretProps, "delete"> & {
|
|
80
|
+
/**
|
|
81
|
+
* The binding type for Cloudflare Workers
|
|
82
|
+
*/
|
|
83
|
+
type: "secrets_store_secret";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The name of the secret
|
|
87
|
+
*/
|
|
88
|
+
name: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The unique identifier of the secrets store this secret belongs to
|
|
92
|
+
*/
|
|
93
|
+
storeId: string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The secret value (as an alchemy Secret instance)
|
|
97
|
+
*/
|
|
98
|
+
value: AlchemySecret;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Timestamp when the secret was created
|
|
102
|
+
*/
|
|
103
|
+
createdAt: number;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Timestamp when the secret was last modified
|
|
107
|
+
*/
|
|
108
|
+
modifiedAt: number;
|
|
109
|
+
};
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* A Cloudflare Secret represents an individual secret stored in a Secrets Store.
|
|
@@ -179,9 +182,20 @@ const _Secret = Resource(
|
|
|
179
182
|
): Promise<Secret> {
|
|
180
183
|
const api = await createCloudflareApi(props);
|
|
181
184
|
|
|
185
|
+
const storeId =
|
|
186
|
+
props.store?.id ??
|
|
187
|
+
(await findSecretsStoreByName(api, SecretsStore.Default))?.id ??
|
|
188
|
+
(
|
|
189
|
+
await SecretsStore("default-store", {
|
|
190
|
+
name: SecretsStore.Default,
|
|
191
|
+
adopt: true,
|
|
192
|
+
delete: false,
|
|
193
|
+
})
|
|
194
|
+
)?.id!;
|
|
195
|
+
|
|
182
196
|
if (this.phase === "delete") {
|
|
183
197
|
if (props.delete !== false) {
|
|
184
|
-
await deleteSecret(api,
|
|
198
|
+
await deleteSecret(api, storeId, name);
|
|
185
199
|
}
|
|
186
200
|
return this.destroy();
|
|
187
201
|
}
|
|
@@ -192,12 +206,12 @@ const _Secret = Resource(
|
|
|
192
206
|
: Date.now();
|
|
193
207
|
|
|
194
208
|
// Insert or update the secret
|
|
195
|
-
await insertSecret(api,
|
|
209
|
+
await insertSecret(api, storeId, name, props.value);
|
|
196
210
|
|
|
197
211
|
return this({
|
|
198
212
|
type: "secrets_store_secret",
|
|
199
213
|
name,
|
|
200
|
-
storeId
|
|
214
|
+
storeId,
|
|
201
215
|
store: props.store,
|
|
202
216
|
value: props.value,
|
|
203
217
|
createdAt,
|
|
@@ -317,10 +331,10 @@ export async function deleteSecret(
|
|
|
317
331
|
storeId: string,
|
|
318
332
|
secretName: string,
|
|
319
333
|
): Promise<void> {
|
|
334
|
+
const secretId = await getSecretId(api, storeId, secretName);
|
|
320
335
|
const response = await api.delete(
|
|
321
|
-
`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`,
|
|
336
|
+
`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets/${secretId}`,
|
|
322
337
|
{
|
|
323
|
-
body: JSON.stringify([secretName]),
|
|
324
338
|
headers: {
|
|
325
339
|
"Content-Type": "application/json",
|
|
326
340
|
},
|
|
@@ -168,6 +168,10 @@ export async function SecretsStore<
|
|
|
168
168
|
return _SecretsStore(name, normalizedProps);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
export namespace SecretsStore {
|
|
172
|
+
export const Default = "default_secrets_store";
|
|
173
|
+
}
|
|
174
|
+
|
|
171
175
|
const _SecretsStore = Resource("cloudflare::SecretsStore", async function <
|
|
172
176
|
S extends Record<string, Secret> | undefined = undefined,
|
|
173
177
|
>(this: Context<SecretsStore<S>>, id: string, props: SecretsStoreProps<S>): Promise<
|
|
@@ -206,17 +210,12 @@ const _SecretsStore = Resource("cloudflare::SecretsStore", async function <
|
|
|
206
210
|
} else {
|
|
207
211
|
// If adopt is true, first check if a store with this name already exists
|
|
208
212
|
if (props.adopt) {
|
|
209
|
-
console.log(`Checking for existing secrets store '${name}' to adopt`);
|
|
210
213
|
const existingStore = await findSecretsStoreByName(api, name);
|
|
211
214
|
|
|
212
215
|
if (existingStore) {
|
|
213
|
-
console.log(`Found existing secrets store '${name}', adopting it`);
|
|
214
216
|
storeId = existingStore.id;
|
|
215
217
|
createdAt = existingStore.createdAt || Date.now();
|
|
216
218
|
} else {
|
|
217
|
-
console.log(
|
|
218
|
-
`No existing secrets store '${name}' found, creating new one`,
|
|
219
|
-
);
|
|
220
219
|
const { id } = await createSecretsStore(api, {
|
|
221
220
|
...props,
|
|
222
221
|
name,
|
|
@@ -484,6 +484,16 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
484
484
|
name: bindingName,
|
|
485
485
|
namespace: binding.namespaceName,
|
|
486
486
|
});
|
|
487
|
+
} else if (binding.type === "secret_key") {
|
|
488
|
+
meta.bindings.push({
|
|
489
|
+
type: "secret_key",
|
|
490
|
+
name: bindingName,
|
|
491
|
+
algorithm: binding.algorithm,
|
|
492
|
+
format: binding.format,
|
|
493
|
+
usages: binding.usages,
|
|
494
|
+
key_base64: binding.key_base64?.unencrypted,
|
|
495
|
+
key_jwk: binding.key_jwk?.unencrypted,
|
|
496
|
+
});
|
|
487
497
|
} else {
|
|
488
498
|
// @ts-expect-error - we should never reach here
|
|
489
499
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
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
|
*
|
|
@@ -592,8 +599,36 @@ export function WorkerRef<
|
|
|
592
599
|
* }
|
|
593
600
|
* });
|
|
594
601
|
*
|
|
595
|
-
* @
|
|
596
|
-
*
|
|
602
|
+
* @example
|
|
603
|
+
* // Create a worker with queue event sources and custom consumer settings:
|
|
604
|
+
* const taskQueue = await Queue("task-queue", {
|
|
605
|
+
* name: "task-queue"
|
|
606
|
+
* });
|
|
607
|
+
*
|
|
608
|
+
* const dlq = await Queue("failed-tasks", {
|
|
609
|
+
* name: "failed-tasks"
|
|
610
|
+
* });
|
|
611
|
+
*
|
|
612
|
+
* const queueWorker = await Worker("queue-processor", {
|
|
613
|
+
* name: "queue-processor",
|
|
614
|
+
* entrypoint: "./src/processor.ts",
|
|
615
|
+
* bindings: {
|
|
616
|
+
* TASK_QUEUE: taskQueue // Producer: bind queue for sending messages
|
|
617
|
+
* },
|
|
618
|
+
* eventSources: [{ // Consumer: configure processing settings
|
|
619
|
+
* queue: taskQueue,
|
|
620
|
+
* settings: {
|
|
621
|
+
* batchSize: 15, // Process 15 messages at once
|
|
622
|
+
* maxConcurrency: 3, // Allow 3 concurrent invocations
|
|
623
|
+
* maxRetries: 5, // Retry failed messages up to 5 times
|
|
624
|
+
* maxWaitTimeMs: 2500, // Wait up to 2.5 seconds to fill a batch
|
|
625
|
+
* retryDelay: 60, // Wait 60 seconds before retrying failed messages
|
|
626
|
+
* deadLetterQueue: dlq // Send failed messages to dead letter queue
|
|
627
|
+
* }
|
|
628
|
+
* }]
|
|
629
|
+
* });
|
|
630
|
+
*
|
|
631
|
+
* @see https://developers.cloudflare.com/workers/
|
|
597
632
|
*/
|
|
598
633
|
export function Worker<
|
|
599
634
|
const B extends Bindings,
|
|
@@ -847,6 +882,7 @@ export const _Worker = Resource(
|
|
|
847
882
|
props.script ??
|
|
848
883
|
(await bundleWorkerScript({
|
|
849
884
|
...props,
|
|
885
|
+
name: workerName,
|
|
850
886
|
compatibilityDate,
|
|
851
887
|
compatibilityFlags,
|
|
852
888
|
}));
|
|
@@ -607,6 +607,8 @@ function processBindings(
|
|
|
607
607
|
binding: bindingName,
|
|
608
608
|
namespace: binding.namespaceName,
|
|
609
609
|
});
|
|
610
|
+
} else if (binding.type === "secret_key") {
|
|
611
|
+
// no-op
|
|
610
612
|
} else {
|
|
611
613
|
// biome-ignore lint/correctness/noVoidTypeReturn: it returns never
|
|
612
614
|
return assertNever(binding);
|
package/src/secret.ts
CHANGED
|
@@ -8,7 +8,7 @@ declare global {
|
|
|
8
8
|
|
|
9
9
|
// a global registry of all secrets that we will use when serializing an application
|
|
10
10
|
const globalSecrets: {
|
|
11
|
-
[name: string]: Secret
|
|
11
|
+
[name: string]: Secret<any>;
|
|
12
12
|
} = (globalThis.__ALCHEMY_SECRETS__ ??= {});
|
|
13
13
|
|
|
14
14
|
let i = 0;
|
|
@@ -53,19 +53,19 @@ const SecretSymbol = Symbol.for("alchemy::Secret");
|
|
|
53
53
|
* }
|
|
54
54
|
* }
|
|
55
55
|
*/
|
|
56
|
-
export class Secret {
|
|
56
|
+
export class Secret<T = string> {
|
|
57
57
|
[SecretSymbol] = true;
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* @internal
|
|
61
61
|
*/
|
|
62
|
-
public static all(): Secret[] {
|
|
62
|
+
public static all(): Secret<any>[] {
|
|
63
63
|
return Object.values(globalSecrets);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
public readonly type = "secret";
|
|
67
67
|
constructor(
|
|
68
|
-
readonly unencrypted:
|
|
68
|
+
readonly unencrypted: T,
|
|
69
69
|
readonly name: string = nextName(),
|
|
70
70
|
) {
|
|
71
71
|
globalSecrets[name] = this;
|
|
@@ -111,10 +111,10 @@ export function isSecret(binding: any): binding is Secret {
|
|
|
111
111
|
* @throws {Error} If the value is undefined
|
|
112
112
|
* @throws {Error} If no password is set in the alchemy application options or current scope
|
|
113
113
|
*/
|
|
114
|
-
export function secret<S
|
|
115
|
-
unencrypted: S,
|
|
114
|
+
export function secret<S = string>(
|
|
115
|
+
unencrypted: S | undefined,
|
|
116
116
|
name?: string,
|
|
117
|
-
): Secret {
|
|
117
|
+
): Secret<S> {
|
|
118
118
|
if (unencrypted === undefined) {
|
|
119
119
|
throw new Error("Secret cannot be undefined");
|
|
120
120
|
}
|
package/src/serde.ts
CHANGED
|
@@ -31,33 +31,39 @@ export type Serialized<T> = T extends
|
|
|
31
31
|
? {
|
|
32
32
|
"@schema": string;
|
|
33
33
|
}
|
|
34
|
-
: T extends Secret
|
|
34
|
+
: T extends Secret<string>
|
|
35
35
|
? {
|
|
36
36
|
"@secret": string;
|
|
37
37
|
}
|
|
38
|
-
: T extends
|
|
38
|
+
: T extends Secret<any>
|
|
39
39
|
? {
|
|
40
|
-
"@
|
|
40
|
+
"@secret": {
|
|
41
|
+
object: string;
|
|
42
|
+
};
|
|
41
43
|
}
|
|
42
|
-
: T extends
|
|
44
|
+
: T extends Date
|
|
43
45
|
? {
|
|
44
|
-
"@
|
|
46
|
+
"@date": string;
|
|
45
47
|
}
|
|
46
|
-
: T extends
|
|
48
|
+
: T extends Symbol
|
|
47
49
|
? {
|
|
48
|
-
"@
|
|
50
|
+
"@symbol": string;
|
|
49
51
|
}
|
|
50
|
-
: T extends
|
|
51
|
-
?
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
: T extends Scope
|
|
53
|
+
? {
|
|
54
|
+
"@scope": null;
|
|
55
|
+
}
|
|
56
|
+
: T extends Function
|
|
57
|
+
? undefined
|
|
58
|
+
: T extends Array<infer U>
|
|
59
|
+
? Array<Serialized<U>>
|
|
60
|
+
: T extends object
|
|
61
|
+
? {
|
|
62
|
+
[K in keyof T as K extends symbol
|
|
63
|
+
? string
|
|
64
|
+
: K]: Serialized<T[K]>;
|
|
65
|
+
}
|
|
66
|
+
: T;
|
|
61
67
|
|
|
62
68
|
export type SerializedScope = {
|
|
63
69
|
[fqn: ResourceFQN]: Serialized<Resource>;
|
|
@@ -121,7 +127,14 @@ export async function serialize(
|
|
|
121
127
|
return {
|
|
122
128
|
"@secret":
|
|
123
129
|
options?.encrypt !== false
|
|
124
|
-
?
|
|
130
|
+
? typeof value.unencrypted === "string"
|
|
131
|
+
? await encrypt(value.unencrypted, scope.password)
|
|
132
|
+
: {
|
|
133
|
+
data: await encrypt(
|
|
134
|
+
JSON.stringify(value.unencrypted),
|
|
135
|
+
scope.password,
|
|
136
|
+
),
|
|
137
|
+
}
|
|
125
138
|
: value.unencrypted,
|
|
126
139
|
};
|
|
127
140
|
} else if (isType(value)) {
|
|
@@ -203,13 +216,20 @@ export async function deserialize(
|
|
|
203
216
|
);
|
|
204
217
|
}
|
|
205
218
|
if (value && typeof value === "object") {
|
|
206
|
-
if (
|
|
219
|
+
if (value["@secret"]) {
|
|
207
220
|
if (!scope.password) {
|
|
208
221
|
throw new Error(
|
|
209
222
|
"Cannot deserialize secret without password, did you forget to set password when initializing your alchemy app?\n" +
|
|
210
223
|
"See: https://alchemy.run/docs/concepts/secret.html#encryption-password",
|
|
211
224
|
);
|
|
212
225
|
}
|
|
226
|
+
if (typeof value["@secret"] === "object") {
|
|
227
|
+
return new Secret(
|
|
228
|
+
JSON.parse(
|
|
229
|
+
await decryptWithKey(value["@secret"].data, scope.password),
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
213
233
|
return new Secret(await decryptWithKey(value["@secret"], scope.password));
|
|
214
234
|
} else if ("@schema" in value) {
|
|
215
235
|
return value["@schema"];
|
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
|
}
|