alchemy 0.70.1 → 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 (122) 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/clickhouse/organization.d.ts +1 -1
  7. package/lib/clickhouse/organization.js +2 -2
  8. package/lib/clickhouse/organization.js.map +1 -1
  9. package/lib/clickhouse/service.d.ts +28 -18
  10. package/lib/clickhouse/service.d.ts.map +1 -1
  11. package/lib/clickhouse/service.js +61 -9
  12. package/lib/clickhouse/service.js.map +1 -1
  13. package/lib/cloudflare/account-api-token.js +1 -1
  14. package/lib/cloudflare/account-api-token.js.map +1 -1
  15. package/lib/cloudflare/bindings.d.ts +12 -2
  16. package/lib/cloudflare/bindings.d.ts.map +1 -1
  17. package/lib/cloudflare/bindings.js.map +1 -1
  18. package/lib/cloudflare/bound.d.ts +3 -2
  19. package/lib/cloudflare/bound.d.ts.map +1 -1
  20. package/lib/cloudflare/bucket.d.ts +6 -0
  21. package/lib/cloudflare/bucket.d.ts.map +1 -1
  22. package/lib/cloudflare/bucket.js +5 -3
  23. package/lib/cloudflare/bucket.js.map +1 -1
  24. package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -1
  25. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  26. package/lib/cloudflare/bun-spa/bun-spa.js +29 -9
  27. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  28. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  29. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  30. package/lib/cloudflare/d1-database.d.ts +1 -1
  31. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  32. package/lib/cloudflare/d1-database.js +1 -1
  33. package/lib/cloudflare/d1-database.js.map +1 -1
  34. package/lib/cloudflare/index.d.ts +2 -0
  35. package/lib/cloudflare/index.d.ts.map +1 -1
  36. package/lib/cloudflare/index.js +2 -0
  37. package/lib/cloudflare/index.js.map +1 -1
  38. package/lib/cloudflare/logpush-job.d.ts +349 -0
  39. package/lib/cloudflare/logpush-job.d.ts.map +1 -0
  40. package/lib/cloudflare/logpush-job.js +277 -0
  41. package/lib/cloudflare/logpush-job.js.map +1 -0
  42. package/lib/cloudflare/miniflare/build-worker-options.d.ts +1 -0
  43. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  44. package/lib/cloudflare/miniflare/build-worker-options.js +16 -6
  45. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  46. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  47. package/lib/cloudflare/miniflare/remote-binding-proxy.js +2 -0
  48. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  49. package/lib/cloudflare/permission-groups.d.ts +1489 -26
  50. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  51. package/lib/cloudflare/permission-groups.js +1787 -6
  52. package/lib/cloudflare/permission-groups.js.map +1 -1
  53. package/lib/cloudflare/queue-consumer.js +2 -2
  54. package/lib/cloudflare/queue-consumer.js.map +1 -1
  55. package/lib/cloudflare/worker-loader.d.ts +9 -0
  56. package/lib/cloudflare/worker-loader.d.ts.map +1 -0
  57. package/lib/cloudflare/worker-loader.js +6 -0
  58. package/lib/cloudflare/worker-loader.js.map +1 -0
  59. package/lib/cloudflare/worker-metadata.d.ts +14 -2
  60. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  61. package/lib/cloudflare/worker-metadata.js +10 -1
  62. package/lib/cloudflare/worker-metadata.js.map +1 -1
  63. package/lib/cloudflare/worker-stub.d.ts +3 -1
  64. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  65. package/lib/cloudflare/worker-stub.js +19 -7
  66. package/lib/cloudflare/worker-stub.js.map +1 -1
  67. package/lib/cloudflare/worker-subdomain.js +1 -1
  68. package/lib/cloudflare/worker.d.ts +74 -10
  69. package/lib/cloudflare/worker.d.ts.map +1 -1
  70. package/lib/cloudflare/worker.js +23 -7
  71. package/lib/cloudflare/worker.js.map +1 -1
  72. package/lib/cloudflare/wrangler.json.d.ts +3 -0
  73. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  74. package/lib/cloudflare/wrangler.json.js +9 -0
  75. package/lib/cloudflare/wrangler.json.js.map +1 -1
  76. package/lib/github/comment.d.ts.map +1 -1
  77. package/lib/github/comment.js +14 -7
  78. package/lib/github/comment.js.map +1 -1
  79. package/lib/scope.d.ts.map +1 -1
  80. package/lib/scope.js +6 -7
  81. package/lib/scope.js.map +1 -1
  82. package/lib/util/camel-to-snake.d.ts +12 -0
  83. package/lib/util/camel-to-snake.d.ts.map +1 -0
  84. package/lib/util/camel-to-snake.js +16 -0
  85. package/lib/util/camel-to-snake.js.map +1 -0
  86. package/lib/util/retry.d.ts +1 -1
  87. package/lib/util/retry.d.ts.map +1 -1
  88. package/lib/util/retry.js +2 -2
  89. package/lib/util/retry.js.map +1 -1
  90. package/lib/util/validate-resource-id.d.ts +2 -0
  91. package/lib/util/validate-resource-id.d.ts.map +1 -0
  92. package/lib/util/validate-resource-id.js +11 -0
  93. package/lib/util/validate-resource-id.js.map +1 -0
  94. package/package.json +2 -2
  95. package/src/apply.ts +2 -0
  96. package/src/clickhouse/organization.ts +2 -2
  97. package/src/clickhouse/service.ts +124 -33
  98. package/src/cloudflare/account-api-token.ts +1 -1
  99. package/src/cloudflare/bindings.ts +13 -0
  100. package/src/cloudflare/bound.ts +36 -32
  101. package/src/cloudflare/bucket.ts +13 -4
  102. package/src/cloudflare/bun-spa/bun-spa.ts +42 -11
  103. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  104. package/src/cloudflare/d1-database.ts +2 -2
  105. package/src/cloudflare/index.ts +2 -0
  106. package/src/cloudflare/logpush-job.ts +768 -0
  107. package/src/cloudflare/miniflare/build-worker-options.ts +18 -6
  108. package/src/cloudflare/miniflare/remote-binding-proxy.ts +2 -0
  109. package/src/cloudflare/permission-groups.ts +1832 -45
  110. package/src/cloudflare/queue-consumer.ts +2 -2
  111. package/src/cloudflare/worker-loader.ts +12 -0
  112. package/src/cloudflare/worker-metadata.ts +25 -3
  113. package/src/cloudflare/worker-stub.ts +28 -11
  114. package/src/cloudflare/worker-subdomain.ts +1 -1
  115. package/src/cloudflare/worker.ts +106 -17
  116. package/src/cloudflare/wrangler.json.ts +15 -0
  117. package/src/github/comment.ts +17 -8
  118. package/src/scope.ts +7 -10
  119. package/src/util/camel-to-snake.ts +88 -0
  120. package/src/util/retry.ts +2 -1
  121. package/src/util/validate-resource-id.ts +10 -0
  122. package/workers/tunnel-proxy.js +1 -1
