alchemy 0.70.2 → 0.71.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 (88) hide show
  1. package/bin/alchemy.js +13 -15
  2. package/bin/commands/init.ts +0 -10
  3. package/lib/apply.d.ts.map +1 -1
  4. package/lib/apply.js +2 -0
  5. package/lib/apply.js.map +1 -1
  6. package/lib/cloudflare/bindings.d.ts +12 -2
  7. package/lib/cloudflare/bindings.d.ts.map +1 -1
  8. package/lib/cloudflare/bindings.js.map +1 -1
  9. package/lib/cloudflare/bound.d.ts +3 -2
  10. package/lib/cloudflare/bound.d.ts.map +1 -1
  11. package/lib/cloudflare/bucket.d.ts +6 -0
  12. package/lib/cloudflare/bucket.d.ts.map +1 -1
  13. package/lib/cloudflare/bucket.js +5 -3
  14. package/lib/cloudflare/bucket.js.map +1 -1
  15. package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -1
  16. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  17. package/lib/cloudflare/bun-spa/bun-spa.js +29 -9
  18. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  19. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  20. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  21. package/lib/cloudflare/index.d.ts +2 -0
  22. package/lib/cloudflare/index.d.ts.map +1 -1
  23. package/lib/cloudflare/index.js +2 -0
  24. package/lib/cloudflare/index.js.map +1 -1
  25. package/lib/cloudflare/logpush-job.d.ts +349 -0
  26. package/lib/cloudflare/logpush-job.d.ts.map +1 -0
  27. package/lib/cloudflare/logpush-job.js +277 -0
  28. package/lib/cloudflare/logpush-job.js.map +1 -0
  29. package/lib/cloudflare/miniflare/build-worker-options.d.ts +1 -0
  30. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  31. package/lib/cloudflare/miniflare/build-worker-options.js +16 -6
  32. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  33. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  34. package/lib/cloudflare/miniflare/remote-binding-proxy.js +2 -0
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  36. package/lib/cloudflare/queue-consumer.js +2 -2
  37. package/lib/cloudflare/queue-consumer.js.map +1 -1
  38. package/lib/cloudflare/worker-loader.d.ts +9 -0
  39. package/lib/cloudflare/worker-loader.d.ts.map +1 -0
  40. package/lib/cloudflare/worker-loader.js +6 -0
  41. package/lib/cloudflare/worker-loader.js.map +1 -0
  42. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  43. package/lib/cloudflare/worker-metadata.js +6 -0
  44. package/lib/cloudflare/worker-metadata.js.map +1 -1
  45. package/lib/cloudflare/worker-subdomain.js +1 -1
  46. package/lib/cloudflare/worker.d.ts.map +1 -1
  47. package/lib/cloudflare/worker.js +7 -0
  48. package/lib/cloudflare/worker.js.map +1 -1
  49. package/lib/cloudflare/wrangler.json.d.ts +3 -0
  50. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  51. package/lib/cloudflare/wrangler.json.js +9 -0
  52. package/lib/cloudflare/wrangler.json.js.map +1 -1
  53. package/lib/github/comment.d.ts.map +1 -1
  54. package/lib/github/comment.js +14 -7
  55. package/lib/github/comment.js.map +1 -1
  56. package/lib/scope.d.ts.map +1 -1
  57. package/lib/scope.js +6 -7
  58. package/lib/scope.js.map +1 -1
  59. package/lib/util/retry.d.ts +1 -1
  60. package/lib/util/retry.d.ts.map +1 -1
  61. package/lib/util/retry.js +2 -2
  62. package/lib/util/retry.js.map +1 -1
  63. package/lib/util/validate-resource-id.d.ts +2 -0
  64. package/lib/util/validate-resource-id.d.ts.map +1 -0
  65. package/lib/util/validate-resource-id.js +11 -0
  66. package/lib/util/validate-resource-id.js.map +1 -0
  67. package/package.json +2 -2
  68. package/src/apply.ts +2 -0
  69. package/src/cloudflare/bindings.ts +13 -0
  70. package/src/cloudflare/bound.ts +36 -32
  71. package/src/cloudflare/bucket.ts +13 -4
  72. package/src/cloudflare/bun-spa/bun-spa.ts +42 -10
  73. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  74. package/src/cloudflare/index.ts +2 -0
  75. package/src/cloudflare/logpush-job.ts +768 -0
  76. package/src/cloudflare/miniflare/build-worker-options.ts +18 -6
  77. package/src/cloudflare/miniflare/remote-binding-proxy.ts +2 -0
  78. package/src/cloudflare/queue-consumer.ts +2 -2
  79. package/src/cloudflare/worker-loader.ts +12 -0
  80. package/src/cloudflare/worker-metadata.ts +5 -0
  81. package/src/cloudflare/worker-subdomain.ts +1 -1
  82. package/src/cloudflare/worker.ts +8 -0
  83. package/src/cloudflare/wrangler.json.ts +15 -0
  84. package/src/github/comment.ts +17 -8
  85. package/src/scope.ts +7 -10
  86. package/src/util/retry.ts +2 -1
  87. package/src/util/validate-resource-id.ts +10 -0
  88. package/workers/tunnel-proxy.js +1 -1
