alchemy 0.30.0 → 0.31.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.
Files changed (55) hide show
  1. package/bin/alchemy.mjs +17 -12
  2. package/bin/create-alchemy.ts +17 -11
  3. package/lib/cloudflare/bindings.d.ts +24 -4
  4. package/lib/cloudflare/bindings.d.ts.map +1 -1
  5. package/lib/cloudflare/bindings.js.map +1 -1
  6. package/lib/cloudflare/bound.d.ts +3 -2
  7. package/lib/cloudflare/bound.d.ts.map +1 -1
  8. package/lib/cloudflare/index.d.ts +1 -0
  9. package/lib/cloudflare/index.d.ts.map +1 -1
  10. package/lib/cloudflare/index.js +1 -0
  11. package/lib/cloudflare/index.js.map +1 -1
  12. package/lib/cloudflare/queue.d.ts +24 -0
  13. package/lib/cloudflare/queue.d.ts.map +1 -1
  14. package/lib/cloudflare/queue.js +24 -0
  15. package/lib/cloudflare/queue.js.map +1 -1
  16. package/lib/cloudflare/secret-key.d.ts +120 -0
  17. package/lib/cloudflare/secret-key.d.ts.map +1 -0
  18. package/lib/cloudflare/secret-key.js +92 -0
  19. package/lib/cloudflare/secret-key.js.map +1 -0
  20. package/lib/cloudflare/secret.d.ts +9 -5
  21. package/lib/cloudflare/secret.d.ts.map +1 -1
  22. package/lib/cloudflare/secret.js +13 -5
  23. package/lib/cloudflare/secret.js.map +1 -1
  24. package/lib/cloudflare/secrets-store.d.ts +3 -0
  25. package/lib/cloudflare/secrets-store.d.ts.map +1 -1
  26. package/lib/cloudflare/secrets-store.js +3 -3
  27. package/lib/cloudflare/secrets-store.js.map +1 -1
  28. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  29. package/lib/cloudflare/worker-metadata.js +11 -0
  30. package/lib/cloudflare/worker-metadata.js.map +1 -1
  31. package/lib/cloudflare/worker.d.ts +30 -2
  32. package/lib/cloudflare/worker.d.ts.map +1 -1
  33. package/lib/cloudflare/worker.js.map +1 -1
  34. package/lib/cloudflare/wrangler.json.js +3 -0
  35. package/lib/cloudflare/wrangler.json.js.map +1 -1
  36. package/lib/secret.d.ts +5 -5
  37. package/lib/secret.d.ts.map +1 -1
  38. package/lib/secret.js.map +1 -1
  39. package/lib/serde.d.ts +5 -1
  40. package/lib/serde.d.ts.map +1 -1
  41. package/lib/serde.js +9 -2
  42. package/lib/serde.js.map +1 -1
  43. package/package.json +18 -17
  44. package/src/cloudflare/bindings.ts +34 -2
  45. package/src/cloudflare/bound.ts +36 -33
  46. package/src/cloudflare/index.ts +1 -0
  47. package/src/cloudflare/queue.ts +24 -0
  48. package/src/cloudflare/secret-key.ts +143 -0
  49. package/src/cloudflare/secret.ts +55 -41
  50. package/src/cloudflare/secrets-store.ts +4 -5
  51. package/src/cloudflare/worker-metadata.ts +10 -0
  52. package/src/cloudflare/worker.ts +30 -2
  53. package/src/cloudflare/wrangler.json.ts +2 -0
  54. package/src/secret.ts +7 -7
  55. package/src/serde.ts +40 -20
@@ -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}`);
@@ -599,8 +599,36 @@ export function WorkerRef<
599
599
  * }
600
600
  * });
601
601
  *
602
- * @see
603
- * 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/
604
632
  */
605
633
  export function Worker<
606
634
  const B extends Bindings,
@@ -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"];