alchemy 0.57.2 → 0.59.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 (136) hide show
  1. package/bin/alchemy.js +1 -1
  2. package/lib/apply.js +3 -3
  3. package/lib/apply.js.map +1 -1
  4. package/lib/cloudflare/api-error.js +1 -1
  5. package/lib/cloudflare/api-error.js.map +1 -1
  6. package/lib/cloudflare/api-response.d.ts +19 -0
  7. package/lib/cloudflare/api-response.d.ts.map +1 -1
  8. package/lib/cloudflare/api-response.js.map +1 -1
  9. package/lib/cloudflare/api.d.ts.map +1 -1
  10. package/lib/cloudflare/api.js +10 -2
  11. package/lib/cloudflare/api.js.map +1 -1
  12. package/lib/cloudflare/assets.d.ts +1 -1
  13. package/lib/cloudflare/assets.d.ts.map +1 -1
  14. package/lib/cloudflare/bucket.d.ts +21 -3
  15. package/lib/cloudflare/bucket.d.ts.map +1 -1
  16. package/lib/cloudflare/bucket.js +83 -53
  17. package/lib/cloudflare/bucket.js.map +1 -1
  18. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  19. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  20. package/lib/cloudflare/container.d.ts +8 -0
  21. package/lib/cloudflare/container.d.ts.map +1 -1
  22. package/lib/cloudflare/container.js +7 -1
  23. package/lib/cloudflare/container.js.map +1 -1
  24. package/lib/cloudflare/custom-domain.d.ts +8 -0
  25. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  26. package/lib/cloudflare/custom-domain.js +11 -0
  27. package/lib/cloudflare/custom-domain.js.map +1 -1
  28. package/lib/cloudflare/d1-database.d.ts +15 -1
  29. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  30. package/lib/cloudflare/d1-database.js +24 -17
  31. package/lib/cloudflare/d1-database.js.map +1 -1
  32. package/lib/cloudflare/d1-import.d.ts.map +1 -1
  33. package/lib/cloudflare/d1-import.js.map +1 -1
  34. package/lib/cloudflare/hyperdrive.d.ts +37 -9
  35. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  36. package/lib/cloudflare/hyperdrive.js +91 -5
  37. package/lib/cloudflare/hyperdrive.js.map +1 -1
  38. package/lib/cloudflare/index.d.ts +1 -0
  39. package/lib/cloudflare/index.d.ts.map +1 -1
  40. package/lib/cloudflare/index.js +1 -0
  41. package/lib/cloudflare/index.js.map +1 -1
  42. package/lib/cloudflare/kv-namespace.d.ts +23 -4
  43. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  44. package/lib/cloudflare/kv-namespace.js +70 -58
  45. package/lib/cloudflare/kv-namespace.js.map +1 -1
  46. package/lib/cloudflare/miniflare/build-worker-options.d.ts +2 -0
  47. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  48. package/lib/cloudflare/miniflare/build-worker-options.js +9 -7
  49. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  50. package/lib/cloudflare/miniflare/delete.d.ts +21 -0
  51. package/lib/cloudflare/miniflare/delete.d.ts.map +1 -0
  52. package/lib/cloudflare/miniflare/delete.js +91 -0
  53. package/lib/cloudflare/miniflare/delete.js.map +1 -0
  54. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  55. package/lib/cloudflare/miniflare/miniflare-controller.js +26 -9
  56. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  57. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +2 -0
  58. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  59. package/lib/cloudflare/miniflare/remote-binding-proxy.js +3 -4
  60. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  61. package/lib/cloudflare/pipeline.d.ts +3 -0
  62. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  63. package/lib/cloudflare/pipeline.js +14 -1
  64. package/lib/cloudflare/pipeline.js.map +1 -1
  65. package/lib/cloudflare/queue-consumer.d.ts +8 -0
  66. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  67. package/lib/cloudflare/queue-consumer.js +15 -4
  68. package/lib/cloudflare/queue-consumer.js.map +1 -1
  69. package/lib/cloudflare/queue.d.ts +22 -4
  70. package/lib/cloudflare/queue.d.ts.map +1 -1
  71. package/lib/cloudflare/queue.js +32 -9
  72. package/lib/cloudflare/queue.js.map +1 -1
  73. package/lib/cloudflare/route.d.ts +8 -0
  74. package/lib/cloudflare/route.d.ts.map +1 -1
  75. package/lib/cloudflare/route.js +13 -0
  76. package/lib/cloudflare/route.js.map +1 -1
  77. package/lib/cloudflare/tunnel.d.ts +448 -0
  78. package/lib/cloudflare/tunnel.d.ts.map +1 -0
  79. package/lib/cloudflare/tunnel.js +467 -0
  80. package/lib/cloudflare/tunnel.js.map +1 -0
  81. package/lib/cloudflare/worker-subdomain.d.ts +8 -0
  82. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
  83. package/lib/cloudflare/worker-subdomain.js +9 -1
  84. package/lib/cloudflare/worker-subdomain.js.map +1 -1
  85. package/lib/cloudflare/worker.d.ts +7 -1
  86. package/lib/cloudflare/worker.d.ts.map +1 -1
  87. package/lib/cloudflare/worker.js +405 -375
  88. package/lib/cloudflare/worker.js.map +1 -1
  89. package/lib/cloudflare/wrangler.json.d.ts +1 -1
  90. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  91. package/lib/cloudflare/wrangler.json.js +24 -12
  92. package/lib/cloudflare/wrangler.json.js.map +1 -1
  93. package/lib/destroy.d.ts.map +1 -1
  94. package/lib/destroy.js +4 -8
  95. package/lib/destroy.js.map +1 -1
  96. package/lib/esbuild/bundle.d.ts +1 -1
  97. package/lib/secret.d.ts +18 -1
  98. package/lib/secret.d.ts.map +1 -1
  99. package/lib/secret.js +25 -0
  100. package/lib/secret.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/apply.ts +5 -6
  103. package/src/cloudflare/api-error.ts +2 -2
  104. package/src/cloudflare/api-response.ts +22 -0
  105. package/src/cloudflare/api.ts +13 -2
  106. package/src/cloudflare/bucket.ts +126 -68
  107. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  108. package/src/cloudflare/container.ts +18 -1
  109. package/src/cloudflare/custom-domain.ts +22 -0
  110. package/src/cloudflare/d1-database.ts +44 -19
  111. package/src/cloudflare/d1-import.ts +1 -14
  112. package/src/cloudflare/hyperdrive.ts +170 -14
  113. package/src/cloudflare/index.ts +1 -0
  114. package/src/cloudflare/kv-namespace.ts +117 -73
  115. package/src/cloudflare/miniflare/build-worker-options.ts +12 -8
  116. package/src/cloudflare/miniflare/delete.ts +136 -0
  117. package/src/cloudflare/miniflare/miniflare-controller.ts +26 -9
  118. package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -4
  119. package/src/cloudflare/pipeline.ts +17 -1
  120. package/src/cloudflare/queue-consumer.ts +27 -5
  121. package/src/cloudflare/queue.ts +62 -12
  122. package/src/cloudflare/route.ts +24 -0
  123. package/src/cloudflare/tunnel.ts +911 -0
  124. package/src/cloudflare/worker-subdomain.ts +29 -6
  125. package/src/cloudflare/worker.ts +484 -465
  126. package/src/cloudflare/wrangler.json.ts +34 -13
  127. package/src/destroy.ts +4 -8
  128. package/src/secret.ts +30 -1
  129. package/templates/astro/README.md +2 -0
  130. package/templates/nuxt/README.md +2 -0
  131. package/templates/react-router/README.md +2 -0
  132. package/templates/rwsdk/README.md +2 -0
  133. package/templates/sveltekit/README.md +2 -0
  134. package/templates/tanstack-start/README.md +2 -0
  135. package/templates/typescript/README.md +2 -0
  136. package/templates/vite/README.md +2 -0