@@ -1,4 +1,5 @@
1
1
  import * as miniflare from "miniflare";
2
+ import path from "pathe";
2
3
  import { assertNever } from "../../util/assert-never.ts";
3
4
  import type { HTTPServer } from "../../util/http.ts";
4
5
  import { logger } from "../../util/logger.ts";
@@ -26,6 +27,7 @@ export interface MiniflareWorkerInput {
26
27
  bundle: WorkerBundleSource;
27
28
  port: number | undefined;
28
29
  tunnel: boolean | undefined;
30
+ cwd: string;
29
31
  }
30
32
 
31
33
  type RemoteOnlyBindingType =
@@ -78,11 +80,6 @@ export const buildWorkerOptions = async (
78
80
  proxy: true,
79
81
  },
80
82
  ],
81
- containerEngine: {
82
- localDocker: {
83
- socketPath: await getDockerSocketPath(),
84
- },
85
- },
86
83
  // This exposes the worker as a route that can be accessed by setting the MF-Route-Override header.
87
84
  routes: [input.name],
88
85
  };
@@ -119,7 +116,7 @@ export const buildWorkerOptions = async (
119
116
  case "assets": {
120
117
  options.assets = {
121
118
  binding: key,
122
- directory: binding.path,
119
+ directory: path.resolve(input.cwd, binding.path),
123
120
  assetConfig: {
124
121
  html_handling: input.assets?.html_handling,
125
122
  not_found_handling: input.assets?.not_found_handling,
@@ -144,6 +141,11 @@ export const buildWorkerOptions = async (
144
141
  imageName: binding.image.imageRef,
145
142
  },
146
143
  };
144
+ options.containerEngine = {
145
+ localDocker: {
146
+ socketPath: await getDockerSocketPath(),
147
+ },
148
+ };
147
149
  break;
148
150
  }
149
151
  case "d1": {
@@ -268,6 +270,10 @@ export const buildWorkerOptions = async (
268
270
  type: "r2_bucket",
269
271
  name: key,
270
272
  bucket_name: binding.name,
273
+ jurisdiction:
274
+ binding.jurisdiction === "default"
275
+ ? undefined
276
+ : binding.jurisdiction,
271
277
  raw: true,
272
278
  });
273
279
  } else {
@@ -308,6 +314,10 @@ export const buildWorkerOptions = async (
308
314
  };
309
315
  break;
310
316
  }
317
+ case "worker_loader": {
318
+ (options.workerLoaders ??= {})[key] = {};
319
+ break;
320
+ }
311
321
  case "workflow": {
312
322
  (options.workflows ??= {})[key] = {
313
323
  name: binding.workflowName,
@@ -329,11 +339,13 @@ export const buildWorkerOptions = async (
329
339
  );
330
340
  }
331
341
  if (isQueueEventSource(eventSource)) {
342
+ const dlq = eventSource.settings?.deadLetterQueue;
332
343
  (options.queueConsumers ??= {})[queue.name] = {
333
344
  maxBatchSize: eventSource.settings?.batchSize,
334
345
  maxBatchTimeout: eventSource.settings?.maxWaitTimeMs,
335
346
  maxRetries: eventSource.settings?.maxRetries,
336
347
  retryDelay: eventSource.settings?.retryDelay,
348
+ deadLetterQueue: typeof dlq === "string" ? dlq : dlq?.name,
337
349
  };
338
350
  } else {
339
351
  (options.queueConsumers ??= {})[eventSource.name] = {};
@@ -82,6 +82,8 @@ export async function createRemoteProxyWorker(input: {
82
82
  headers,
83
83
  body: req.body,
84
84
  redirect: "manual",
85
+ // @ts-expect-error - caused by @cloudflare/workers-types
86
+ duplex: req.body ? "half" : undefined,
85
87
  });
86
88
 
87
89
  const responseHeaders = new Headers(res.headers);
@@ -318,7 +318,7 @@ export async function createQueueConsumer(
318
318
  typeof props.settings.deadLetterQueue === "string"
319
319
  ? props.settings.deadLetterQueue
320
320
  : props.settings.deadLetterQueue.name;
321
- createPayload.settings.dead_letter_queue = dlqName;
321
+ createPayload.dead_letter_queue = dlqName;
322
322
  }
323
323
  }
324
324
 
@@ -405,7 +405,7 @@ async function updateQueueConsumer(
405
405
  typeof props.settings.deadLetterQueue === "string"
406
406
  ? props.settings.deadLetterQueue
407
407
  : props.settings.deadLetterQueue.name;
408
- updatePayload.settings.dead_letter_queue = dlqName;
408
+ updatePayload.dead_letter_queue = dlqName;
409
409
  }
410
410
  }
411
411
 
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A binding for dynamic worker loaders.
3
+ *
4
+ */
5
+ export type WorkerLoader = {
6
+ type: "worker_loader";
7
+ };
8
+ export function WorkerLoader(): WorkerLoader {
9
+ return {
10
+ type: "worker_loader",
11
+ };
12
+ }
@@ -568,6 +568,11 @@ export async function prepareWorkerMetadata(
568
568
  name: bindingName,
569
569
  namespace: binding.namespaceName,
570
570
  });
