wrangler 2.0.23 → 2.0.26

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 (80) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +235 -47
  4. package/package.json +11 -6
  5. package/src/__tests__/configuration.test.ts +89 -17
  6. package/src/__tests__/dev.test.tsx +29 -4
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +87 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/init.test.ts +537 -359
  11. package/src/__tests__/jest.setup.ts +34 -1
  12. package/src/__tests__/kv.test.ts +2 -2
  13. package/src/__tests__/metrics.test.ts +5 -0
  14. package/src/__tests__/pages.test.ts +14 -0
  15. package/src/__tests__/publish.test.ts +497 -254
  16. package/src/__tests__/r2.test.ts +173 -71
  17. package/src/__tests__/tail.test.ts +112 -42
  18. package/src/__tests__/user.test.ts +1 -0
  19. package/src/__tests__/validate-dev-props.test.ts +56 -0
  20. package/src/__tests__/whoami.test.tsx +60 -1
  21. package/src/api/dev.ts +7 -0
  22. package/src/bundle.ts +279 -44
  23. package/src/cfetch/internal.ts +73 -2
  24. package/src/config/config.ts +8 -3
  25. package/src/config/environment.ts +40 -8
  26. package/src/config/index.ts +13 -0
  27. package/src/config/validation.ts +102 -8
  28. package/src/create-worker-upload-form.ts +25 -0
  29. package/src/dev/dev.tsx +121 -28
  30. package/src/dev/local.tsx +88 -14
  31. package/src/dev/remote.tsx +39 -8
  32. package/src/dev/use-esbuild.ts +28 -0
  33. package/src/dev/validate-dev-props.ts +31 -0
  34. package/src/dev-registry.tsx +160 -0
  35. package/src/dev.tsx +107 -80
  36. package/src/generate.ts +112 -14
  37. package/src/index.tsx +212 -4
  38. package/src/init.ts +111 -38
  39. package/src/inspect.ts +90 -5
  40. package/src/metrics/index.ts +1 -0
  41. package/src/metrics/metrics-dispatcher.ts +1 -0
  42. package/src/metrics/metrics-usage-headers.ts +24 -0
  43. package/src/metrics/send-event.ts +2 -2
  44. package/src/miniflare-cli/assets.ts +27 -16
  45. package/src/miniflare-cli/index.ts +124 -2
  46. package/src/module-collection.ts +3 -3
  47. package/src/pages/build.tsx +75 -41
  48. package/src/pages/constants.ts +5 -0
  49. package/src/pages/deployments.tsx +10 -10
  50. package/src/pages/dev.tsx +177 -52
  51. package/src/pages/errors.ts +22 -0
  52. package/src/pages/functions/buildPlugin.ts +4 -0
  53. package/src/pages/functions/buildWorker.ts +4 -0
  54. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  55. package/src/pages/functions/routes-consolidation.ts +73 -0
  56. package/src/pages/functions/routes-transformation.test.ts +271 -0
  57. package/src/pages/functions/routes-transformation.ts +122 -0
  58. package/src/pages/functions.tsx +96 -0
  59. package/src/pages/index.tsx +65 -55
  60. package/src/pages/projects.tsx +9 -3
  61. package/src/pages/publish.tsx +76 -23
  62. package/src/pages/types.ts +9 -0
  63. package/src/pages/upload.tsx +38 -21
  64. package/src/publish.ts +126 -112
  65. package/src/r2.ts +81 -0
  66. package/src/tail/filters.ts +3 -1
  67. package/src/tail/index.ts +15 -2
  68. package/src/tail/printing.ts +43 -3
  69. package/src/user/user.tsx +20 -2
  70. package/src/whoami.tsx +79 -1
  71. package/src/worker.ts +12 -0
  72. package/templates/first-party-worker-module-facade.ts +18 -0
  73. package/templates/format-dev-errors.ts +32 -0
  74. package/templates/pages-template-plugin.ts +16 -4
  75. package/templates/pages-template-worker.ts +16 -5
  76. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  77. package/templates/service-bindings-module-facade.js +54 -0
  78. package/templates/service-bindings-sw-facade.js +42 -0
  79. package/wrangler-dist/cli.d.ts +7 -0
  80. package/wrangler-dist/cli.js +40851 -15332
