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,4 +1,3 @@
1
- import { isDeepStrictEqual } from "node:util";
2
1
  import path from "pathe";
3
2
  import { Resource, ResourceKind } from "../resource.js";
4
3
  import { DeferredPromise } from "../util/deferred-promise.js";
@@ -12,11 +11,12 @@ import { ContainerApplication } from "./container.js";
12
11
  import { CustomDomain } from "./custom-domain.js";
13
12
  import { DurableObjectNamespace, isDurableObjectNamespace, } from "./durable-object-namespace.js";
14
13
  import { isQueueEventSource } from "./event-source.js";
14
+ import { deleteMiniflareWorkerData } from "./miniflare/delete.js";
15
15
  import { QueueConsumer, deleteQueueConsumer, listQueueConsumersForWorker, } from "./queue-consumer.js";
16
16
  import { isQueue } from "./queue.js";
17
17
  import { Route } from "./route.js";
18
18
  import { uploadAssets } from "./worker-assets.js";
19
- import { WorkerBundle, normalizeWorkerBundle } from "./worker-bundle.js";
19
+ import { WorkerBundle, normalizeWorkerBundle, } from "./worker-bundle.js";
20
20
  import { bumpMigrationTagVersion, prepareWorkerMetadata, } from "./worker-metadata.js";
21
21
  import { WorkerSubdomain, disableWorkerSubdomain } from "./worker-subdomain.js";
22
22
  import { createTail } from "./worker-tail.js";