@@ -0,0 +1,2 @@
1
+ export declare function validateResourceID(id: string, qualifier?: string): void;
2
+ //# sourceMappingURL=validate-resource-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-resource-id.d.ts","sourceRoot":"","sources":["../../src/util/validate-resource-id.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,SAAa,GAAG,IAAI,CAS3E"}
@@ -0,0 +1,11 @@
1
+ export function validateResourceID(id, qualifier = "Resource") {
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
+ }
11
+ //# sourceMappingURL=validate-resource-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-resource-id.js","sourceRoot":"","sources":["../../src/util/validate-resource-id.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,EAAU,EAAE,SAAS,GAAG,UAAU;IACnE,qEAAqE;IACrE,wEAAwE;IACxE,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,mCAAmC,EAAE,GAAG,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.70.1",
3
+ "version": "0.71.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Sam Goodwin <sam@alchemy.run>",
6
6
  "homepage": "https://alchemy.run",
@@ -161,6 +161,7 @@
161
161
  "@aws-sdk/credential-providers": "^3.0.0",
162
162
  "@cloudflare/unenv-preset": "^2.6.3",
163
163
  "@iarna/toml": "^2.2.5",
164
+ "@octokit/rest": "^21.1.1",
164
165
  "@smithy/node-config-provider": "^4.0.0",
165
166
  "@smithy/types": "^4.6.0",
166
167
  "aws4fetch": "^1.0.20",
@@ -199,7 +200,6 @@
199
200
  "@cloudflare/vite-plugin": "^1.10.1",