package/src/dev/local.tsx CHANGED
@@ -4,11 +4,13 @@ import { writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { useState, useEffect, useRef } from "react";
6
6
  import onExit from "signal-exit";
7
+ import { registerWorker } from "../dev-registry";
7
8
  import useInspector from "../inspect";
8
9
  import { logger } from "../logger";
9
10
  import { DEFAULT_MODULE_RULES } from "../module-collection";
10
11
  import { waitForPortToBeAvailable } from "../proxy";
11
12
  import type { Config } from "../config";
13
+ import type { WorkerRegistry } from "../dev-registry";
12
14
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
13
15
  import type { AssetPaths } from "../sites";
14
16
  import type { CfWorkerInit, CfScriptFormat } from "../worker";
@@ -21,9 +23,10 @@ interface LocalProps {
21
23
  format: CfScriptFormat | undefined;
22
24
  compatibilityDate: string;
23
25
  compatibilityFlags: string[] | undefined;
26
+ usageModel: "bundled" | "unbound" | undefined;
24
27
  bindings: CfWorkerInit["bindings"];
28
+ workerDefinitions: WorkerRegistry;
25
29
  assetPaths: AssetPaths | undefined;
26
- isWorkersSite: boolean;
27
30
  port: number;
28
31
  ip: string;
29
32
  rules: Config["rules"];
@@ -56,9 +59,10 @@ function useLocalWorker({
56
59
  format,
57
60
  compatibilityDate,
58
61
  compatibilityFlags,
62
+ usageModel,
59
63
  bindings,
64
+ workerDefinitions,
60
65
  assetPaths,
61
- isWorkersSite,
62
66
  port,
63
67
  inspectorPort,
64
68
  rules,
@@ -88,6 +92,27 @@ function useLocalWorker({
88
92
  // so that it's persisted in the temp dir across a dev session
89
93
  // even when we change source and reload
90
94
  null;
95
+
96
+ useEffect(() => {
97
+ if (bindings.services && bindings.services.length > 0) {
98
+ logger.warn(
99
+ "⎔ Support for service bindings in local mode is experimental and may change."
100
+ );
101
+ }
102
+ }, [bindings.services]);
103
+
104
+ useEffect(() => {
105
+ const externalDurableObjects = (
106
+ bindings.durable_objects?.bindings || []
107
+ ).filter((binding) => binding.script_name);
108
+
109
+ if (externalDurableObjects.length > 0) {
110
+ logger.warn(
111
+ "⎔ Support for external Durable Objects in local mode is experimental and may change."
112
+ );
113
+ }
114
+ }, [bindings.durable_objects?.bindings]);
115
+
91
116
  useEffect(() => {
92
117
  const abortController = new AbortController();
93
118
  async function startLocalWorker() {
@@ -100,12 +125,6 @@ function useLocalWorker({
100
125
  abortSignal: abortController.signal,
101
126
  });
102
127
 
103
- if (bindings.services && bindings.services.length > 0) {
104
- throw new Error(
105
- "⎔ Service bindings are not yet supported in local mode."
106
- );
107
- }
108
-
109
128
  // In local mode, we want to copy all referenced modules into
110
129
  // the output bundle directory before starting up
111
130
  for (const module of bundle.modules) {
@@ -178,6 +197,15 @@ function useLocalWorker({
178
197
  ? `${localProtocol}://${localUpstream}`
179
198
  : undefined;
180
199
 
200
+ const internalDurableObjects = (
201
+ bindings.durable_objects?.bindings || []
202
+ ).filter((binding) => !binding.script_name);
203
+ const externalDurableObjects = (
204
+ bindings.durable_objects?.bindings || []
205
+ ).filter((binding) => binding.script_name);
206
+
207
+ // TODO: This was already messy with the custom `disableLogs` and `logOptions`.
208
+ // It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
181
209
  const options: MiniflareOptions = {
182
210
  name: workerName,
183
211
  port,
@@ -194,12 +222,37 @@ function useLocalWorker({
194
222
  })),
195
223
  compatibilityDate,
196
224
  compatibilityFlags,
225
+ usageModel,
197
226
  kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
198
227
  r2Buckets: bindings.r2_buckets?.map((r2) => r2.binding),
199
228
  durableObjects: Object.fromEntries(
200
- (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
201
- (value) => [value.name, value.class_name]
202
- )
229
+ internalDurableObjects.map((binding) => [
230
+ binding.name,
231
+ binding.class_name,
232
+ ])
233
+ ),
234
+ externalDurableObjects: Object.fromEntries(
235
+ externalDurableObjects
236
+ .map((binding) => {
237
+ const service = workerDefinitions[binding.script_name as string];
238
+ if (!service) return [binding.name, undefined];
239
+
240
+ const name = service.durableObjects.find(
241
+ (durableObject) =>
242
+ durableObject.className === binding.class_name
243
+ )?.name;
244
+ if (!name) return [binding.name, undefined];
245
+
246
+ return [
247
+ binding.name,
248
+ {
249
+ name,
250
+ host: service.durableObjectsHost,
251
+ port: service.durableObjectsPort,
252
+ },
253
+ ];
254
+ })
255
+ .filter(([_, details]) => !!details)
203
256
  ),
204
257
  ...(localPersistencePath
205
258
  ? {
@@ -273,8 +326,28 @@ function useLocalWorker({
273
326
  stdio: "pipe",
274
327
  }));
275
328
 
276
- child.on("message", (message) => {
277
- if (message === "ready") {
329
+ child.on("message", async (messageString) => {
330
+ const message = JSON.parse(messageString as string);
331
+ if (message.ready) {
332
+ // Let's register our presence in the dev registry
333
+ if (workerName) {
334
+ await registerWorker(workerName, {
335
+ protocol: localProtocol,
336
+ mode: "local",
337
+ port,
338
+ host: ip,
339
+ durableObjects: internalDurableObjects.map((binding) => ({
340
+ name: binding.name,
341
+ className: binding.class_name,
342
+ })),
343
+ ...(message.durableObjectsPort
344
+ ? {
345
+ durableObjectsHost: ip,
346
+ durableObjectsPort: message.durableObjectsPort,
347
+ }
348
+ : {}),
349
+ });
350
+ }
278
351
  onReady?.();
279
352
  }
280
353
  });
@@ -352,12 +425,13 @@ function useLocalWorker({
352
425
  bindings.r2_buckets,
353
426
  bindings.vars,
354
427
  bindings.services,
428
+ workerDefinitions,
355
429
  compatibilityDate,
356
430
  compatibilityFlags,
431
+ usageModel,
357
432
  localPersistencePath,
358
433
  liveReload,
359
434
  assetPaths,
360
- isWorkersSite,
361
435
  rules,
362
436
  bindings.wasm_modules,
363
437
  bindings.text_blobs,
@@ -55,6 +55,8 @@ export function Remote(props: {
55
55
  host: string | undefined;
56
56
  routes: Route[] | undefined;
57
57
  onReady?: (() => void) | undefined;
58
+ sourceMapPath: string | undefined;
59
+ sendMetrics: boolean | undefined;
58
60
  }) {
59
61
  const [accountId, setAccountId] = useState(props.accountId);
60
62
  const accountChoicesRef = useRef<Promise<ChooseAccountItem[]>>();
@@ -78,6 +80,7 @@ export function Remote(props: {
78
80
  host: props.host,
79
81
  routes: props.routes,
80
82
  onReady: props.onReady,
83
+ sendMetrics: props.sendMetrics,
81
84
  });
82
85
 
83
86
  usePreviewServer({
@@ -97,6 +100,7 @@ export function Remote(props: {
97
100
  : undefined,
98
101
  port: props.inspectorPort,
99
102
  logToTerminal: true,
103
+ sourceMapPath: props.sourceMapPath,
100
104
  });
101
105
 
102
106
  const errorHandler = useErrorHandler();
@@ -161,6 +165,7 @@ export function useWorker(props: {
161
165
  host: string | undefined;
162
166
  routes: Route[] | undefined;
163
167
  onReady: (() => void) | undefined;
168
+ sendMetrics: boolean | undefined;
164
169
  }): CfPreviewToken | undefined {
165
170
  const {
166
171
  name,
@@ -202,6 +207,7 @@ export function useWorker(props: {
202
207
  zone: props.zone,
203
208
  host: props.host,
204
209
  routes: props.routes,
210
+ sendMetrics: props.sendMetrics,
205
211
  };
206
212
 
207
213
  setSession(
@@ -230,6 +236,7 @@ export function useWorker(props: {
230
236
  props.legacyEnv,
231
237
  props.routes,
232
238
  props.zone,
239
+ props.sendMetrics,
233
240
  ]);
234
241
 
235
242
  // This effect uses the session to upload the worker and create a preview
@@ -320,17 +327,40 @@ export function useWorker(props: {
320
327
  zone: props.zone,
321
328
  host: props.host,
322
329
  routes: props.routes,
330
+ sendMetrics: props.sendMetrics,
323
331
  };
324
332
 
325
- setToken(
326
- await createWorkerPreview(
327
- init,
328
- workerAccount,
329
- workerCtx,
330
- session,
331
- abortController.signal
332
- )
333
+ const workerPreviewToken = await createWorkerPreview(
334
+ init,
335
+ workerAccount,
336
+ workerCtx,
337
+ session,
338
+ abortController.signal
333
339
  );
340
+
341
+ setToken(workerPreviewToken);
342
+
343
+ // TODO: Once we get service bindings working in the
344
+ // edge preview server, we can define remote dev service bindings
345
+ // and you can uncomment this code.
346
+ // https://github.com/cloudflare/wrangler2/issues/1182
347
+
348
+ /*
349
+ if (name) {
350
+ await registerWorker(name, {
351
+ mode: "remote",
352
+ // upstream protocol is always https (https://github.com/cloudflare/wrangler2/issues/583)
353
+ protocol: "https",
354
+ port: undefined,
355
+ host: workerPreviewToken.host,
356
+ headers: {
357
+ "cf-workers-preview-token": workerPreviewToken.value,
358
+ host: workerPreviewToken.host,
359
+ },
360
+ });
361
+ }
362
+ */
363
+
334
364
  onReady?.();
335
365
  }
336
366
  start().catch((err) => {
@@ -377,6 +407,7 @@ export function useWorker(props: {
377
407
  props.routes,
378
408
  session,
379
409
  onReady,
410
+ props.sendMetrics,
380
411
  ]);
381
412
  return token;
382
413
  }
@@ -5,6 +5,7 @@ import { useState, useEffect } from "react";
5
5
  import { bundleWorker } from "../bundle";
6
6
  import { logger } from "../logger";
7
7
  import type { Config } from "../config";
8
+ import type { WorkerRegistry } from "../dev-registry";
8
9
  import type { Entry } from "../entry";
9
10
  import type { CfModule } from "../worker";
10
11
  import type { WatchMode } from "esbuild";
@@ -15,6 +16,7 @@ export type EsbuildBundle = {
15
16
  entry: Entry;
16
17
  type: "esm" | "commonjs";
17
18
  modules: CfModule[];
19
+ sourceMapPath: string | undefined;
18
20
  };
19
21
 
20
22
  export function useEsbuild({
@@ -23,24 +25,34 @@ export function useEsbuild({
23
25
  jsxFactory,
24
26
  jsxFragment,
25
27
  rules,
28
+ assets,
26
29
  serveAssetsFromWorker,
27
30
  tsconfig,
28
31
  minify,
29
32
  nodeCompat,
30
33
  define,
31
34
  noBundle,
35
+ workerDefinitions,
36
+ services,
37
+ durableObjects,
38
+ firstPartyWorkerDevFacade,
32
39
  }: {
33
40
  entry: Entry;
34
41
  destination: string | undefined;
35
42
  jsxFactory: string | undefined;
36
43
  jsxFragment: string | undefined;
37
44
  rules: Config["rules"];
45
+ assets: Config["assets"];
38
46
  define: Config["define"];
47
+ services: Config["services"];
39
48
  serveAssetsFromWorker: boolean;
40
49
  tsconfig: string | undefined;
41
50
  minify: boolean | undefined;
42
51
  nodeCompat: boolean | undefined;
43
52
  noBundle: boolean;
53
+ workerDefinitions: WorkerRegistry;
54
+ durableObjects: Config["durable_objects"];
55
+ firstPartyWorkerDevFacade: boolean | undefined;
44
56
  }): EsbuildBundle | undefined {
45
57
  const [bundle, setBundle] = useState<EsbuildBundle>();
46
58
  const { exit } = useApp();
@@ -76,12 +88,14 @@ export function useEsbuild({
76
88
  bundleType,
77
89
  modules,
78
90
  stop,
91
+ sourceMapPath,
79
92
  }: Awaited<ReturnType<typeof bundleWorker>> = noBundle
80
93
  ? {
81
94
  modules: [],
82
95
  resolvedEntryPointPath: entry.file,
83
96
  bundleType: entry.format === "modules" ? "esm" : "commonjs",
84
97
  stop: undefined,
98
+ sourceMapPath: undefined,
85
99
  }
86
100
  : await bundleWorker(entry, destination, {
87
101
  serveAssetsFromWorker,
@@ -94,6 +108,14 @@ export function useEsbuild({
94
108
  nodeCompat,
95
109
  define,
96
110
  checkFetch: true,
111
+ assets: assets && {
112
+ ...assets,
113
+ // disable the cache in dev
114
+ bypassCache: true,
115
+ },
116
+ workerDefinitions,
117
+ services,
118
+ firstPartyWorkerDevFacade,
97
119
  });
98
120
 
99
121
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -119,6 +141,7 @@ export function useEsbuild({
119
141
  path: resolvedEntryPointPath,
120
142
  type: bundleType,
121
143
  modules,
144
+ sourceMapPath,
122
145
  });
123
146
  }
124
147
 
@@ -145,6 +168,11 @@ export function useEsbuild({
145
168
  minify,
146
169
  nodeCompat,
147
170
  define,
171
+ assets,
172
+ services,
173
+ durableObjects,
174
+ workerDefinitions,
175
+ firstPartyWorkerDevFacade,
148
176
  ]);
149
177
  return bundle;
150
178
  }
@@ -0,0 +1,31 @@
1
+ import type { DevProps } from "./dev";
2
+
3
+ export function validateDevProps(props: DevProps) {
4
+ if (
5
+ !props.isWorkersSite &&
6
+ props.assetPaths &&
7
+ props.entry.format === "service-worker"
8
+ ) {
9
+ throw new Error(
10
+ "You cannot use the service-worker format with an `assets` directory yet. For information on how to migrate to the module-worker format, see: https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
11
+ );
12
+ }
13
+
14
+ if (props.bindings.wasm_modules && props.entry.format === "modules") {
15
+ throw new Error(
16
+ "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
17
+ );
18
+ }
19
+
20
+ if (props.bindings.text_blobs && props.entry.format === "modules") {
21
+ throw new Error(
22
+ "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
23
+ );
24
+ }
25
+
26
+ if (props.bindings.data_blobs && props.entry.format === "modules") {
27
+ throw new Error(
28
+ "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,160 @@
1
+ import http from "http";
2
+ import net from "net";
3
+ import bodyParser from "body-parser";
4
+ import express from "express";
5
+ import { createHttpTerminator } from "http-terminator";
6
+ import { fetch } from "undici";
7
+ import { logger } from "./logger";
8
+ import type { Server } from "http";
9
+ import type { HttpTerminator } from "http-terminator";
10
+
11
+ const DEV_REGISTRY_PORT = "6284";
12
+ const DEV_REGISTRY_HOST = `http://localhost:${DEV_REGISTRY_PORT}`;
13
+
14
+ let server: Server;
15
+ let terminator: HttpTerminator;
16
+
17
+ export type WorkerRegistry = Record<string, WorkerDefinition>;
18
+
19
+ type WorkerDefinition = {
20
+ port: number | undefined;
21
+ protocol: "http" | "https" | undefined;
22
+ host: string | undefined;
23
+ mode: "local" | "remote";
24
+ headers?: Record<string, string>;
25
+ durableObjects: { name: string; className: string }[];
26
+ durableObjectsHost?: string;
27
+ durableObjectsPort?: number;
28
+ };
29
+
30
+ /**
31
+ * A helper function to check whether our service registry is already running
32
+ */
33
+ async function isPortAvailable() {
34
+ return new Promise((resolve, reject) => {
35
+ const netServer = net
36
+ .createServer()
37
+ .once("error", (err) => {
38
+ netServer.close();
39
+ if ((err as unknown as { code: string }).code === "EADDRINUSE") {
40
+ resolve(false);
41
+ } else {
42
+ reject(err);
43
+ }
44
+ })
45
+ .once("listening", () => {
46
+ netServer.close();
47
+ resolve(true);
48
+ });
49
+ netServer.listen(DEV_REGISTRY_PORT);
50
+ });
51
+ }
52
+
53
+ const jsonBodyParser = bodyParser.json();
54
+
55
+ /**
56
+ * Start the service registry. It's a simple server
57
+ * that exposes endpoints for registering and unregistering
58
+ * services, as well as getting the state of the registry.
59
+ */
60
+ export async function startWorkerRegistry() {
61
+ if ((await isPortAvailable()) && !server) {
62
+ const app = express();
63
+
64
+ let workers: WorkerRegistry = {};
65
+ app
66
+ .get("/workers", async (req, res) => {
67
+ res.json(workers);
68
+ })
69
+ .post("/workers/:workerId", jsonBodyParser, async (req, res) => {
70
+ workers[req.params.workerId] = req.body;
71
+ res.json(null);
72
+ })
73
+ .delete(`/workers/:workerId`, async (req, res) => {
74
+ delete workers[req.params.workerId];
75
+ res.json(null);
76
+ })
77
+ .delete("/workers", async (req, res) => {
78
+ workers = {};
79
+ res.json(null);
80
+ });
81
+ server = http.createServer(app);
82
+ terminator = createHttpTerminator({ server });
83
+ server.listen(DEV_REGISTRY_PORT);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Stop the service registry.
89
+ */
90
+ export async function stopWorkerRegistry() {
91
+ await terminator?.terminate();
92
+ }
93
+
94
+ /**
95
+ * Register a worker in the registry.
96
+ */
97
+ export async function registerWorker(
98
+ name: string,
99
+ definition: WorkerDefinition
100
+ ) {
101
+ try {
102
+ return await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ },
107
+ body: JSON.stringify(definition),
108
+ });
109
+ } catch (e) {
110
+ if (
111
+ !["ECONNRESET", "ECONNREFUSED"].includes(
112
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
113
+ )
114
+ ) {
115
+ logger.error("Failed to register worker in local service registry", e);
116
+ } else {
117
+ logger.debug("Failed to register worker in local service registry", e);
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Unregister a worker from the registry.
124
+ */
125
+ export async function unregisterWorker(name: string) {
126
+ try {
127
+ await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
128
+ method: "DELETE",
129
+ });
130
+ } catch (e) {
131
+ if (
132
+ !["ECONNRESET", "ECONNREFUSED"].includes(
133
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
134
+ )
135
+ ) {
136
+ throw e;
137
+ // logger.error("failed to unregister worker", e);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get the state of the service registry.
144
+ */
145
+ export async function getRegisteredWorkers(): Promise<
146
+ WorkerRegistry | undefined
147
+ > {
148
+ try {
149
+ const response = await fetch(`${DEV_REGISTRY_HOST}/workers`);
150
+ return (await response.json()) as WorkerRegistry;
151
+ } catch (e) {
152
+ if (
153
+ !["ECONNRESET", "ECONNREFUSED"].includes(
154
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
155
+ )
156
+ ) {
157
+ throw e;
158
+ }
159
+ }
160
+ }