@@ -33,111 +33,179 @@ export function Worker(id, props) {
33
33
  const _Worker = Resource("cloudflare::Worker", {
34
34
  alwaysUpdate: true,
35
35
  }, async function (id, props) {
36
- if (props.projectRoot) {
37
- logger.warn("projectRoot is deprecated, use cwd instead");
38
- props.cwd = props.projectRoot;
36
+ const options = (() => {
37
+ if (props.projectRoot) {
38
+ logger.warn("projectRoot is deprecated, use cwd instead");
39
+ props.cwd = props.projectRoot;
40
+ }
41
+ const name = props.name ?? id;
42
+ const cwd = path.resolve(props.cwd ?? process.cwd());
43
+ const compatibilityDate = props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
44
+ const compatibilityFlags = unionCompatibilityFlags(props.compatibility, props.compatibilityFlags);
45
+ const dispatchNamespace = typeof props.namespace === "string"
46
+ ? props.namespace
47
+ : props.namespace?.namespaceName;
48
+ const bundle = normalizeWorkerBundle({
49
+ id,
50
+ entrypoint: props.entrypoint,
51
+ script: props.script,
52
+ format: props.format,
53
+ noBundle: props.noBundle,
54
+ rules: "rules" in props ? props.rules : undefined,
55
+ bundle: props.bundle,
56
+ cwd,
57
+ compatibilityDate,
58
+ compatibilityFlags,
59
+ outdir: props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", name),
60
+ sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
61
+ });
62
+ let assets;
63
+ const containers = [];
64
+ const workflows = [];
65
+ const durableObjects = [];
66
+ for (const binding of Object.values(props.bindings ?? {})) {
67
+ if (typeof binding !== "object")
68
+ continue;
69
+ if (binding.type === "assets") {
70
+ assets = binding;
71
+ }
72
+ else if (binding.type === "container") {
73
+ containers.push(binding);
74
+ }
75
+ else if (binding.type === "workflow" &&
76
+ (!binding.scriptName || binding.scriptName === name)) {
77
+ workflows.push(binding);
78
+ }
79
+ else if (binding.type === "durable_object_namespace" &&
80
+ (!binding.scriptName || binding.scriptName === name)) {
81
+ durableObjects.push(binding);
82
+ }
83
+ }
84
+ return {
85
+ name,
86
+ cwd,
87
+ compatibilityDate,
88
+ compatibilityFlags,
89
+ dispatchNamespace,
90
+ bundle,
91
+ assets,
92
+ containers,
93
+ workflows,
94
+ durableObjects,
95
+ };
96
+ })();
97
+ if (this.phase === "delete") {
98
+ if (options.bundle.isOk()) {
99
+ await options.bundle.value.delete?.();
100
+ }
101
+ await deleteMiniflareWorkerData(options.name, {
102
+ durableObjects: options.durableObjects,
103
+ workflows: options.workflows,
104
+ });
105
+ if (!props.version && this.output?.dev?.hasRemote !== false) {
106
+ const api = await createCloudflareApi(props);
107
+ await deleteQueueConsumers(api, options.name);
108
+ await deleteWorker(api, {
109
+ scriptName: options.name,
110
+ dispatchNamespace: options.dispatchNamespace,
111
+ });
112
+ }
113
+ return this.destroy();
39
114
  }
40
- const cwd = path.resolve(props.cwd ?? process.cwd());
41
- const relativeCwd = cwd === process.cwd() ? undefined : path.relative(process.cwd(), cwd);
42
- const workerName = props.name ?? id;
43
- const compatibilityDate = props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
44
- const compatibilityFlags = unionCompatibilityFlags(props.compatibility, props.compatibilityFlags);
45
- const dispatchNamespace = typeof props.namespace === "string"
46
- ? props.namespace
47
- : props.namespace?.namespaceName;
48
- const bundleSourceResult = normalizeWorkerBundle({
49
- id,
50
- entrypoint: props.entrypoint,
51
- script: props.script,
52
- format: props.format,
53
- noBundle: props.noBundle,
54
- rules: "rules" in props ? props.rules : undefined,
55
- bundle: props.bundle,
56
- cwd,
57
- compatibilityDate,
58
- compatibilityFlags,
59
- outdir: props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", workerName),
60
- sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
61
- });
62
- // run locally if
63
- const local = this.scope.local && !props.dev?.remote;
64
- const watch = this.scope.watch;
65
- if (local) {
115
+ if (options.bundle.isErr()) {
116
+ throw new Error(options.bundle.error);
117
+ }
118
+ const bundle = options.bundle.value;
119
+ const api = await createCloudflareApi(props);
120
+ if (this.scope.local && !props.dev?.remote) {
66
121
  let url;
67
- if (bundleSourceResult.isErr()) {
68
- throw bundleSourceResult.error;
69
- }
70
- if (props.dev && "url" in props.dev) {
122
+ if (props.dev?.url) {
71
123
  url = props.dev.url;
72
124
  }
73
125
  else {
74
126
  const { MiniflareController } = await import("./miniflare/miniflare-controller.js");
75
127
  const controller = MiniflareController.singleton;
76
128
  url = await controller.add({
129
+ api,
77
130
  id,
78
- name: workerName,
79
- compatibilityDate,
80
- compatibilityFlags,
131
+ name: options.name,
132
+ compatibilityDate: options.compatibilityDate,
133
+ compatibilityFlags: options.compatibilityFlags,
81
134
  bindings: props.bindings,
82
135
  eventSources: props.eventSources,
83
136
  assets: props.assets,
84
- bundle: bundleSourceResult.value,
85
- port: props.dev?.port ?? undefined,
137
+ bundle,
138
+ port: props.dev?.port,
86
139
  });
87
140
  this.onCleanup(() => controller.dispose());
88
141
  }
142
+ await provisionResources(props, {
143
+ name: options.name,
144
+ local: true,
145
+ dispatchNamespace: options.dispatchNamespace,
146
+ containers: options.containers,
147
+ });
89
148
  return this({
149
+ ...props,
90
150
  type: "service",
91
151
  id,
92
- entrypoint: props.entrypoint,
93
- name: workerName,
94
- cwd: relativeCwd,
95
- compatibilityDate,
96
- compatibilityFlags,
97
- format: props.format || "esm", // Include format in the output
98
- bindings: normalizeExportBindings(workerName, props.bindings),
99
- env: props.env,
100
- observability: props.observability,
152
+ name: options.name,
153
+ cwd: path.relative(process.cwd(), options.cwd),
154
+ compatibilityDate: options.compatibilityDate,
155
+ compatibilityFlags: options.compatibilityFlags,
156
+ format: props.format || "esm",
157
+ bindings: normalizeExportBindings(options.name, props.bindings),
101
158
  createdAt: this.output?.createdAt ?? Date.now(),
102
159
  updatedAt: Date.now(),
103
- eventSources: props.eventSources,
104
160
  url,
105
- dev: props.dev,
106
- // Include assets configuration in the output
107
- assets: props.assets,
108
- // Include cron triggers in the output
109
- crons: props.crons,
110
- // Include placement configuration in the output
111
- placement: props.placement,
112
- // Include limits configuration in the output
113
- limits: props.limits,
114
- // phantom property
161
+ routes: [],
162
+ domains: [],
163
+ dev: {
164
+ hasRemote: this.output?.dev?.hasRemote ?? false,
165
+ },
115
166
  Env: undefined,
116
167
  });
117
168
  }
118
- const api = await createCloudflareApi(props);
119
- if (this.phase === "delete") {
120
- if (bundleSourceResult.isOk()) {
121
- await bundleSourceResult.value.delete?.();
169
+ if (this.phase === "create" || this.output.dev?.hasRemote === false) {
170
+ if (props.version) {
171
+ // When version is specified, we adopt existing workers or create them if they don't exist
172
+ if (!(await workerExists(api, options))) {
173
+ // Create the base worker first if it doesn't exist
174
+ await putWorker(api, {
175
+ ...props,
176
+ version: undefined,
177
+ workerName: options.name,
178
+ scriptBundle: await bundle.create(),
179
+ dispatchNamespace: options.dispatchNamespace,
180
+ compatibilityDate: options.compatibilityDate,
181
+ compatibilityFlags: options.compatibilityFlags,
182
+ assetUploadResult: options.assets
183
+ ? await uploadAssets(api, {
184
+ workerName: options.name,
185
+ assets: options.assets,
186
+ assetConfig: props.assets,
187
+ namespace: options.dispatchNamespace,
188
+ })
189
+ : undefined,
190
+ });
191
+ }
192
+ // We always "adopt" when publishing versions
122
193
  }
123
- if (!props.version) {
124
- await deleteQueueConsumers(api, workerName);
125
- await deleteWorker(api, {
126
- scriptName: workerName,
127
- dispatchNamespace,
128
- });
194
+ else if (!props.adopt) {
195
+ await assertWorkerDoesNotExist(api, options.name);
196
+ }
197
+ else if (props.adopt &&
198
+ !options.dispatchNamespace &&
199
+ props.url === false) {
200
+ // explicitly disable the workers.dev subdomain
201
+ await disableWorkerSubdomain(api, options.name);
129
202
  }
130
- return this.destroy();
131
- }
132
- else if (bundleSourceResult.isErr()) {
133
- throw new Error(bundleSourceResult.error);
134
203
  }
135
- const bundleSource = bundleSourceResult.value;
136
- if (this.phase === "update") {
204
+ else if (this.phase === "update") {
137
205
  const oldName = this.output.name ?? this.output.id;
138
- const newName = workerName;
206
+ const newName = options.name;
139
207
  if (oldName && oldName !== newName) {
140
- if (dispatchNamespace) {
208
+ if (options.dispatchNamespace) {
141
209
  this.replace(true);
142
210
  }
143
211
  else {
@@ -148,212 +216,93 @@ const _Worker = Resource("cloudflare::Worker", {
148
216
  }
149
217
  }
150
218
  }
151
- let assetsBinding;
152
- const workflowsBindings = [];
153
- const containersBindings = [];
154
- for (const binding of Object.values(props.bindings ?? {})) {
155
- if (typeof binding === "object") {
156
- if (binding.type === "assets") {
157
- assetsBinding = binding;
158
- }
159
- else if (binding.type === "workflow") {
160
- workflowsBindings.push(binding);
161
- }
162
- else if (binding.type === "container") {
163
- containersBindings.push(binding);
164
- }
165
- }
166
- }
167
- const putWorkerWithAssets = async (props, scriptBundle) => {
168
- const assetUploadResult = assetsBinding
169
- ? await uploadAssets(api, {
170
- workerName,
171
- assets: assetsBinding,
172
- assetConfig: props.assets,
173
- namespace: dispatchNamespace,
174
- })
175
- : undefined;
176
- // Deploy worker (either as version or live worker)
177
- return await putWorker(api, {
178
- ...props,
179
- workerName,
180
- scriptBundle,
181
- dispatchNamespace,
219
+ const assets = options.assets
220
+ ? await uploadAssets(api, {
221
+ workerName: options.name,
222
+ assets: options.assets,
223
+ assetConfig: props.assets,
224
+ namespace: options.dispatchNamespace,
225
+ })
226
+ : undefined;
227
+ let result;
228
+ if (this.scope.watch) {
229
+ const controller = new AbortController();
230
+ result = await watchWorker(api, props, {
231
+ id,
232
+ name: options.name,
233
+ dispatchNamespace: options.dispatchNamespace,
234
+ bundle,
235
+ compatibilityDate: options.compatibilityDate,
236
+ compatibilityFlags: options.compatibilityFlags,
182
237
  version: props.version,
183
- compatibilityDate,
184
- compatibilityFlags,
185
- assetUploadResult,
238
+ assets,
239
+ controller,
186
240
  });
187
- };
188
- if (this.phase === "create") {
189
- if (props.version) {
190
- // When version is specified, we adopt existing workers or create them if they don't exist
191
- if (!(await workerExists(api, workerName))) {
192
- // Create the base worker first if it doesn't exist
193
- const baseWorkerProps = { ...props, version: undefined };
194
- await putWorkerWithAssets(baseWorkerProps, await bundleSource.create());
195
- }
196
- // We always "adopt" when publishing versions
197
- }
198
- else if (!props.adopt) {
199
- await assertWorkerDoesNotExist(api, workerName);
200
- }
201
- }
202
- let putWorkerResult;
203
- if (watch) {
204
- const controller = new AbortController();
205
- const promise = new DeferredPromise();
206
- const runWatch = async () => {
207
- for await (const bundle of bundleSource.watch(controller.signal)) {
208
- if (promise.status === "pending") {
209
- await putWorkerWithAssets(props, bundle)
210
- .then((result) => promise.resolve(result))
211
- .catch((error) => {
212
- controller.abort();
213
- promise.reject(error);
214
- });
215
- continue;
216
- }
217
- logger.task("", {
218
- message: "reload",
219
- status: "success",
220
- resource: id,
221
- prefix: "build",
222
- prefixColor: "cyanBright",
223
- });
224
- await putWorker(api, {
225
- ...props,
226
- workerName,
227
- scriptBundle: bundle,
228
- dispatchNamespace,
229
- version: props.version,
230
- compatibilityDate,
231
- compatibilityFlags,
232
- assetUploadResult: assetsBinding
233
- ? {
234
- keepAssets: true,
235
- assetConfig: props.assets,
236
- }
237
- : undefined,
238
- unstable_cacheWorkerSettings: true,
239
- });
240
- logger.task("", {
241
- message: "updated",
242
- status: "success",
243
- resource: id,
244
- prefix: "build",
245
- prefixColor: "greenBright",
246
- });
247
- }
248
- };
249
- void runWatch(); // this is not awaited because it's an ongoing process
250
241
  this.onCleanup(() => controller.abort());
251
- putWorkerResult = await promise.value;
252
- const tail = await createTail(api, id, workerName).catch((error) => {
253
- logger.error(`Failed to create tail for ${workerName}`, error);
242
+ const tail = await createTail(api, id, options.name).catch((error) => {
243
+ logger.error(`Failed to create tail for ${options.name}`, error);
254
244
  });
255
245
  this.onCleanup(() => tail?.close());
256
246
  }
257
247
  else {
258
- const scriptBundle = await bundleSource.create();
259
- putWorkerResult = await putWorkerWithAssets(props, scriptBundle);
260
- }
261
- const tasks = [];
262
- for (const workflow of workflowsBindings) {
263
- if (workflow.scriptName === undefined ||
264
- workflow.scriptName === workerName) {
265
- tasks.push(upsertWorkflow(api, {
266
- workflowName: workflow.workflowName,
267
- className: workflow.className,
268
- scriptName: workflow.scriptName ?? workerName,
269
- }));
270
- }
271
- }
272
- if (containersBindings.length > 0) {
273
- tasks.push(getVersionMetadata(api, workerName, putWorkerResult.deployment_id).then((versionMetadata) => provisionContainers(api, {
274
- scriptName: workerName,
275
- containers: containersBindings,
276
- bindings: versionMetadata.resources.bindings,
277
- })));
248
+ result = await putWorker(api, {
249
+ ...props,
250
+ workerName: options.name,
251
+ scriptBundle: await bundle.create(),
252
+ dispatchNamespace: options.dispatchNamespace,
253
+ compatibilityDate: options.compatibilityDate,
254
+ compatibilityFlags: options.compatibilityFlags,
255
+ assetUploadResult: assets,
256
+ });
278
257
  }
279
- if (!isDeepStrictEqual(props.crons, this.output?.crons)) {
280
- tasks.push(api.put(`/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`, props.crons?.map((cron) => ({ cron })) ?? []));
258
+ if (props.crons) {
259
+ await api.put(`/accounts/${api.accountId}/workers/scripts/${options.name}/schedules`, props.crons?.map((cron) => ({ cron })) ?? []);
281
260
  }
282
- tasks.push(provisionEventSources(api, {
283
- scriptName: workerName,
284
- eventSources: props.eventSources,
285
- adopt: props.adopt,
286
- }));
287
- const [domains, routes, subdomain] = await Promise.all([
288
- // TODO: can you provision domains and routes in parallel, or is there a dependency?
289
- provisionDomains(api, {
290
- scriptName: workerName,
291
- adopt: props.adopt,
292
- domains: props.domains,
293
- }),
294
- provisionRoutes(api, {
295
- scriptName: workerName,
296
- adopt: props.adopt,
297
- routes: props.routes,
298
- }),
299
- provisionSubdomain(api, {
300
- scriptName: workerName,
301
- enable: props.url ?? dispatchNamespace === undefined,
302
- previewVersionId: props.version && putWorkerResult.metadata.has_preview
303
- ? putWorkerResult.id
304
- : undefined,
305
- retain: !!props.version,
306
- forceDelete: this.phase === "create" && !!props.adopt && props.url === false,
307
- }),
308
- ...tasks,
309
- ]);
310
- const now = Date.now();
311
- // Construct the output
261
+ await Promise.all(options.workflows.map((workflow) => upsertWorkflow(api, {
262
+ workflowName: workflow.workflowName,
263
+ className: workflow.className,
264
+ scriptName: workflow.scriptName ?? options.name,
265
+ })));
266
+ const { domains, routes, subdomain } = await provisionResources(props, {
267
+ name: options.name,
268
+ local: false,
269
+ dispatchNamespace: options.dispatchNamespace,
270
+ containers: options.containers,
271
+ result,
272
+ api,
273
+ });
274
+ const now = new Date();
312
275
  return this({
313
276
  ...props,
314
277
  type: "service",
315
278
  id,
316
279
  entrypoint: props.entrypoint,
317
- name: workerName,
318
- cwd: relativeCwd,
319
- compatibilityDate,
320
- compatibilityFlags,
321
- format: props.format || "esm", // Include format in the output
322
- bindings: normalizeExportBindings(workerName, props.bindings),
280
+ name: options.name,
281
+ cwd: path.relative(process.cwd(), options.cwd),
282
+ compatibilityDate: options.compatibilityDate,
283
+ compatibilityFlags: options.compatibilityFlags,
284
+ format: props.format || "esm",
285
+ bindings: normalizeExportBindings(options.name, props.bindings),
323
286
  env: props.env,
324
287
  observability: props.observability,
325
288
  createdAt: this.output?.createdAt ?? now,
326
289
  updatedAt: now,
327
290
  eventSources: props.eventSources,
328
291
  url: subdomain?.url,
329
- dev: props.dev,
330
- // Include assets configuration in the output
331
292
  assets: props.assets,
332
- // Include cron triggers in the output
333
293
  crons: props.crons,
334
- // Include the created routes in the output
335
294
  routes,
336
- // Include the created domains in the output
337
295
  domains,
338
- // Include the dispatch namespace in the output
339
296
  namespace: props.namespace,
340
- // Include version information in the output
341
297
  version: props.version,
342
- // Include placement configuration in the output
343
298
  placement: props.placement,
344
- // Include limits configuration in the output
345
299
  limits: props.limits,
346
- // phantom property
347
300
  Env: undefined,
301
+ dev: {
302
+ hasRemote: true,
303
+ },
348
304
  });
349
305
  });
350
- const assertUnique = (inputs, key, message) => {
351
- const ids = inputs.map((input) => input[key]);
352
- const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
353
- if (duplicates.length > 0) {
354
- throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
355
- }
356
- };
357
306
  const normalizeExportBindings = (scriptName, bindings = {}) => {
358
307
  return Object.fromEntries(Object.entries(bindings).map(([bindingName, binding]) => [
359
308
  bindingName,
@@ -372,133 +321,212 @@ const normalizeExportBindings = (scriptName, bindings = {}) => {
372
321
  : binding,
373
322
  ]));
374
323
  };
375
- const normalizeApiOptions = (api) => ({
376
- accountId: api.accountId,
377
- baseUrl: api.baseUrl,
378
- ...api.authOptions,
379
- });
380
- async function provisionContainers(api, props) {
381
- if (!props.containers?.length) {
382
- return;
383
- }
384
- return await Promise.all(props.containers.map((container) => {
385
- const namespaceId = props.bindings.find((binding) => binding.type === "durable_object_namespace" &&
386
- binding.class_name === container.className)?.namespace_id;
387
- if (!namespaceId) {
388
- throw new Error(`Namespace ID not found for container ${container.id}`);
389
- }
390
- return ContainerApplication(container.id, {
391
- image: container.image,
392
- name: container.name,
393
- instanceType: container.instanceType,
394
- observability: container.observability,
395
- durableObjects: {
396
- namespaceId,
397
- },
398
- schedulingPolicy: container.schedulingPolicy,
399
- adopt: container.adopt,
400
- ...normalizeApiOptions(api),
401
- });
402
- }));
403
- }
404
- async function provisionEventSources(api, props) {
405
- if (!props.eventSources?.length) {
406
- return;
407
- }
408
- return await Promise.all(props.eventSources.map((eventSource) => {
409
- if (isQueue(eventSource)) {
410
- return QueueConsumer(`${eventSource.id}-consumer`, {
411
- queue: eventSource,
412
- scriptName: props.scriptName,
413
- settings: eventSource.dlq
414
- ? { deadLetterQueue: eventSource.dlq }
415
- : undefined,
416
- adopt: props.adopt,
417
- ...normalizeApiOptions(api),
418
- });
419
- }
420
- if (isQueueEventSource(eventSource)) {
421
- return QueueConsumer(`${eventSource.queue.id}-consumer`, {
422
- queue: eventSource.queue,
423
- scriptName: props.scriptName,
424
- settings: eventSource.settings,
425
- adopt: props.adopt,
426
- ...normalizeApiOptions(api),
427
- });
428
- }
429
- throw new Error(`Unsupported event source: ${eventSource}`);
430
- }));
431
- }
432
- async function provisionDomains(api, props) {
433
- if (!props.domains?.length) {
434
- return;
324
+ const assertUnique = (inputs, key, message) => {
325
+ const ids = inputs.map((input) => input[key]);
326
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
327
+ if (duplicates.length > 0) {
328
+ throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
435
329
  }
436
- const domains = props.domains.map((domain) => {
437
- if (typeof domain === "string") {
330
+ };
331
+ async function provisionResources(props, options) {
332
+ let metadataPromise;
333
+ const input = {
334
+ containers: options.containers,
335
+ domains: props.domains?.map((domain) => {
336
+ if (typeof domain === "string") {
337
+ return {
338
+ name: domain,
339
+ zoneId: undefined,
340
+ adopt: props.adopt,
341
+ };
342
+ }
438
343
  return {
439
- name: domain,
440
- zoneId: undefined,
441
- adopt: props.adopt,
344
+ name: domain.domainName,
345
+ zoneId: domain.zoneId,
346
+ adopt: domain.adopt ?? props.adopt,
442
347
  };
443
- }
444
- return {
445
- name: domain.domainName,
446
- zoneId: domain.zoneId,
447
- adopt: domain.adopt ?? props.adopt,
448
- };
449
- });
450
- assertUnique(domains, "name", "Custom Domain");
451
- return await Promise.all(domains.map(async (domain) => {
452
- return await CustomDomain(domain.name, {
453
- workerName: props.scriptName,
454
- name: domain.name,
455
- zoneId: domain.zoneId,
456
- adopt: domain.adopt,
457
- ...normalizeApiOptions(api),
458
- });
459
- }));
460
- }
461
- async function provisionRoutes(api, props) {
462
- if (!props.routes?.length) {
463
- return;
464
- }
465
- const routes = props.routes.map((route) => {
466
- if (typeof route === "string") {
348
+ }),
349
+ eventSources: props.eventSources?.map((eventSource) => {
350
+ if (isQueue(eventSource)) {
351
+ return {
352
+ queue: eventSource,
353
+ settings: eventSource.dlq
354
+ ? { deadLetterQueue: eventSource.dlq }
355
+ : undefined,
356
+ };
357
+ }
358
+ if (isQueueEventSource(eventSource)) {
359
+ return {
360
+ queue: eventSource.queue,
361
+ settings: eventSource.settings,
362
+ };
363
+ }
364
+ throw new Error(`Unsupported event source: ${eventSource}`);
365
+ }),
366
+ routes: props.routes?.map((route) => {
367
+ if (typeof route === "string") {
368
+ return {
369
+ pattern: route,
370
+ adopt: props.adopt,
371
+ };
372
+ }
467
373
  return {
468
- pattern: route,
469
- adopt: props.adopt,
374
+ pattern: route.pattern,
375
+ zoneId: route.zoneId,
376
+ adopt: route.adopt ?? props.adopt,
470
377
  };
471
- }
472
- return {
473
- pattern: route.pattern,
474
- zoneId: route.zoneId,
475
- adopt: route.adopt ?? props.adopt,
476
- };
477
- });
478
- assertUnique(routes, "pattern", "Route");
479
- return await Promise.all(routes.map(async (route) => {
480
- return await Route(route.pattern, {
481
- pattern: route.pattern,
482
- script: props.scriptName,
483
- zoneId: route.zoneId,
484
- adopt: route.adopt,
485
- ...normalizeApiOptions(api),
486
- });
487
- }));
488
- }
489
- async function provisionSubdomain(api, props) {
490
- if (props.enable) {
491
- return await WorkerSubdomain("url", {
492
- scriptName: props.scriptName,
493
- previewVersionId: props.previewVersionId,
494
- retain: props.retain,
495
- ...normalizeApiOptions(api),
496
- });
378
+ }),
379
+ api: {
380
+ accountId: props.accountId,
381
+ apiKey: props.apiKey,
382
+ apiToken: props.apiToken,
383
+ email: props.email,
384
+ baseUrl: props.baseUrl,
385
+ },
386
+ };
387
+ if (input.routes) {
388
+ assertUnique(input.routes, "pattern", "Route");
497
389
  }
498
- if (props.forceDelete) {
499
- await disableWorkerSubdomain(api, props.scriptName);
390
+ if (input.domains) {
391
+ assertUnique(input.domains, "name", "Custom Domain");
392
+ }
393
+ const [containers, domains, eventSources, routes, subdomain] = await Promise.all([
394
+ input.containers
395
+ ? Promise.all(input.containers.map(async (container) => {
396
+ return await ContainerApplication(container.id, {
397
+ image: container.image,
398
+ name: container.name,
399
+ instanceType: container.instanceType,
400
+ observability: container.observability,
401
+ durableObjects: {
402
+ namespaceId: await getContainerNamespaceId(container),
403
+ },
404
+ schedulingPolicy: container.schedulingPolicy,
405
+ adopt: container.adopt,
406
+ dev: options.local,
407
+ ...input.api,
408
+ });
409
+ }))
410
+ : undefined,
411
+ input.domains
412
+ ? Promise.all(input.domains.map(async (domain) => {
413
+ return await CustomDomain(domain.name, {
414
+ name: domain.name,
415
+ zoneId: domain.zoneId,
416
+ adopt: domain.adopt,
417
+ workerName: options.name,
418
+ dev: options.local,
419
+ ...input.api,
420
+ });
421
+ }))
422
+ : undefined,
423
+ input.eventSources
424
+ ? Promise.all(input.eventSources.map(async (eventSource) => {
425
+ return await QueueConsumer(`${eventSource.queue.id}-consumer`, {
426
+ queue: eventSource.queue,
427
+ scriptName: options.name,
428
+ settings: eventSource.settings,
429
+ adopt: props.adopt,
430
+ dev: options.local,
431
+ ...input.api,
432
+ });
433
+ }))
434
+ : undefined,
435
+ input.routes
436
+ ? Promise.all(input.routes.map(async (route) => {
437
+ return await Route(route.pattern, {
438
+ pattern: route.pattern,
439
+ script: options.name,
440
+ zoneId: route.zoneId,
441
+ adopt: route.adopt,
442
+ dev: options.local,
443
+ ...input.api,
444
+ });
445
+ }))
446
+ : undefined,
447
+ (props.url ?? !options.dispatchNamespace)
448
+ ? WorkerSubdomain("url", {
449
+ scriptName: options.name,
450
+ previewVersionId: props.version ? options.result?.id : undefined,
451
+ retain: !!props.version,
452
+ dev: options.local,
453
+ ...input.api,
454
+ })
455
+ : undefined,
456
+ ]);
457
+ return { containers, domains, routes, eventSources, subdomain };
458
+ async function getContainerNamespaceId(container) {
459
+ if (options.local) {
460
+ return container.id;
461
+ }
462
+ metadataPromise ??= getVersionMetadata(options.api, options.name, options.result.deployment_id);
463
+ const metadata = await metadataPromise;
464
+ const binding = metadata.resources.bindings.find((binding) => binding.type === "durable_object_namespace" &&
465
+ binding.class_name === container.className);
466
+ if (!binding?.namespace_id) {
467
+ throw new Error(`Container ${container.name} not found in version ${options.result.deployment_id}`);
468
+ }
469
+ return binding.namespace_id;
500
470
  }
501
471
  }
472
+ const watchWorker = async (api, props, input) => {
473
+ const promise = new DeferredPromise();
474
+ const run = async () => {
475
+ for await (const bundle of input.bundle.watch(input.controller.signal)) {
476
+ if (promise.status === "pending") {
477
+ try {
478
+ const result = await putWorker(api, {
479
+ ...props,
480
+ workerName: input.name,
481
+ scriptBundle: bundle,
482
+ dispatchNamespace: input.dispatchNamespace,
483
+ compatibilityDate: input.compatibilityDate,
484
+ compatibilityFlags: input.compatibilityFlags,
485
+ assetUploadResult: input.assets,
486
+ });
487
+ promise.resolve(result);
488
+ }
489
+ catch (error) {
490
+ input.controller.abort();
491
+ promise.reject(error);
492
+ }
493
+ continue;
494
+ }
495
+ logger.task("", {
496
+ message: "reload",
497
+ status: "success",
498
+ resource: input.id,
499
+ prefix: "build",
500
+ prefixColor: "cyanBright",
501
+ });
502
+ await putWorker(api, {
503
+ ...props,
504
+ workerName: input.name,
505
+ scriptBundle: bundle,
506
+ dispatchNamespace: input.dispatchNamespace,
507
+ version: input.version,
508
+ compatibilityDate: input.compatibilityDate,
509
+ compatibilityFlags: input.compatibilityFlags,
510
+ assetUploadResult: input.assets
511
+ ? {
512
+ keepAssets: true,
513
+ assetConfig: props.assets,
514
+ }
515
+ : undefined,
516
+ unstable_cacheWorkerSettings: true,
517
+ });
518
+ logger.task("", {
519
+ message: "updated",
520
+ status: "success",
521
+ resource: input.id,
522
+ prefix: "build",
523
+ prefixColor: "greenBright",
524
+ });
525
+ }
526
+ };
527
+ void run();
528
+ return await promise.value;
529
+ };
502
530
  async function prepareWorkerUpload(api, props) {
503
531
  const scriptMetadata = await prepareWorkerMetadata(api, props);
504
532
  if (props.format === "cjs") {
@@ -610,10 +638,12 @@ export async function putWorker(api, props) {
610
638
  err.status === 400 &&
611
639
  err.message.match(/binding.*failed to generate/)), 10, 100);
612
640
  }
613
- async function workerExists(api, scriptName) {
614
- const res = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}`);
641
+ const workerExists = async (api, options) => {
642
+ const res = await api.get(options.dispatchNamespace
643
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${options.dispatchNamespace}/scripts/${options.name}`
644
+ : `/accounts/${api.accountId}/workers/scripts/${options.name}`);
615
645
  return res.status === 200;
616
- }
646
+ };
617
647
  async function assertWorkerDoesNotExist(api, scriptName) {
618
648
  const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}`);
619
649
  if (response.status === 404) {