200
201
  "@cloudflare/workers-types": "^4.20250805.0",
201
202
  "@libsql/client": "^0.15.12",
202
- "@octokit/rest": "^21.1.1",
203
203
  "@opennextjs/cloudflare": "^1.6.5",
204
204
  "astro": "^5.13.2",
205
205
  "drizzle-orm": "^0.44.2",
package/src/apply.ts CHANGED
@@ -20,6 +20,7 @@ import type { State } from "./state.ts";
20
20
  import { formatFQN } from "./util/cli.ts";
21
21
  import { logger } from "./util/logger.ts";
22
22
  import { createAndSendEvent } from "./util/telemetry.ts";
23
+ import { validateResourceID } from "./util/validate-resource-id.ts";
23
24
 
24
25
  export interface ApplyOptions {
25
26
  quiet?: boolean;
@@ -56,6 +57,7 @@ async function _apply<Out extends ResourceAttributes>(
56
57
  ): Promise<Awaited<Out> & Resource> {
57
58
  const scope = resource[ResourceScope];
58
59
  const start = performance.now();
60
+ validateResourceID(resource[ResourceID], "Resource");
59
61
  try {
60
62
  const quiet = props?.quiet ?? scope.quiet;
61
63
  await scope.init();
@@ -5,7 +5,7 @@ import { createClickhouseApi } from "./api.ts";
5
5
  * Get an organization by name.
6
6
  *
7
7
  * @example
8
- * const organization = await getOrganizationByName("Alchemy's Organization");
8
+ * const organization = await OrganizationRef("Alchemy's Organization");
9
9
  *
10
10
  * @param name The name of the organization to get.
11
11
  * @param options The api credentials to use.
@@ -22,7 +22,7 @@ export async function OrganizationRef(
22
22
 
23
23
  const organizations = await api.listAvailableOrganizations();
24
24
  const organization = organizations.data.result?.find(
25
- (organization) => organization.name === name,
25
+ (organization) => organization.id === name || organization.name === name,
26
26
  );
27
27
  if (!organization) {
28
28
  throw new Error(`Organization ${name} not found`);
@@ -5,6 +5,26 @@ import { diff } from "../util/diff.ts";
5
5
  import { createClickhouseApi } from "./api.ts";
6
6
  import type { ClickhouseClient } from "./api/sdk.gen.ts";
7
7
  import type { Service as ApiService, Organization } from "./api/types.gen.ts";
8
+ import { OrganizationRef } from "./organization.ts";
9
+
10
+ type MysqlEndpoint = {
11
+ protocol: "mysql";
12
+ host: string;
13
+ port: number;
14
+ username: string;
15
+ };
16
+
17
+ type HttpsEndpoint = {
18
+ protocol: "https";
19
+ host: string;
20
+ port: number;
21
+ };
22
+
23
+ type NativesecureEndpoint = {
24
+ protocol: "nativesecure";
25
+ host: string;
26
+ port: number;
27
+ };
8
28
 
9
29
  export interface ServiceProps {
10
30
  /**
@@ -18,7 +38,7 @@ export interface ServiceProps {
18
38
  secret?: string | Secret<string>;
19
39
 
20
40
  /**
21
- * The id of Clickhouse cloud organization to create the service in.
41
+ * The id, name, or OrganizationRef of Clickhouse cloud organization to create the service in.
22
42
  */
23
43
  organization: string | Organization;
24
44
 
@@ -128,6 +148,13 @@ export interface ServiceProps {
128
148
  */
129
149
  complianceType?: ApiService["complianceType"];
130
150
 
151
+ /**
152
+ * wait for http service to be ready before marking the resource as created
153
+ *
154
+ * @default true
155
+ */
156
+ waitForHttpEndpointReady?: boolean;
157
+
131
158
  //todo(michael): I need to understand more about what these properties do before documenting
132
159
  //todo(michael): support linking to BYOC infrastructure directly
133
160
  byocId?: ApiService["byocId"];
@@ -269,30 +296,17 @@ export interface Service {
269
296
  /**
270
297
  * The mysql endpoint details of the Clickhouse service.
271
298
  */
272
- mysqlEndpoint?: {
273
- protocol: "mysql";
274
- host: string;
275
- port: number;
276
- username: string;
277
- };
299
+ mysqlEndpoint?: MysqlEndpoint;
278
300
 
279
301
  /**
280
302
  * The https endpoint details of the Clickhouse service.
281
303
  */
282
- httpsEndpoint?: {
283
- protocol: "https";
284
- host: string;
285
- port: number;
286
- };
304
+ httpsEndpoint?: HttpsEndpoint;
287
305
 
288
306
  /**
289
307
  * The nativesecure endpoint details of the Clickhouse service.
290
308
  */
291
- nativesecureEndpoint?: {
292
- protocol: "nativesecure";
293
- host: string;
294
- port: number;
295
- };
309
+ nativesecureEndpoint?: NativesecureEndpoint;
296
310
 
297
311
  /**
298
312
  * The desired state of the Clickhouse service.
@@ -310,7 +324,7 @@ export interface Service {
310
324
  *
311
325
  * @example
312
326
  * // Create a basic Clickhouse service on aws
313
- * const organization = await getOrganizationByName("Alchemy's Organization");
327
+ * const organization = await OrganizationRef("Alchemy's Organization");
314
328
  * const service = await Service("clickhouse", {
315
329
  * organization,
316
330
  * provider: "aws",
@@ -348,6 +362,7 @@ export const Service = Resource(
348
362
  if (enableMysqlEndpoint) {
349
363
  endpoints.push({ protocol: "mysql", enabled: true });
350
364
  }
365
+ const waitForHttpEndpointReady = props.waitForHttpEndpointReady ?? true;
351
366
  //todo(michael): comment these in when disabling is supported
352
367
  // const enableHttpsEndpoint = props.enableHttpsEndpoint ?? true;
353
368
  // if (enableHttpsEndpoint) {
@@ -374,7 +389,9 @@ export const Service = Resource(
374
389
 
375
390
  const organizationId =
376
391
  typeof props.organization === "string"
377
- ? props.organization
392
+ ? await OrganizationRef(props.organization)
393
+ .then((organization) => organization.id!)
394
+ .catch(() => props.organization as string)
378
395
  : props.organization.id!;
379
396
 
380
397
  if (this.phase === "delete") {
@@ -393,7 +410,6 @@ export const Service = Resource(
393
410
  organizationId,
394
411
  this.output.clickhouseId,
395
412
  (state) => state === "stopped",
396
- 10 * 60,
397
413
  );
398
414
 
399
415
  await api.deleteService({
@@ -403,6 +419,12 @@ export const Service = Resource(
403
419
  },
404
420
  });
405
421
 
422
+ await waitForServiceDeletion(
423
+ api,
424
+ organizationId,
425
+ this.output.clickhouseId,
426
+ );
427
+
406
428
  return this.destroy();
407
429
  }
408
430
  if (this.phase === "update") {
@@ -481,13 +503,13 @@ export const Service = Resource(
481
503
  updates.releaseChannel = response.releaseChannel!;
482
504
  updates.mysqlEndpoint = response!.endpoints!.find(
483
505
  (endpoint) => endpoint.protocol === "mysql",
484
- ) as any;
506
+ ) as MysqlEndpoint;
485
507
  updates.httpsEndpoint = response!.endpoints!.find(
486
508
  (endpoint) => endpoint.protocol === "https",
487
- ) as any;
509
+ ) as HttpsEndpoint;
488
510
  updates.nativesecureEndpoint = response!.endpoints!.find(
489
511
  (endpoint) => endpoint.protocol === "nativesecure",
490
- ) as any;
512
+ ) as NativesecureEndpoint;
491
513
  }
492
514
 
493
515
  if (stateTarget !== this.output.stateTarget) {
@@ -579,14 +601,24 @@ export const Service = Resource(
579
601
  const password = response.password!;
580
602
  const service = response.service!;
581
603
 
604
+ const httpEndpoint = service.endpoints!.find(
605
+ (endpoint) => endpoint.protocol === "https",
606
+ ) as HttpsEndpoint;
607
+
582
608
  await waitForServiceState(
583
609
  api,
584
610
  organizationId,
585
611
  response.service!.id!,
586
612
  (state) => state === "running" || state === "idle",
587
- 10 * 60,
588
613
  );
589
614
 
615
+ if (waitForHttpEndpointReady && httpEndpoint) {
616
+ await waitForHttpServiceReady(httpEndpoint as HttpsEndpoint, {
617
+ password: password!,
618
+ username: "default",
619
+ });
620
+ }
621
+
590
622
  return {
591
623
  organizationId: organizationId,
592
624
  name: service.name!,
@@ -614,13 +646,11 @@ export const Service = Resource(
614
646
  state: service.state,
615
647
  mysqlEndpoint: service.endpoints!.find(
616
648
  (endpoint) => endpoint.protocol === "mysql",
617
- ) as any,
618
- httpsEndpoint: service.endpoints!.find(
619
- (endpoint) => endpoint.protocol === "https",
620
- ) as any,
649
+ ) as MysqlEndpoint,
650
+ httpsEndpoint: httpEndpoint,
621
651
  nativesecureEndpoint: service.endpoints!.find(
622
652
  (endpoint) => endpoint.protocol === "nativesecure",
623
- ) as any,
653
+ ) as NativesecureEndpoint,
624
654
  };
625
655
  },
626
656
  );
@@ -630,9 +660,8 @@ async function waitForServiceState(
630
660
  organizationId: string,
631
661
  serviceId: string,
632
662
  stateChecker: (state: string) => boolean,
633
- maxWaitSeconds: number,
634
663
  ) {
635
- const checkState = async (): Promise<void> => {
664
+ async function checkState(): Promise<void> {
636
665
  const service = await api.getServiceDetails({
637
666
  path: {
638
667
  organizationId: organizationId,
@@ -646,8 +675,70 @@ async function waitForServiceState(
646
675
  }
647
676
 
648
677
  throw new Error(`Service ${serviceId} is in state ${serviceState}`);
649
- };
678
+ }
679
+
680
+ await waitFor(checkState, 10 * 60);
681
+ }
650
682
 
683
+ async function waitForHttpServiceReady(
684
+ endpoint: {
685
+ protocol: "https";
686
+ host: string;
687
+ port: number;
688
+ },
689
+ credentials: { password: string; username: string },
690
+ ) {
691
+ async function checkHttpEndpoint() {
692
+ await fetch(
693
+ `https://${endpoint.host}:${endpoint.port}/?query=SELECT%20count%28%29%20FROM%20system.databases%20WHERE%20name%20%3D%20%27default%27`,
694
+ {
695
+ headers: {
696
+ Authorization: `Basic ${btoa(`${credentials.username}:${credentials.password}`)}`,
697
+ },
698
+ },
699
+ )
700
+ .then((res) => res.text())
701
+ .then((text) => {
702
+ if (text === "0") {
703
+ throw new Error("Service is not ready");
704
+ }
705
+ return;
706
+ });
707
+ }
708
+
709
+ await waitFor(checkHttpEndpoint, 10 * 60);
710
+ }
711
+
712
+ async function waitForServiceDeletion(
713
+ api: ClickhouseClient,
714
+ organizationId: string,
715
+ serviceId: string,
716
+ ) {
717
+ async function checkDeletion() {
718
+ const status = await api
719
+ .getServiceDetails({
720
+ path: {
721
+ organizationId: organizationId,
722
+ serviceId: serviceId,
723
+ },
724
+ })
725
+ .then((service) => service.response.status)
726
+ .catch((error) => {
727
+ return (error.status as number) ?? 500;
728
+ });
729
+ if (status === 404) {
730
+ return;
731
+ }
732
+ throw new Error(`Service ${serviceId} is not deleted`);
733
+ }
734
+
735
+ await waitFor(checkDeletion, 10 * 60);
736
+ }
737
+
738
+ async function waitFor(
739
+ checkFunction: () => Promise<void>,
740
+ maxWaitSeconds: number,
741
+ ) {
651
742
  if (maxWaitSeconds < 5) {
652
743
  maxWaitSeconds = 5;
653
744
  }
@@ -656,7 +747,7 @@ async function waitForServiceState(
656
747
 
657
748
  for (let attempt = 0; attempt < maxRetries; attempt++) {
658
749
  try {
659
- await checkState();
750
+ await checkFunction();
660
751
  return;
661
752
  } catch (error) {
662
753
  if (attempt === maxRetries - 1) {
@@ -282,7 +282,7 @@ export const AccountApiToken = Resource(
282
282
  return this.destroy();
283
283
  }
284
284
 
285
- const permissionGroups = await PermissionGroups(props);
285
+ const permissionGroups = await PermissionGroups();
286
286
 
287
287
  // Transform our properties to API format
288
288
  const apiPayload = {
@@ -29,6 +29,7 @@ import type { VersionMetadata } from "./version-metadata.ts";
29
29
  import type { WorkerRef } from "./worker-ref.ts";
30
30
  import type { WorkerStub } from "./worker-stub.ts";
31
31
  import type { Worker } from "./worker.ts";
32
+ import type { WorkerLoader } from "./worker-loader.ts";
32
33
  import type { Workflow } from "./workflow.ts";
33
34
 
34
35
  export type Bindings = {
@@ -74,6 +75,7 @@ export type Binding =
74
75
  | WorkerStub
75
76
  | WorkerRef
76
77
  | WorkerEntrypoint
78
+ | WorkerLoader
77
79
  | Workflow
78
80
  | BrowserRendering
79
81
  | VersionMetadata
@@ -138,6 +140,7 @@ export type WorkerBindingSpec =
138
140
  | WorkerBindingVectorize
139
141
  | WorkerBindingVersionMetadata
140
142
  | WorkerBindingWasmModule
143
+ | WorkerBindingWorkerLoader
141
144
  | WorkerBindingWorkflow;
142
145
 
143
146
  /**
@@ -515,3 +518,13 @@ export interface WorkerBindingPipeline {
515
518
  /** Pipeline name */
516
519
  pipeline: string;
517
520
  }
521
+
522
+ /**
523
+ * Worker Loader binding type
524
+ */
525
+ export interface WorkerBindingWorkerLoader {
526
+ /** The name of the binding */
527
+ name: string;
528
+ /** Type identifier for Worker Loader binding */
529
+ type: "worker_loader";
530
+ }
@@ -4,6 +4,7 @@ import type {
4
4
  D1Database,
5
5
  DispatchNamespace,
6
6
  DurableObjectNamespace,
7
+ WorkerLoader,
7
8
  Fetcher,
8
9
  Hyperdrive,
9
10
  ImagesBinding,
@@ -43,6 +44,7 @@ import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts"
43
44
  import type { WorkerRef } from "./worker-ref.ts";
44
45
  import type { WorkerStub } from "./worker-stub.ts";
45
46
  import type { Worker as _Worker } from "./worker.ts";
47
+ import type { WorkerLoader as _WorkerLoader } from "./worker-loader.ts";
46
48
  import type { Workflow as _Workflow } from "./workflow.ts";
47
49
 
48
50
  type BoundWorker<
@@ -86,35 +88,37 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
86
88
  ? D1Database
87
89
  : T extends DispatchNamespace
88
90
  ? DispatchNamespace
89
- : T extends _VectorizeIndex
90
- ? VectorizeIndex
91
- : T extends _Queue<infer Body>
92
- ? Queue<Body>
93
- : T extends _AnalyticsEngineDataset
94
- ? AnalyticsEngineDataset
95
- : T extends _Pipeline<infer R>
96
- ? Pipeline<R>
97
- : T extends _RateLimit
98
- ? RateLimit
99
- : T extends string
100
- ? T
101
- : T extends BrowserRendering
102
- ? Fetcher
103
- : T extends _Ai<infer M>
104
- ? Ai<M>
105
- : T extends _Images
106
- ? ImagesBinding
107
- : T extends _VersionMetadata
108
- ? WorkerVersionMetadata
109
- : T extends Self
110
- ? Service
111
- : T extends Json<infer T>
112
- ? T
113
- : T extends _Container<
114
- infer Obj
115
- >
116
- ? DurableObjectNamespace<
117
- Obj &
118
- Rpc.DurableObjectBranded
119
- >
120
- : Service;
91
+ : T extends _WorkerLoader
92
+ ? WorkerLoader
93
+ : T extends _VectorizeIndex
94
+ ? VectorizeIndex
95
+ : T extends _Queue<infer Body>
96
+ ? Queue<Body>
97
+ : T extends _AnalyticsEngineDataset
98
+ ? AnalyticsEngineDataset
99
+ : T extends _Pipeline<infer R>
100
+ ? Pipeline<R>
101
+ : T extends _RateLimit
102
+ ? RateLimit
103
+ : T extends string
104
+ ? T
105
+ : T extends BrowserRendering
106
+ ? Fetcher
107
+ : T extends _Ai<infer M>
108
+ ? Ai<M>
109
+ : T extends _Images
110
+ ? ImagesBinding
111
+ : T extends _VersionMetadata
112
+ ? WorkerVersionMetadata
113
+ : T extends Self
114
+ ? Service
115
+ : T extends Json<infer T>
116
+ ? T
117
+ : T extends _Container<
118
+ infer Obj
119
+ >
120
+ ? DurableObjectNamespace<
121
+ Obj &
122
+ Rpc.DurableObjectBranded
123
+ >
124
+ : Service;
@@ -347,6 +347,13 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
347
347
  * Whether the bucket is running remotely
348
348
  */
349
349
  remote: boolean;
350
+
351
+ /**
352
+ * Whether the bucket has been deployed
353
+ *
354
+ * @internal
355
+ */
356
+ isDeployed?: boolean;
350
357
  };
351
358
 
352
359
  /**
@@ -587,15 +594,17 @@ const _R2Bucket = Resource(
587
594
  }
588
595
 
589
596
  const allowPublicAccess = props.allowPublicAccess === true;
597
+ const isLocal = this.scope.local && !props.dev?.remote;
590
598
  const dev = {
591
599
  id: this.output?.dev?.id ?? bucketName,
592
600
  remote: props.dev?.remote ?? false,
593
- };
601
+ isDeployed: this.output?.dev?.isDeployed || !isLocal,
602
+ } satisfies _R2Bucket["dev"];
594
603
  const adopt = props.adopt ?? this.scope.adopt;
595
604
 
596
- if (this.scope.local && !props.dev?.remote) {
605
+ if (isLocal) {
597
606
  return {
598
- name: this.output?.name ?? "",
607
+ name: bucketName,
599
608
  location: this.output?.location ?? "",
600
609
  creationDate: this.output?.creationDate ?? new Date(),
601
610
  jurisdiction: this.output?.jurisdiction ?? "default",
@@ -626,7 +635,7 @@ const _R2Bucket = Resource(
626
635
  return this.destroy();
627
636
  }
628
637
 
629
- if (this.phase === "create" || !this.output?.name) {
638
+ if (this.phase === "create" || !this.output?.dev?.isDeployed) {
630
639
  const bucket = await createBucket(api, bucketName, props).catch(
631
640
  async (err) => {
632
641
  if (
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { Scope } from "../../scope.ts";
4
4
  import { exists } from "../../util/exists.ts";
5
- import type { Assets } from "../assets.ts";
6
5
  import type { Bindings } from "../bindings.ts";
7
6
  import {
8
7
  extractStringAndSecretBindings,
@@ -16,7 +15,12 @@ import {
16
15
  } from "../website.ts";
17
16
 
18
17
  export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
19
- frontend: string;
18
+ /**
19
+ * The path to the frontend entrypoints that bun should bundle for deployment & serve in dev mode.
20
+ * These are usually html files. Glob patterns are supported.
21
+ * Typically set to index.html
22
+ */
23
+ frontend: string | string[];
20
24
  outDir?: string;
21
25
  }
22
26
 
@@ -26,15 +30,39 @@ export async function BunSPA<B extends Bindings>(
26
30
  id: string,
27
31
  props: BunSPAProps<B>,
28
32
  ): Promise<BunSPA<B> & { apiUrl: string }> {
29
- const frontendPath = path.resolve(props.frontend);
30
- if (!(await exists(frontendPath))) {
31
- throw new Error(`Frontend path ${frontendPath} does not exist`);
32
- }
33
+ const frontendPaths = Array.isArray(props.frontend)
34
+ ? props.frontend.map((p) => path.resolve(p))
35
+ : [path.resolve(props.frontend)];
36
+
37
+ // Helper to check if a path contains glob patterns
38
+ const isGlobPattern = (p: string) =>
39
+ p.includes("*") || p.includes("?") || p.includes("[") || p.includes("]");
40
+
41
+ // Only validate non-glob paths
42
+ const nonGlobPaths = frontendPaths.filter((p) => !isGlobPattern(p));
33
43
 
34
- const stats = await fs.stat(frontendPath);
35
- if (!stats.isFile()) {
36
- throw new Error(`Frontend path ${frontendPath} is not a file`);
44
+ if (nonGlobPaths.length > 0) {
45
+ const existsPromises = nonGlobPaths.map((p) => exists(p));
46
+ const existsResults = await Promise.all(existsPromises);
47
+ const missingPaths = nonGlobPaths.filter((_p, i) => !existsResults[i]);
48
+ if (missingPaths.length > 0) {
49
+ if (missingPaths.length === 1) {
50
+ throw new Error(`Frontend path ${missingPaths[0]} does not exist`);
51
+ }
52
+ throw new Error(`Frontend paths ${missingPaths.join(", ")} do not exist`);
53
+ }
54
+
55
+ const statsPromises = nonGlobPaths.map((p) => fs.stat(p));
56
+ const statsResults = await Promise.all(statsPromises);
57
+ const notFiles = nonGlobPaths.filter((_, i) => !statsResults[i].isFile());
58
+ if (notFiles.length > 0) {
59
+ if (notFiles.length === 1) {
60
+ throw new Error(`Frontend path ${notFiles[0]} is not a file`);
61
+ }
62
+ throw new Error(`Frontend paths ${notFiles.join(", ")} are not files`);
63
+ }
37
64
  }
65
+
38
66
  const outDir = path.resolve(props.outDir ?? "dist/client");
39
67
 
40
68
  if (props.assets) {
@@ -59,7 +87,7 @@ export async function BunSPA<B extends Bindings>(
59
87
  },
60
88
  build: spreadBuildProps(
61
89
  props,
62
- `bun build '${frontendPath}' --outdir ${outDir}`,
90
+ `bun build '${frontendPaths.join("' '")}' --outdir ${outDir}`,
63
91
  ),
64
92
  });
65
93
 
@@ -68,9 +96,12 @@ export async function BunSPA<B extends Bindings>(
68
96
  if (scope.local) {
69
97
  const cwd = props.cwd ?? process.cwd();
70
98
  await validateBunfigToml(cwd);
99
+ const frontendPathsRelativeToCwd = frontendPaths.map((p) =>
100
+ path.relative(cwd, p),
101
+ );
71
102
  const dev = spreadDevProps(
72
103
  props,
73
- `bun '${path.relative(cwd, frontendPath)}'`,
104
+ `bun '${frontendPathsRelativeToCwd.join("' '")}'`,
74
105
  );
75
106
  const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
76
107
  const env = {
@@ -4,4 +4,4 @@
4
4
  /**
5
5
  * The default Cloudflare Workers compatibility date, set based on the latest workerd release at the time of build.
6
6
  */
7
- export const DEFAULT_COMPATIBILITY_DATE = "2025-10-01";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-10-08";
@@ -113,7 +113,7 @@ export interface D1DatabaseProps extends CloudflareApiOptions {
113
113
  * Set when `Scope.local` is true to force update to the database even if it was already deployed live.
114
114
  * @internal
115
115
  */
116
- force?: boolean;
116
+ force?: boolean | number;
117
117
  };
118
118
  }
119
119
 
@@ -250,7 +250,7 @@ export async function D1Database(
250
250
  ...(props.dev ?? {}),
251
251
  // force local migrations to run even if the database was already deployed live
252
252
  // this property will oscillate from true to false depending on the dev vs live deployment
253
- force: Scope.current.local,
253
+ force: Scope.current.local ? Date.now() : false,
254
254
  },
255
255
  });
256
256
  }
@@ -36,6 +36,7 @@ export * from "./hyperdrive-ref.ts";
36
36
  export * from "./hyperdrive.ts";
37
37
  export * from "./images.ts";
38
38
  export * from "./kv-namespace.ts";
39
+ export * from "./logpush-job.ts";
39
40
  export * from "./next.ts";
40
41
  export * from "./nuxt/nuxt.ts";
41
42
  export * from "./orange.ts";
@@ -69,5 +70,6 @@ export * from "./worker-stub.ts";
69
70
  export * from "./worker-subdomain.ts";
70
71
  export * from "./worker.ts";
71
72
  export { Workflow } from "./workflow.ts";
73
+ export { WorkerLoader } from "./worker-loader.ts";
72
74
  export * from "./wrangler.json.ts";
73
75
  export * from "./zone.ts";