571
+ } else if (binding.type === "worker_loader") {
572
+ meta.bindings.push({
573
+ type: "worker_loader",
574
+ name: bindingName,
575
+ });
571
576
  } else if (binding.type === "secret_key") {
572
577
  meta.bindings.push({
573
578
  type: "secret_key",
@@ -111,7 +111,7 @@ export async function enableWorkerSubdomain(
111
111
  ),
112
112
  ),
113
113
  (error) => error instanceof CloudflareApiError && error.status === 404,
114
- 10,
114
+ 20,
115
115
  1000,
116
116
  );
117
117
  }
@@ -982,9 +982,11 @@ const _Worker = Resource(
982
982
  bundle,
983
983
  port: props.dev?.port,
984
984
  tunnel: props.dev?.tunnel ?? this.scope.tunnel,
985
+ cwd: props.cwd ?? process.cwd(),
985
986
  });
986
987
  this.onCleanup(() => controller.dispose());
987
988
  }
989
+
988
990
  await provisionResources(
989
991
  {
990
992
  ...props,
@@ -1240,12 +1242,18 @@ async function provisionResources<B extends Bindings>(
1240
1242
  containers: options.containers,
1241
1243
  domains: props.domains?.map((domain) => {
1242
1244
  if (typeof domain === "string") {
1245
+ if (domain === "") {
1246
+ throw new Error("Domain names cannot be empty strings");
1247
+ }
1243
1248
  return {
1244
1249
  name: domain,
1245
1250
  zoneId: undefined,
1246
1251
  adopt: props.adopt,
1247
1252
  };
1248
1253
  }
1254
+ if (domain.domainName === "") {
1255
+ throw new Error("Domain names cannot be empty strings");
1256
+ }
1249
1257
  return {
1250
1258
  name: domain.domainName,
1251
1259
  zoneId: domain.zoneId,
@@ -376,6 +376,10 @@ export interface WranglerJsonSpec {
376
376
  entrypoint?: string;
377
377
  }[];
378
378
 
379
+ worker_loaders?: {
380
+ binding: string;
381
+ }[];
382
+
379
383
  /**
380
384
  * Workflow bindings
381
385
  */
@@ -627,6 +631,9 @@ function processBindings(
627
631
  const containers: {
628
632
  class_name: string;
629
633
  }[] = [];
634
+ const workerLoaders: {
635
+ binding: string;
636
+ }[] = [];
630
637
 
631
638
  for (const eventSource of eventSources ?? []) {
632
639
  if (isQueueEventSource(eventSource)) {
@@ -850,6 +857,10 @@ function processBindings(
850
857
  containers.push({
851
858
  class_name: binding.className,
852
859
  });
860
+ } else if (binding.type === "worker_loader") {
861
+ workerLoaders.push({
862
+ binding: bindingName,
863
+ });
853
864
  } else {
854
865
  console.log("binding", binding);
855
866
  // biome-ignore lint/correctness/noVoidTypeReturn: it returns never
@@ -930,4 +941,8 @@ function processBindings(
930
941
  bindings: unsafeBindings,
931
942
  };
932
943
  }
944
+
945
+ if (workerLoaders.length > 0) {
946
+ spec.worker_loaders = workerLoaders;
947
+ }
933
948
  }
@@ -173,17 +173,17 @@ export const GitHubComment = Resource(
173
173
  props: GitHubCommentProps,
174
174
  ): Promise<GitHubComment> {
175
175
  // Create authenticated Octokit client - will automatically handle token resolution
176
- const octokit = await createGitHubClient({
177
- token: props.token?.unencrypted,
178
- });
179
-
180
- // Verify authentication and permissions
181
- if (!this.quiet) {
182
- await verifyGitHubAuth(octokit, props.owner, props.repository);
183
- }
184
176
 
185
177
  if (this.phase === "delete") {
186
178
  if (this.output?.commentId && props.allowDelete) {
179
+ const octokit = await createGitHubClient({
180
+ token: props.token?.unencrypted,
181
+ });
182
+
183
+ // Verify authentication and permissions
184
+ if (!this.quiet) {
185
+ await verifyGitHubAuth(octokit, props.owner, props.repository);
186
+ }
187
187
  try {
188
188
  // Delete the comment
189
189
  await octokit.rest.issues.deleteComment({
@@ -209,6 +209,15 @@ export const GitHubComment = Resource(
209
209
  return this.destroy();
210
210
  }
211
211
 
212
+ const octokit = await createGitHubClient({
213
+ token: props.token?.unencrypted,
214
+ });
215
+
216
+ // Verify authentication and permissions
217
+ if (!this.quiet) {
218
+ await verifyGitHubAuth(octokit, props.owner, props.repository);
219
+ }
220
+
212
221
  try {
213
222
  if (this.phase === "update" && this.output?.commentId) {
214
223
  // Update existing comment
package/src/scope.ts CHANGED
@@ -30,6 +30,7 @@ import { logger } from "./util/logger.ts";
30
30
  import { AsyncMutex } from "./util/mutex.ts";
31
31
  import { ALCHEMY_ROOT } from "./util/root-dir.ts";
32
32
  import { createAndSendEvent } from "./util/telemetry.ts";
33
+ import { validateResourceID } from "./util/validate-resource-id.ts";
33
34
 
34
35
  export class RootScopeStateAttemptError extends Error {
35
36
  constructor() {
@@ -265,21 +266,17 @@ export class Scope {
265
266
  // Store provider credentials (TypeScript ensures no conflicts with core options)
266
267
  this.providerCredentials = providerCredentials as ProviderCredentials;
267
268
 
268
- const isChild = this.parent !== undefined;
269
- if (this.scopeName?.includes(":") && isChild) {
270
- // TODO(sam): relax this constraint once we move to SQLite3 store
271
- throw new Error(
272
- `Scope name "${this.scopeName}" cannot contain double colons`,
273
- );
274
- }
275
-
276
269
  this.stage = stage ?? this.parent?.stage ?? DEFAULT_STAGE;
277
270
  this.profile = profile ?? this.parent?.profile;
278
271
  this.parent?.children.set(this.scopeName!, this);
279
272
  this.quiet = quiet ?? this.parent?.quiet ?? false;
280
- if (this.parent && !this.scopeName) {
281
- throw new Error("Scope name is required when creating a child scope");
273
+ if (this.parent) {
274
+ if (!this.scopeName) {
275
+ throw new Error("Scope name is required when creating a child scope");
276
+ }
277
+ validateResourceID(this.scopeName, "Scope");
282
278
  }
279
+
283
280
  this.password = password ?? this.parent?.password;
284
281
  this.noTrack = noTrack ?? this.parent?.noTrack ?? false;
285
282
  const resolvedPhase = phase ?? this.parent?.phase;
package/src/util/retry.ts CHANGED
@@ -14,6 +14,7 @@ export async function withExponentialBackoff<T>(
14
14
  isRetryable: (error: any) => any,
15
15
  maxAttempts = 5,
16
16
  initialDelayMs = 100,
17
+ maxDelayMs = 10000,
17
18
  ): Promise<T> {
18
19
  let attempt = 0;
19
20
  let delay = initialDelayMs;
@@ -31,7 +32,7 @@ export async function withExponentialBackoff<T>(
31
32
  const jitter = Math.random() * 0.1 * delay;
32
33
  await new Promise((resolve) => setTimeout(resolve, delay + jitter));
33
34
  delay *= 2; // Double the delay for next attempt
34
- delay = Math.min(delay, 10000); // Cap at 10 seconds
35
+ delay = Math.min(delay, maxDelayMs); // Cap at 10 seconds
35
36
  }
36
37
  }
37
38
  }
@@ -0,0 +1,10 @@
1
+ export function validateResourceID(id: string, qualifier = "Resource"): void {
2
+ //todo(sam): during effect rewrite, this should be much more limiting
3
+ //todo: its hard to undo the flexibility we've offered to users now
4
+ if (!id) {
5
+ throw new Error(`${qualifier} ID cannot be an empty string: "${id}"`);
6
+ }
7
+ if (id.includes(":")) {
8
+ throw new Error(`${qualifier} ID cannot include colons: "${id}"`);
9
+ }
10
+ }
@@ -34,7 +34,7 @@ var h={async fetch(e,r){let n=new URL(e.url);n.host=r.TUNNEL_HOST;let l=new Head
34
34
  Alchemy</a>.</p>
35
35
  </div>
36
36
  <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
37
- <p class="text-sm text-slate-500">Alchemy 0.70.2</p>
37
+ <p class="text-sm text-slate-500">Alchemy 0.71.0</p>
38
38
  </div>
39
39
  </div>
40
40
  </body>