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.
Files changed (78) hide show
  1. package/bin/alchemy.mjs +16086 -0
  2. package/bin/alchemy.ts +67 -0
  3. package/bin/create-alchemy.ts +1208 -0
  4. package/lib/cloudflare/api-error.d.ts.map +1 -1
  5. package/lib/cloudflare/api-error.js +2 -1
  6. package/lib/cloudflare/api-error.js.map +1 -1
  7. package/lib/cloudflare/bindings.d.ts +24 -4
  8. package/lib/cloudflare/bindings.d.ts.map +1 -1
  9. package/lib/cloudflare/bindings.js.map +1 -1
  10. package/lib/cloudflare/bound.d.ts +3 -2
  11. package/lib/cloudflare/bound.d.ts.map +1 -1
  12. package/lib/cloudflare/bundle/bundle-worker.d.ts +1 -0
  13. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  14. package/lib/cloudflare/bundle/bundle-worker.js +16 -5
  15. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  16. package/lib/cloudflare/index.d.ts +1 -0
  17. package/lib/cloudflare/index.d.ts.map +1 -1
  18. package/lib/cloudflare/index.js +1 -0
  19. package/lib/cloudflare/index.js.map +1 -1
  20. package/lib/cloudflare/nuxt.d.ts.map +1 -1
  21. package/lib/cloudflare/nuxt.js +2 -1
  22. package/lib/cloudflare/nuxt.js.map +1 -1
  23. package/lib/cloudflare/queue.d.ts +24 -0
  24. package/lib/cloudflare/queue.d.ts.map +1 -1
  25. package/lib/cloudflare/queue.js +24 -0
  26. package/lib/cloudflare/queue.js.map +1 -1
  27. package/lib/cloudflare/redwood.d.ts.map +1 -1
  28. package/lib/cloudflare/redwood.js +1 -0
  29. package/lib/cloudflare/redwood.js.map +1 -1
  30. package/lib/cloudflare/secret-key.d.ts +120 -0
  31. package/lib/cloudflare/secret-key.d.ts.map +1 -0
  32. package/lib/cloudflare/secret-key.js +92 -0
  33. package/lib/cloudflare/secret-key.js.map +1 -0
  34. package/lib/cloudflare/secret.d.ts +9 -5
  35. package/lib/cloudflare/secret.d.ts.map +1 -1
  36. package/lib/cloudflare/secret.js +13 -5
  37. package/lib/cloudflare/secret.js.map +1 -1
  38. package/lib/cloudflare/secrets-store.d.ts +3 -0
  39. package/lib/cloudflare/secrets-store.d.ts.map +1 -1
  40. package/lib/cloudflare/secrets-store.js +3 -3
  41. package/lib/cloudflare/secrets-store.js.map +1 -1
  42. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  43. package/lib/cloudflare/worker-metadata.js +11 -0
  44. package/lib/cloudflare/worker-metadata.js.map +1 -1
  45. package/lib/cloudflare/worker.d.ts +36 -2
  46. package/lib/cloudflare/worker.d.ts.map +1 -1
  47. package/lib/cloudflare/worker.js +1 -0
  48. package/lib/cloudflare/worker.js.map +1 -1
  49. package/lib/cloudflare/wrangler.json.js +3 -0
  50. package/lib/cloudflare/wrangler.json.js.map +1 -1
  51. package/lib/secret.d.ts +5 -5
  52. package/lib/secret.d.ts.map +1 -1
  53. package/lib/secret.js.map +1 -1
  54. package/lib/serde.d.ts +5 -1
  55. package/lib/serde.d.ts.map +1 -1
  56. package/lib/serde.js +9 -2
  57. package/lib/serde.js.map +1 -1
  58. package/lib/util/content-type.d.ts.map +1 -1
  59. package/lib/util/content-type.js +24 -20
  60. package/lib/util/content-type.js.map +1 -1
  61. package/package.json +11 -5
  62. package/src/cloudflare/api-error.ts +4 -1
  63. package/src/cloudflare/bindings.ts +34 -2
  64. package/src/cloudflare/bound.ts +36 -33
  65. package/src/cloudflare/bundle/bundle-worker.ts +26 -5
  66. package/src/cloudflare/index.ts +1 -0
  67. package/src/cloudflare/nuxt.ts +2 -1
  68. package/src/cloudflare/queue.ts +24 -0
  69. package/src/cloudflare/redwood.ts +1 -0
  70. package/src/cloudflare/secret-key.ts +143 -0
  71. package/src/cloudflare/secret.ts +55 -41
  72. package/src/cloudflare/secrets-store.ts +4 -5
  73. package/src/cloudflare/worker-metadata.ts +10 -0
  74. package/src/cloudflare/worker.ts +38 -2
  75. package/src/cloudflare/wrangler.json.ts +2 -0
  76. package/src/secret.ts +7 -7
  77. package/src/serde.ts +40 -20
  78. 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
+ }
@@ -10,7 +10,7 @@ import {
10
10
  type CloudflareApi,
11
11
  type CloudflareApiOptions,
12
12
  } from "./api.ts";
13
- import type { SecretsStore } from "./secrets-store.ts";
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: SecretsStore<any>;
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: SecretsStore<any>;
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 interface Secret
75
- extends Resource<"cloudflare::Secret">,
76
- Omit<_SecretProps, "delete"> {
77
- /**
78
- * The binding type for Cloudflare Workers
79
- */
80
- type: "secrets_store_secret";
81
-
82
- /**
83
- * The name of the secret
84
- */
85
- name: string;
86
-
87
- /**
88
- * The unique identifier of the secrets store this secret belongs to
89
- */
90
- storeId: string;
91
-
92
- /**
93
- * The secret value (as an alchemy Secret instance)
94
- */
95
- value: AlchemySecret;
96
-
97
- /**
98
- * Timestamp when the secret was created
99
- */
100
- createdAt: number;
101
-
102
- /**
103
- * Timestamp when the secret was last modified
104
- */
105
- modifiedAt: number;
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, props.store.id, name);
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, props.store.id, name, props.value);
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: props.store.id,
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}`);
@@ -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
- * @see
596
- * https://developers.cloudflare.com/workers/
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: string,
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 extends string | undefined>(
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 Date
38
+ : T extends Secret<any>
39
39
  ? {
40
- "@date": string;
40
+ "@secret": {
41
+ object: string;
42
+ };
41
43
  }
42
- : T extends Symbol
44
+ : T extends Date
43
45
  ? {
44
- "@symbol": string;
46
+ "@date": string;
45
47
  }
46
- : T extends Scope
48
+ : T extends Symbol
47
49
  ? {
48
- "@scope": null;
50
+ "@symbol": string;
49
51
  }
50
- : T extends Function
51
- ? undefined
52
- : T extends Array<infer U>
53
- ? Array<Serialized<U>>
54
- : T extends object
55
- ? {
56
- [K in keyof T as K extends symbol
57
- ? string
58
- : K]: Serialized<T[K]>;
59
- }
60
- : T;
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
- ? await encrypt(value.unencrypted, scope.password)
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 (typeof value["@secret"] === "string") {
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"];
@@ -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
- ".otf": "font/otf",
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
  }