@@ -1,5 +1,3 @@
1
- import type { Rpc } from "@cloudflare/workers-types";
2
- import { isDeepStrictEqual } from "node:util";
3
1
  import path from "pathe";
4
2
  import type { Context } from "../context.ts";
5
3
  import type { BundleProps } from "../esbuild/bundle.ts";
@@ -12,7 +10,6 @@ import { CloudflareApiError, handleApiError } from "./api-error.ts";
12
10
  import {
13
11
  type CloudflareApi,
14
12
  type CloudflareApiOptions,
15
- type InternalCloudflareApiOptions,
16
13
  createCloudflareApi,
17
14
  } from "./api.ts";
18
15
  import type { Assets } from "./assets.ts";
@@ -35,6 +32,7 @@ import {
35
32
  isDurableObjectNamespace,
36
33
  } from "./durable-object-namespace.ts";
37
34
  import { type EventSource, isQueueEventSource } from "./event-source.ts";
35
+ import { deleteMiniflareWorkerData } from "./miniflare/delete.ts";
38
36
  import {
39
37
  QueueConsumer,
40
38
  deleteQueueConsumer,
@@ -42,8 +40,12 @@ import {
42
40
  } from "./queue-consumer.ts";
43
41
  import { isQueue } from "./queue.ts";
44
42
  import { Route } from "./route.ts";
45
- import { uploadAssets } from "./worker-assets.ts";
46
- import { WorkerBundle, normalizeWorkerBundle } from "./worker-bundle.ts";
43
+ import { type AssetUploadResult, uploadAssets } from "./worker-assets.ts";
44
+ import {
45
+ WorkerBundle,
46
+ type WorkerBundleSource,
47
+ normalizeWorkerBundle,
48
+ } from "./worker-bundle.ts";
47
49
  import {
48
50
  type WorkerScriptMetadata,
49
51
  bumpMigrationTagVersion,
@@ -523,6 +525,14 @@ export type Worker<
523
525
  placement?: {
524
526
  mode: "smart";
525
527
  };
528
+
529
+ /**
530
+ * Whether the worker has a remote deployment
531
+ * @internal
532
+ */
533
+ dev?: {
534
+ hasRemote: boolean;
535
+ };
526
536
  };
527
537
 
528
538
  /**
@@ -698,55 +708,104 @@ const _Worker = Resource(
698
708
  this: Context<Worker<NoInfer<B>>>,
699
709
  id: string,
700
710
  props: WorkerProps<B>,
701
- ): Promise<Worker<B>> {
702
- if (props.projectRoot) {
703
- logger.warn("projectRoot is deprecated, use cwd instead");
704
- props.cwd = props.projectRoot;
705
- }
711
+ ) {
712
+ const options = (() => {
713
+ if (props.projectRoot) {
714
+ logger.warn("projectRoot is deprecated, use cwd instead");
715
+ props.cwd = props.projectRoot;
716
+ }
717
+ const name = props.name ?? id;
718
+ const cwd = path.resolve(props.cwd ?? process.cwd());
719
+ const compatibilityDate =
720
+ props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
721
+ const compatibilityFlags = unionCompatibilityFlags(
722
+ props.compatibility,
723
+ props.compatibilityFlags,
724
+ );
725
+ const dispatchNamespace =
726
+ typeof props.namespace === "string"
727
+ ? props.namespace
728
+ : props.namespace?.namespaceName;
729
+ const bundle = normalizeWorkerBundle({
730
+ id,
731
+ entrypoint: props.entrypoint,
732
+ script: props.script,
733
+ format: props.format,
734
+ noBundle: props.noBundle,
735
+ rules: "rules" in props ? props.rules : undefined,
736
+ bundle: props.bundle,
737
+ cwd,
738
+ compatibilityDate,
739
+ compatibilityFlags,
740
+ outdir: props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", name),
741
+ sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
742
+ });
706
743
 
707
- const cwd = path.resolve(props.cwd ?? process.cwd());
708
- const relativeCwd =
709
- cwd === process.cwd() ? undefined : path.relative(process.cwd(), cwd);
744
+ let assets: Assets | undefined;
745
+ const containers: Container[] = [];
746
+ const workflows: Workflow[] = [];
747
+ const durableObjects: DurableObjectNamespace[] = [];
748
+ for (const binding of Object.values(props.bindings ?? {})) {
749
+ if (typeof binding !== "object") continue;
710
750
 
711
- const workerName = props.name ?? id;
712
- const compatibilityDate =
713
- props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
714
- const compatibilityFlags = unionCompatibilityFlags(
715
- props.compatibility,
716
- props.compatibilityFlags,
717
- );
718
- const dispatchNamespace =
719
- typeof props.namespace === "string"
720
- ? props.namespace
721
- : props.namespace?.namespaceName;
751
+ if (binding.type === "assets") {
752
+ assets = binding;
753
+ } else if (binding.type === "container") {
754
+ containers.push(binding);
755
+ } else if (
756
+ binding.type === "workflow" &&
757
+ (!binding.scriptName || binding.scriptName === name)
758
+ ) {
759
+ workflows.push(binding);
760
+ } else if (
761
+ binding.type === "durable_object_namespace" &&
762
+ (!binding.scriptName || binding.scriptName === name)
763
+ ) {
764
+ durableObjects.push(binding);
765
+ }
766
+ }
722
767
 
723
- const bundleSourceResult = normalizeWorkerBundle({
724
- id,
725
- entrypoint: props.entrypoint,
726
- script: props.script,
727
- format: props.format,
728
- noBundle: props.noBundle,
729
- rules: "rules" in props ? props.rules : undefined,
730
- bundle: props.bundle,
731
- cwd,
732
- compatibilityDate,
733
- compatibilityFlags,
734
- outdir:
735
- props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", workerName),
736
- sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
737
- });
768
+ return {
769
+ name,
770
+ cwd,
771
+ compatibilityDate,
772
+ compatibilityFlags,
773
+ dispatchNamespace,
774
+ bundle,
775
+ assets,
776
+ containers,
777
+ workflows,
778
+ durableObjects,
779
+ };
780
+ })();
781
+ if (this.phase === "delete") {
782
+ if (options.bundle.isOk()) {
783
+ await options.bundle.value.delete?.();
784
+ }
785
+ await deleteMiniflareWorkerData(options.name, {
786
+ durableObjects: options.durableObjects,
787
+ workflows: options.workflows,
788
+ });
789
+ if (!props.version && this.output?.dev?.hasRemote !== false) {
790
+ const api = await createCloudflareApi(props);
791
+ await deleteQueueConsumers(api, options.name);
792
+ await deleteWorker(api, {
793
+ scriptName: options.name,
794
+ dispatchNamespace: options.dispatchNamespace,
795
+ });
796
+ }
797
+ return this.destroy();
798
+ }
738
799
 
739
- // run locally if
740
- const local = this.scope.local && !props.dev?.remote;
741
- const watch = this.scope.watch;
800
+ if (options.bundle.isErr()) {
801
+ throw new Error(options.bundle.error);
802
+ }
803
+ const bundle = options.bundle.value;
804
+ const api = await createCloudflareApi(props);
742
805
 
743
- if (local) {
806
+ if (this.scope.local && !props.dev?.remote) {
744
807
  let url: string | undefined;
745
- if (bundleSourceResult.isErr()) {
746
- throw bundleSourceResult.error;
747
- }
748
-
749
- if (props.dev && "url" in props.dev) {
808
+ if (props.dev?.url) {
750
809
  url = props.dev.url;
751
810
  } else {
752
811
  const { MiniflareController } = await import(
@@ -754,75 +813,87 @@ const _Worker = Resource(
754
813
  );
755
814
  const controller = MiniflareController.singleton;
756
815
  url = await controller.add({
816
+ api,
757
817
  id,
758
- name: workerName,
759
- compatibilityDate,
760
- compatibilityFlags,
818
+ name: options.name,
819
+ compatibilityDate: options.compatibilityDate,
820
+ compatibilityFlags: options.compatibilityFlags,
761
821
  bindings: props.bindings,
762
822
  eventSources: props.eventSources,
763
823
  assets: props.assets,
764
- bundle: bundleSourceResult.value,
765
- port: props.dev?.port ?? undefined,
824
+ bundle,
825
+ port: (props.dev as { port?: number } | undefined)?.port,
766
826
  });
767
827
  this.onCleanup(() => controller.dispose());
768
828
  }
769
-
829
+ await provisionResources(props, {
830
+ name: options.name,
831
+ local: true,
832
+ dispatchNamespace: options.dispatchNamespace,
833
+ containers: options.containers,
834
+ });
770
835
  return this({
836
+ ...props,
771
837
  type: "service",
772
838
  id,
773
- entrypoint: props.entrypoint,
774
- name: workerName,
775
- cwd: relativeCwd,
776
- compatibilityDate,
777
- compatibilityFlags,
778
- format: props.format || "esm", // Include format in the output
779
- bindings: normalizeExportBindings(workerName, props.bindings),
780
- env: props.env,
781
- observability: props.observability,
839
+ name: options.name,
840
+ cwd: path.relative(process.cwd(), options.cwd),
841
+ compatibilityDate: options.compatibilityDate,
842
+ compatibilityFlags: options.compatibilityFlags,
843
+ format: props.format || "esm",
844
+ bindings: normalizeExportBindings(options.name, props.bindings),
782
845
  createdAt: this.output?.createdAt ?? Date.now(),
783
846
  updatedAt: Date.now(),
784
- eventSources: props.eventSources,
785
847
  url,
786
- dev: props.dev,
787
- // Include assets configuration in the output
788
- assets: props.assets,
789
- // Include cron triggers in the output
790
- crons: props.crons,
791
- // Include placement configuration in the output
792
- placement: props.placement,
793
- // Include limits configuration in the output
794
- limits: props.limits,
795
- // phantom property
848
+ routes: [],
849
+ domains: [],
850
+ dev: {
851
+ hasRemote: this.output?.dev?.hasRemote ?? false,
852
+ },
796
853
  Env: undefined!,
797
854
  } as unknown as Worker<B>);
798
855
  }
799
856
 
800
- const api = await createCloudflareApi(props);
801
-
802
- if (this.phase === "delete") {
803
- if (bundleSourceResult.isOk()) {
804
- await bundleSourceResult.value.delete?.();
805
- }
806
- if (!props.version) {
807
- await deleteQueueConsumers(api, workerName);
808
- await deleteWorker(api, {
809
- scriptName: workerName,
810
- dispatchNamespace,
811
- });
857
+ if (this.phase === "create" || this.output.dev?.hasRemote === false) {
858
+ if (props.version) {
859
+ // When version is specified, we adopt existing workers or create them if they don't exist
860
+ if (!(await workerExists(api, options))) {
861
+ // Create the base worker first if it doesn't exist
862
+ await putWorker(api, {
863
+ ...props,
864
+ version: undefined,
865
+ workerName: options.name,
866
+ scriptBundle: await bundle.create(),
867
+ dispatchNamespace: options.dispatchNamespace,
868
+ compatibilityDate: options.compatibilityDate,
869
+ compatibilityFlags: options.compatibilityFlags,
870
+ assetUploadResult: options.assets
871
+ ? await uploadAssets(api, {
872
+ workerName: options.name,
873
+ assets: options.assets,
874
+ assetConfig: props.assets,
875
+ namespace: options.dispatchNamespace,
876
+ })
877
+ : undefined,
878
+ });
879
+ }
880
+ // We always "adopt" when publishing versions
881
+ } else if (!props.adopt) {
882
+ await assertWorkerDoesNotExist(api, options.name);
883
+ } else if (
884
+ props.adopt &&
885
+ !options.dispatchNamespace &&
886
+ props.url === false
887
+ ) {
888
+ // explicitly disable the workers.dev subdomain
889
+ await disableWorkerSubdomain(api, options.name);
812
890
  }
813
- return this.destroy();
814
- } else if (bundleSourceResult.isErr()) {
815
- throw new Error(bundleSourceResult.error);
816
- }
817
-
818
- const bundleSource = bundleSourceResult.value;
819
-
820
- if (this.phase === "update") {
891
+ } else if (this.phase === "update") {
821
892
  const oldName = this.output.name ?? this.output.id;
822
- const newName = workerName;
893
+ const newName = options.name;
823
894
 
824
895
  if (oldName && oldName !== newName) {
825
- if (dispatchNamespace) {
896
+ if (options.dispatchNamespace) {
826
897
  this.replace(true);
827
898
  } else {
828
899
  const renameResponse = await api.patch(
@@ -837,253 +908,105 @@ const _Worker = Resource(
837
908
  }
838
909
  }
839
910
 
840
- let assetsBinding: Assets | undefined;
841
- const workflowsBindings: Workflow[] = [];
842
- const containersBindings: Container[] = [];
843
-
844
- for (const binding of Object.values(props.bindings ?? {})) {
845
- if (typeof binding === "object") {
846
- if (binding.type === "assets") {
847
- assetsBinding = binding;
848
- } else if (binding.type === "workflow") {
849
- workflowsBindings.push(binding);
850
- } else if (binding.type === "container") {
851
- containersBindings.push(binding);
852
- }
853
- }
854
- }
855
-
856
- const putWorkerWithAssets = async (
857
- props: WorkerProps<B>,
858
- scriptBundle: WorkerBundle,
859
- ) => {
860
- const assetUploadResult = assetsBinding
861
- ? await uploadAssets(api, {
862
- workerName,
863
- assets: assetsBinding,
864
- assetConfig: props.assets,
865
- namespace: dispatchNamespace,
866
- })
867
- : undefined;
911
+ const assets = options.assets
912
+ ? await uploadAssets(api, {
913
+ workerName: options.name,
914
+ assets: options.assets,
915
+ assetConfig: props.assets,
916
+ namespace: options.dispatchNamespace,
917
+ })
918
+ : undefined;
919
+ let result: PutWorkerResult;
868
920
 
869
- // Deploy worker (either as version or live worker)
870
- return await putWorker(api, {
871
- ...props,
872
- workerName,
873
- scriptBundle,
874
- dispatchNamespace,
921
+ if (this.scope.watch) {
922
+ const controller = new AbortController();
923
+ result = await watchWorker(api, props, {
924
+ id,
925
+ name: options.name,
926
+ dispatchNamespace: options.dispatchNamespace,
927
+ bundle,
928
+ compatibilityDate: options.compatibilityDate,
929
+ compatibilityFlags: options.compatibilityFlags,
875
930
  version: props.version,
876
- compatibilityDate,
877
- compatibilityFlags,
878
- assetUploadResult,
931
+ assets,
932
+ controller,
879
933
  });
880
- };
881
-
882
- if (this.phase === "create") {
883
- if (props.version) {
884
- // When version is specified, we adopt existing workers or create them if they don't exist
885
- if (!(await workerExists(api, workerName))) {
886
- // Create the base worker first if it doesn't exist
887
- const baseWorkerProps = { ...props, version: undefined };
888
- await putWorkerWithAssets(
889
- baseWorkerProps,
890
- await bundleSource.create(),
891
- );
892
- }
893
- // We always "adopt" when publishing versions
894
- } else if (!props.adopt) {
895
- await assertWorkerDoesNotExist(api, workerName);
896
- }
897
- }
898
-
899
- let putWorkerResult: PutWorkerResult;
900
- if (watch) {
901
- const controller = new AbortController();
902
- const promise = new DeferredPromise<PutWorkerResult>();
903
- const runWatch = async () => {
904
- for await (const bundle of bundleSource.watch(controller.signal)) {
905
- if (promise.status === "pending") {
906
- await putWorkerWithAssets(props, bundle)
907
- .then((result) => promise.resolve(result))
908
- .catch((error) => {
909
- controller.abort();
910
- promise.reject(error);
911
- });
912
- continue;
913
- }
914
-
915
- logger.task("", {
916
- message: "reload",
917
- status: "success",
918
- resource: id,
919
- prefix: "build",
920
- prefixColor: "cyanBright",
921
- });
922
- await putWorker(api, {
923
- ...props,
924
- workerName,
925
- scriptBundle: bundle,
926
- dispatchNamespace,
927
- version: props.version,
928
- compatibilityDate,
929
- compatibilityFlags,
930
- assetUploadResult: assetsBinding
931
- ? {
932
- keepAssets: true,
933
- assetConfig: props.assets,
934
- }
935
- : undefined,
936
- unstable_cacheWorkerSettings: true,
937
- });
938
- logger.task("", {
939
- message: "updated",
940
- status: "success",
941
- resource: id,
942
- prefix: "build",
943
- prefixColor: "greenBright",
944
- });
945
- }
946
- };
947
- void runWatch(); // this is not awaited because it's an ongoing process
948
934
  this.onCleanup(() => controller.abort());
949
- putWorkerResult = await promise.value;
950
- const tail = await createTail(api, id, workerName).catch((error) => {
951
- logger.error(`Failed to create tail for ${workerName}`, error);
935
+ const tail = await createTail(api, id, options.name).catch((error) => {
936
+ logger.error(`Failed to create tail for ${options.name}`, error);
952
937
  });
953
938
  this.onCleanup(() => tail?.close());
954
939
  } else {
955
- const scriptBundle = await bundleSource.create();
956
- putWorkerResult = await putWorkerWithAssets(props, scriptBundle);
957
- }
958
-
959
- const tasks: Promise<unknown>[] = [];
960
-
961
- for (const workflow of workflowsBindings) {
962
- if (
963
- workflow.scriptName === undefined ||
964
- workflow.scriptName === workerName
965
- ) {
966
- tasks.push(
967
- upsertWorkflow(api, {
968
- workflowName: workflow.workflowName,
969
- className: workflow.className,
970
- scriptName: workflow.scriptName ?? workerName,
971
- }),
972
- );
973
- }
974
- }
975
-
976
- if (containersBindings.length > 0) {
977
- tasks.push(
978
- getVersionMetadata(api, workerName, putWorkerResult.deployment_id).then(
979
- (versionMetadata) =>
980
- provisionContainers(api, {
981
- scriptName: workerName,
982
- containers: containersBindings,
983
- bindings: versionMetadata.resources.bindings,
984
- }),
985
- ),
986
- );
940
+ result = await putWorker(api, {
941
+ ...props,
942
+ workerName: options.name,
943
+ scriptBundle: await bundle.create(),
944
+ dispatchNamespace: options.dispatchNamespace,
945
+ compatibilityDate: options.compatibilityDate,
946
+ compatibilityFlags: options.compatibilityFlags,
947
+ assetUploadResult: assets,
948
+ });
987
949
  }
988
950
 
989
- if (!isDeepStrictEqual(props.crons, this.output?.crons)) {
990
- tasks.push(
991
- api.put(
992
- `/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`,
993
- props.crons?.map((cron) => ({ cron })) ?? [],
994
- ),
951
+ if (props.crons) {
952
+ await api.put(
953
+ `/accounts/${api.accountId}/workers/scripts/${options.name}/schedules`,
954
+ props.crons?.map((cron) => ({ cron })) ?? [],
995
955
  );
996
956
  }
997
-
998
- tasks.push(
999
- provisionEventSources(api, {
1000
- scriptName: workerName,
1001
- eventSources: props.eventSources,
1002
- adopt: props.adopt,
1003
- }),
957
+ await Promise.all(
958
+ options.workflows.map((workflow) =>
959
+ upsertWorkflow(api, {
960
+ workflowName: workflow.workflowName,
961
+ className: workflow.className,
962
+ scriptName: workflow.scriptName ?? options.name,
963
+ }),
964
+ ),
1004
965
  );
1005
966
 
1006
- const [domains, routes, subdomain] = await Promise.all([
1007
- // TODO: can you provision domains and routes in parallel, or is there a dependency?
1008
- provisionDomains(api, {
1009
- scriptName: workerName,
1010
- adopt: props.adopt,
1011
- domains: props.domains,
1012
- }),
1013
- provisionRoutes(api, {
1014
- scriptName: workerName,
1015
- adopt: props.adopt,
1016
- routes: props.routes,
1017
- }),
1018
- provisionSubdomain(api, {
1019
- scriptName: workerName,
1020
- enable: props.url ?? dispatchNamespace === undefined,
1021
- previewVersionId:
1022
- props.version && putWorkerResult.metadata.has_preview
1023
- ? putWorkerResult.id
1024
- : undefined,
1025
- retain: !!props.version,
1026
- forceDelete:
1027
- this.phase === "create" && !!props.adopt && props.url === false,
1028
- }),
1029
- ...tasks,
1030
- ]);
1031
-
1032
- const now = Date.now();
967
+ const { domains, routes, subdomain } = await provisionResources(props, {
968
+ name: options.name,
969
+ local: false,
970
+ dispatchNamespace: options.dispatchNamespace,
971
+ containers: options.containers,
972
+ result,
973
+ api,
974
+ });
1033
975
 
1034
- // Construct the output
976
+ const now = new Date();
1035
977
  return this({
1036
978
  ...props,
1037
979
  type: "service",
1038
980
  id,
1039
981
  entrypoint: props.entrypoint,
1040
- name: workerName,
1041
- cwd: relativeCwd,
1042
- compatibilityDate,
1043
- compatibilityFlags,
1044
- format: props.format || "esm", // Include format in the output
1045
- bindings: normalizeExportBindings(workerName, props.bindings),
982
+ name: options.name,
983
+ cwd: path.relative(process.cwd(), options.cwd),
984
+ compatibilityDate: options.compatibilityDate,
985
+ compatibilityFlags: options.compatibilityFlags,
986
+ format: props.format || "esm",
987
+ bindings: normalizeExportBindings(options.name, props.bindings),
1046
988
  env: props.env,
1047
989
  observability: props.observability,
1048
990
  createdAt: this.output?.createdAt ?? now,
1049
991
  updatedAt: now,
1050
992
  eventSources: props.eventSources,
1051
993
  url: subdomain?.url,
1052
- dev: props.dev,
1053
- // Include assets configuration in the output
1054
994
  assets: props.assets,
1055
- // Include cron triggers in the output
1056
995
  crons: props.crons,
1057
- // Include the created routes in the output
1058
996
  routes,
1059
- // Include the created domains in the output
1060
997
  domains,
1061
- // Include the dispatch namespace in the output
1062
998
  namespace: props.namespace,
1063
- // Include version information in the output
1064
999
  version: props.version,
1065
- // Include placement configuration in the output
1066
1000
  placement: props.placement,
1067
- // Include limits configuration in the output
1068
1001
  limits: props.limits,
1069
- // phantom property
1070
1002
  Env: undefined!,
1003
+ dev: {
1004
+ hasRemote: true,
1005
+ },
1071
1006
  } as unknown as Worker<B>);
1072
1007
  },
1073
1008
  );
1074
1009
 
1075
- const assertUnique = <T, Key extends keyof T>(
1076
- inputs: T[],
1077
- key: Key,
1078
- message: string,
1079
- ) => {
1080
- const ids = inputs.map((input) => input[key]);
1081
- const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1082
- if (duplicates.length > 0) {
1083
- throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
1084
- }
1085
- };
1086
-
1087
1010
  const normalizeExportBindings = (
1088
1011
  scriptName: string,
1089
1012
  bindings: Bindings = {},
@@ -1108,188 +1031,276 @@ const normalizeExportBindings = (
1108
1031
  );
1109
1032
  };
1110
1033
 
1111
- const normalizeApiOptions = (
1112
- api: CloudflareApi,
1113
- ): InternalCloudflareApiOptions => ({
1114
- accountId: api.accountId,
1115
- baseUrl: api.baseUrl,
1116
- ...api.authOptions,
1117
- });
1118
-
1119
- async function provisionContainers(
1120
- api: CloudflareApi,
1121
- props: {
1122
- scriptName: string;
1123
- containers?: Container[];
1124
- bindings: WorkerBindingSpec[];
1125
- },
1126
- ): Promise<ContainerApplication[] | undefined> {
1127
- if (!props.containers?.length) {
1128
- return;
1034
+ const assertUnique = <T, Key extends keyof T>(
1035
+ inputs: T[],
1036
+ key: Key,
1037
+ message: string,
1038
+ ) => {
1039
+ const ids = inputs.map((input) => input[key]);
1040
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1041
+ if (duplicates.length > 0) {
1042
+ throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
1129
1043
  }
1130
- return await Promise.all(
1131
- props.containers.map((container) => {
1132
- const namespaceId = props.bindings.find(
1133
- (binding): binding is WorkerBindingDurableObjectNamespace =>
1134
- binding.type === "durable_object_namespace" &&
1135
- binding.class_name === container.className,
1136
- )?.namespace_id;
1137
- if (!namespaceId) {
1138
- throw new Error(`Namespace ID not found for container ${container.id}`);
1044
+ };
1045
+
1046
+ async function provisionResources<B extends Bindings>(
1047
+ props: WorkerProps<B>,
1048
+ options:
1049
+ | {
1050
+ name: string;
1051
+ local: true;
1052
+ dispatchNamespace: string | undefined;
1053
+ containers: Container[] | undefined;
1054
+ result?: undefined;
1055
+ api?: undefined;
1139
1056
  }
1140
- return ContainerApplication(container.id, {
1141
- image: container.image,
1142
- name: container.name,
1143
- instanceType: container.instanceType,
1144
- observability: container.observability,
1145
- durableObjects: {
1146
- namespaceId,
1147
- },
1148
- schedulingPolicy: container.schedulingPolicy,
1149
- adopt: container.adopt,
1150
- ...normalizeApiOptions(api),
1151
- });
1057
+ | {
1058
+ name: string;
1059
+ local: false;
1060
+ dispatchNamespace: string | undefined;
1061
+ containers: Container[] | undefined;
1062
+ result: PutWorkerResult;
1063
+ api: CloudflareApi;
1064
+ },
1065
+ ) {
1066
+ let metadataPromise: ReturnType<typeof getVersionMetadata> | undefined;
1067
+
1068
+ const input = {
1069
+ containers: options.containers,
1070
+ domains: props.domains?.map((domain) => {
1071
+ if (typeof domain === "string") {
1072
+ return {
1073
+ name: domain,
1074
+ zoneId: undefined,
1075
+ adopt: props.adopt,
1076
+ };
1077
+ }
1078
+ return {
1079
+ name: domain.domainName,
1080
+ zoneId: domain.zoneId,
1081
+ adopt: domain.adopt ?? props.adopt,
1082
+ };
1152
1083
  }),
1153
- );
1154
- }
1155
-
1156
- async function provisionEventSources(
1157
- api: CloudflareApi,
1158
- props: {
1159
- scriptName: string;
1160
- eventSources?: EventSource[];
1161
- adopt?: boolean;
1162
- },
1163
- ): Promise<QueueConsumer[] | undefined> {
1164
- if (!props.eventSources?.length) {
1165
- return;
1166
- }
1167
- return await Promise.all(
1168
- props.eventSources.map((eventSource) => {
1084
+ eventSources: props.eventSources?.map((eventSource) => {
1169
1085
  if (isQueue(eventSource)) {
1170
- return QueueConsumer(`${eventSource.id}-consumer`, {
1086
+ return {
1171
1087
  queue: eventSource,
1172
- scriptName: props.scriptName,
1173
1088
  settings: eventSource.dlq
1174
1089
  ? { deadLetterQueue: eventSource.dlq }
1175
1090
  : undefined,
1176
- adopt: props.adopt,
1177
- ...normalizeApiOptions(api),
1178
- });
1091
+ };
1179
1092
  }
1180
1093
  if (isQueueEventSource(eventSource)) {
1181
- return QueueConsumer(`${eventSource.queue.id}-consumer`, {
1094
+ return {
1182
1095
  queue: eventSource.queue,
1183
- scriptName: props.scriptName,
1184
1096
  settings: eventSource.settings,
1185
- adopt: props.adopt,
1186
- ...normalizeApiOptions(api),
1187
- });
1097
+ };
1188
1098
  }
1189
1099
  throw new Error(`Unsupported event source: ${eventSource}`);
1190
1100
  }),
1191
- );
1192
- }
1193
-
1194
- async function provisionDomains(
1195
- api: CloudflareApi,
1196
- props: {
1197
- scriptName: string;
1198
- adopt?: boolean;
1199
- domains?: WorkerProps["domains"];
1200
- },
1201
- ): Promise<CustomDomain[] | undefined> {
1202
- if (!props.domains?.length) {
1203
- return;
1204
- }
1205
- const domains = props.domains.map((domain) => {
1206
- if (typeof domain === "string") {
1101
+ routes: props.routes?.map((route) => {
1102
+ if (typeof route === "string") {
1103
+ return {
1104
+ pattern: route,
1105
+ adopt: props.adopt,
1106
+ };
1107
+ }
1207
1108
  return {
1208
- name: domain,
1209
- zoneId: undefined,
1210
- adopt: props.adopt,
1109
+ pattern: route.pattern,
1110
+ zoneId: route.zoneId,
1111
+ adopt: route.adopt ?? props.adopt,
1211
1112
  };
1212
- }
1213
- return {
1214
- name: domain.domainName,
1215
- zoneId: domain.zoneId,
1216
- adopt: domain.adopt ?? props.adopt,
1217
- };
1218
- });
1219
- assertUnique(domains, "name", "Custom Domain");
1220
- return await Promise.all(
1221
- domains.map(async (domain) => {
1222
- return await CustomDomain(domain.name, {
1223
- workerName: props.scriptName,
1224
- name: domain.name,
1225
- zoneId: domain.zoneId,
1226
- adopt: domain.adopt,
1227
- ...normalizeApiOptions(api),
1228
- });
1229
1113
  }),
1230
- );
1231
- }
1114
+ api: {
1115
+ accountId: props.accountId,
1116
+ apiKey: props.apiKey,
1117
+ apiToken: props.apiToken,
1118
+ email: props.email,
1119
+ baseUrl: props.baseUrl,
1120
+ } satisfies CloudflareApiOptions,
1121
+ };
1232
1122
 
1233
- async function provisionRoutes(
1234
- api: CloudflareApi,
1235
- props: {
1236
- scriptName: string;
1237
- adopt?: boolean;
1238
- routes?: WorkerProps["routes"];
1239
- },
1240
- ): Promise<Route[] | undefined> {
1241
- if (!props.routes?.length) {
1242
- return;
1123
+ if (input.routes) {
1124
+ assertUnique(input.routes, "pattern", "Route");
1243
1125
  }
1244
- const routes = props.routes.map((route) => {
1245
- if (typeof route === "string") {
1246
- return {
1247
- pattern: route,
1248
- adopt: props.adopt,
1249
- };
1126
+ if (input.domains) {
1127
+ assertUnique(input.domains, "name", "Custom Domain");
1128
+ }
1129
+
1130
+ const [containers, domains, eventSources, routes, subdomain] =
1131
+ await Promise.all([
1132
+ input.containers
1133
+ ? Promise.all(
1134
+ input.containers.map(async (container) => {
1135
+ return await ContainerApplication(container.id, {
1136
+ image: container.image,
1137
+ name: container.name,
1138
+ instanceType: container.instanceType,
1139
+ observability: container.observability,
1140
+ durableObjects: {
1141
+ namespaceId: await getContainerNamespaceId(container),
1142
+ },
1143
+ schedulingPolicy: container.schedulingPolicy,
1144
+ adopt: container.adopt,
1145
+ dev: options.local,
1146
+ ...input.api,
1147
+ });
1148
+ }),
1149
+ )
1150
+ : undefined,
1151
+ input.domains
1152
+ ? Promise.all(
1153
+ input.domains.map(async (domain) => {
1154
+ return await CustomDomain(domain.name, {
1155
+ name: domain.name,
1156
+ zoneId: domain.zoneId,
1157
+ adopt: domain.adopt,
1158
+ workerName: options.name,
1159
+ dev: options.local,
1160
+ ...input.api,
1161
+ });
1162
+ }),
1163
+ )
1164
+ : undefined,
1165
+ input.eventSources
1166
+ ? Promise.all(
1167
+ input.eventSources.map(async (eventSource) => {
1168
+ return await QueueConsumer(`${eventSource.queue.id}-consumer`, {
1169
+ queue: eventSource.queue,
1170
+ scriptName: options.name,
1171
+ settings: eventSource.settings,
1172
+ adopt: props.adopt,
1173
+ dev: options.local,
1174
+ ...input.api,
1175
+ });
1176
+ }),
1177
+ )
1178
+ : undefined,
1179
+ input.routes
1180
+ ? Promise.all(
1181
+ input.routes.map(async (route) => {
1182
+ return await Route(route.pattern, {
1183
+ pattern: route.pattern,
1184
+ script: options.name,
1185
+ zoneId: route.zoneId,
1186
+ adopt: route.adopt,
1187
+ dev: options.local,
1188
+ ...input.api,
1189
+ });
1190
+ }),
1191
+ )
1192
+ : undefined,
1193
+ (props.url ?? !options.dispatchNamespace)
1194
+ ? WorkerSubdomain("url", {
1195
+ scriptName: options.name,
1196
+ previewVersionId: props.version ? options.result?.id : undefined,
1197
+ retain: !!props.version,
1198
+ dev: options.local,
1199
+ ...input.api,
1200
+ })
1201
+ : undefined,
1202
+ ]);
1203
+
1204
+ return { containers, domains, routes, eventSources, subdomain };
1205
+
1206
+ async function getContainerNamespaceId(container: Container) {
1207
+ if (options.local) {
1208
+ return container.id;
1250
1209
  }
1251
- return {
1252
- pattern: route.pattern,
1253
- zoneId: route.zoneId,
1254
- adopt: route.adopt ?? props.adopt,
1255
- };
1256
- });
1257
- assertUnique(routes, "pattern", "Route");
1258
- return await Promise.all(
1259
- routes.map(async (route) => {
1260
- return await Route(route.pattern, {
1261
- pattern: route.pattern,
1262
- script: props.scriptName,
1263
- zoneId: route.zoneId,
1264
- adopt: route.adopt,
1265
- ...normalizeApiOptions(api),
1266
- });
1267
- }),
1268
- );
1210
+ metadataPromise ??= getVersionMetadata(
1211
+ options.api,
1212
+ options.name,
1213
+ options.result.deployment_id,
1214
+ );
1215
+ const metadata = await metadataPromise;
1216
+ const binding = metadata.resources.bindings.find(
1217
+ (binding): binding is WorkerBindingDurableObjectNamespace =>
1218
+ binding.type === "durable_object_namespace" &&
1219
+ binding.class_name === container.className,
1220
+ );
1221
+ if (!binding?.namespace_id) {
1222
+ throw new Error(
1223
+ `Container ${container.name} not found in version ${options.result.deployment_id}`,
1224
+ );
1225
+ }
1226
+ return binding.namespace_id;
1227
+ }
1269
1228
  }
1270
1229
 
1271
- async function provisionSubdomain(
1230
+ const watchWorker = async <B extends Bindings>(
1272
1231
  api: CloudflareApi,
1273
- props: {
1274
- scriptName: string;
1275
- enable: boolean;
1276
- previewVersionId: string | undefined;
1277
- retain: boolean;
1278
- forceDelete: boolean;
1232
+ props: WorkerProps<B>,
1233
+ input: {
1234
+ id: string;
1235
+ name: string;
1236
+ dispatchNamespace: string | undefined;
1237
+ bundle: WorkerBundleSource;
1238
+ compatibilityDate: string;
1239
+ compatibilityFlags: string[];
1240
+ version: string | undefined;
1241
+ assets: AssetUploadResult | undefined;
1242
+ controller: AbortController;
1279
1243
  },
1280
- ): Promise<WorkerSubdomain | undefined> {
1281
- if (props.enable) {
1282
- return await WorkerSubdomain("url", {
1283
- scriptName: props.scriptName,
1284
- previewVersionId: props.previewVersionId,
1285
- retain: props.retain,
1286
- ...normalizeApiOptions(api),
1287
- });
1288
- }
1289
- if (props.forceDelete) {
1290
- await disableWorkerSubdomain(api, props.scriptName);
1291
- }
1292
- }
1244
+ ) => {
1245
+ const promise = new DeferredPromise<PutWorkerResult>();
1246
+ const run = async () => {
1247
+ for await (const bundle of input.bundle.watch(input.controller.signal)) {
1248
+ if (promise.status === "pending") {
1249
+ try {
1250
+ const result = await putWorker(api, {
1251
+ ...props,
1252
+ workerName: input.name,
1253
+ scriptBundle: bundle,
1254
+ dispatchNamespace: input.dispatchNamespace,
1255
+ compatibilityDate: input.compatibilityDate,
1256
+ compatibilityFlags: input.compatibilityFlags,
1257
+ assetUploadResult: input.assets,
1258
+ });
1259
+ promise.resolve(result);
1260
+ } catch (error) {
1261
+ input.controller.abort();
1262
+ promise.reject(error);
1263
+ }
1264
+ continue;
1265
+ }
1266
+
1267
+ logger.task("", {
1268
+ message: "reload",
1269
+ status: "success",
1270
+ resource: input.id,
1271
+ prefix: "build",
1272
+ prefixColor: "cyanBright",
1273
+ });
1274
+
1275
+ await putWorker(api, {
1276
+ ...props,
1277
+ workerName: input.name,
1278
+ scriptBundle: bundle,
1279
+ dispatchNamespace: input.dispatchNamespace,
1280
+ version: input.version,
1281
+ compatibilityDate: input.compatibilityDate,
1282
+ compatibilityFlags: input.compatibilityFlags,
1283
+ assetUploadResult: input.assets
1284
+ ? {
1285
+ keepAssets: true,
1286
+ assetConfig: props.assets,
1287
+ }
1288
+ : undefined,
1289
+ unstable_cacheWorkerSettings: true,
1290
+ });
1291
+
1292
+ logger.task("", {
1293
+ message: "updated",
1294
+ status: "success",
1295
+ resource: input.id,
1296
+ prefix: "build",
1297
+ prefixColor: "greenBright",
1298
+ });
1299
+ }
1300
+ };
1301
+ void run();
1302
+ return await promise.value;
1303
+ };
1293
1304
 
1294
1305
  type PutWorkerOptions = Omit<WorkerProps, "entrypoint"> & {
1295
1306
  dispatchNamespace?: string;
@@ -1462,12 +1473,20 @@ export async function putWorker(
1462
1473
  );
1463
1474
  }
1464
1475
 
1465
- async function workerExists(api: CloudflareApi, scriptName: string) {
1476
+ const workerExists = async (
1477
+ api: CloudflareApi,
1478
+ options: {
1479
+ name: string;
1480
+ dispatchNamespace: string | undefined;
1481
+ },
1482
+ ) => {
1466
1483
  const res = await api.get(
1467
- `/accounts/${api.accountId}/workers/scripts/${scriptName}`,
1484
+ options.dispatchNamespace
1485
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${options.dispatchNamespace}/scripts/${options.name}`
1486
+ : `/accounts/${api.accountId}/workers/scripts/${options.name}`,
1468
1487
  );
1469
1488
  return res.status === 200;
1470
- }
1489
+ };
1471
1490
 
1472
1491
  async function assertWorkerDoesNotExist(
1473
1492
  api: CloudflareApi,