wrangler 2.0.21 → 2.0.24

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 (65) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +527 -5
  4. package/package.json +18 -5
  5. package/src/__tests__/configuration.test.ts +88 -16
  6. package/src/__tests__/dev.test.tsx +95 -4
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +54 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/jest.setup.ts +31 -1
  11. package/src/__tests__/kv.test.ts +82 -61
  12. package/src/__tests__/metrics.test.ts +35 -0
  13. package/src/__tests__/publish.test.ts +573 -254
  14. package/src/__tests__/r2.test.ts +155 -71
  15. package/src/__tests__/user.test.ts +1 -0
  16. package/src/__tests__/validate-dev-props.test.ts +56 -0
  17. package/src/__tests__/version.test.ts +35 -0
  18. package/src/__tests__/whoami.test.tsx +60 -1
  19. package/src/api/dev.ts +43 -9
  20. package/src/bundle.ts +297 -37
  21. package/src/cfetch/internal.ts +34 -2
  22. package/src/config/config.ts +14 -2
  23. package/src/config/environment.ts +40 -8
  24. package/src/config/index.ts +13 -0
  25. package/src/config/validation.ts +110 -8
  26. package/src/create-worker-preview.ts +3 -1
  27. package/src/create-worker-upload-form.ts +25 -0
  28. package/src/dev/dev.tsx +135 -31
  29. package/src/dev/local.tsx +48 -20
  30. package/src/dev/remote.tsx +39 -12
  31. package/src/dev/use-esbuild.ts +25 -0
  32. package/src/dev/validate-dev-props.ts +31 -0
  33. package/src/dev-registry.tsx +157 -0
  34. package/src/dev.tsx +137 -65
  35. package/src/generate.ts +112 -14
  36. package/src/index.tsx +222 -7
  37. package/src/inspect.ts +93 -5
  38. package/src/metrics/index.ts +1 -0
  39. package/src/metrics/is-ci.ts +14 -0
  40. package/src/metrics/metrics-config.ts +19 -2
  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 +543 -0
  45. package/src/miniflare-cli/index.ts +36 -4
  46. package/src/module-collection.ts +3 -3
  47. package/src/pages/constants.ts +1 -0
  48. package/src/pages/deployments.tsx +1 -1
  49. package/src/pages/dev.tsx +85 -639
  50. package/src/pages/publish.tsx +1 -1
  51. package/src/pages/upload.tsx +32 -13
  52. package/src/publish.ts +139 -112
  53. package/src/r2.ts +68 -0
  54. package/src/user/choose-account.tsx +20 -11
  55. package/src/user/user.tsx +20 -2
  56. package/src/whoami.tsx +79 -1
  57. package/src/worker.ts +12 -0
  58. package/templates/first-party-worker-module-facade.ts +18 -0
  59. package/templates/format-dev-errors.ts +32 -0
  60. package/templates/pages-shim.ts +9 -0
  61. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  62. package/templates/service-bindings-module-facade.js +51 -0
  63. package/templates/service-bindings-sw-facade.js +39 -0
  64. package/wrangler-dist/cli.d.ts +32 -3
  65. package/wrangler-dist/cli.js +45257 -25209
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 { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
12
14
  import type { AssetPaths } from "../sites";
13
15
  import type { CfWorkerInit, CfScriptFormat } from "../worker";
14
16
  import type { EsbuildBundle } from "./use-esbuild";
@@ -22,18 +24,20 @@ interface LocalProps {
22
24
  compatibilityFlags: string[] | undefined;
23
25
  bindings: CfWorkerInit["bindings"];
24
26
  assetPaths: AssetPaths | undefined;
25
- isWorkersSite: boolean;
26
27
  port: number;
27
28
  ip: string;
28
29
  rules: Config["rules"];
29
30
  inspectorPort: number;
30
31
  enableLocalPersistence: boolean;
32
+ liveReload: boolean;
31
33
  crons: Config["triggers"]["crons"];
32
34
  localProtocol: "http" | "https";
33
35
  localUpstream: string | undefined;
34
36
  inspect: boolean;
35
37
  onReady: (() => void) | undefined;
36
38
  logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
39
+ logPrefix?: string;
40
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
37
41
  }
38
42
 
39
43
  export function Local(props: LocalProps) {
@@ -54,10 +58,11 @@ function useLocalWorker({
54
58
  compatibilityFlags,
55
59
  bindings,
56
60
  assetPaths,
57
- isWorkersSite,
58
61
  port,
62
+ inspectorPort,
59
63
  rules,
60
64
  enableLocalPersistence,
65
+ liveReload,
61
66
  ip,
62
67
  crons,
63
68
  localProtocol,
@@ -65,6 +70,8 @@ function useLocalWorker({
65
70
  inspect,
66
71
  onReady,
67
72
  logLevel,
73
+ logPrefix,
74
+ enablePagesAssetsServiceBinding,
68
75
  }: LocalProps) {
69
76
  // TODO: pass vars via command line
70
77
  const local = useRef<ChildProcess>();
@@ -80,6 +87,15 @@ function useLocalWorker({
80
87
  // so that it's persisted in the temp dir across a dev session
81
88
  // even when we change source and reload
82
89
  null;
90
+
91
+ useEffect(() => {
92
+ if (bindings.services && bindings.services.length > 0) {
93
+ logger.warn(
94
+ "⎔ Support for service bindings in local mode is experimental and may change."
95
+ );
96
+ }
97
+ }, [bindings.services]);
98
+
83
99
  useEffect(() => {
84
100
  const abortController = new AbortController();
85
101
  async function startLocalWorker() {
@@ -92,12 +108,6 @@ function useLocalWorker({
92
108
  abortSignal: abortController.signal,
93
109
  });
94
110
 
95
- if (bindings.services && bindings.services.length > 0) {
96
- throw new Error(
97
- "⎔ Service bindings are not yet supported in local mode."
98
- );
99
- }
100
-
101
111
  // In local mode, we want to copy all referenced modules into
102
112
  // the output bundle directory before starting up
103
113
  for (const module of bundle.modules) {
@@ -212,6 +222,7 @@ function useLocalWorker({
212
222
  r2Persist: true,
213
223
  }),
214
224
 
225
+ liveReload,
215
226
  sitePath: assetPaths?.assetDirectory
216
227
  ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
217
228
  : undefined,
@@ -230,6 +241,7 @@ function useLocalWorker({
230
241
  crons,
231
242
  upstream,
232
243
  disableLogs: logLevel === "none",
244
+ logOptions: logPrefix ? { prefix: logPrefix } : undefined,
233
245
  };
234
246
 
235
247
  // The path to the Miniflare CLI assumes that this file is being run from
@@ -248,19 +260,32 @@ function useLocalWorker({
248
260
  // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
249
261
  ];
250
262
  if (inspect) {
251
- nodeOptions.push("--inspect"); // start Miniflare listening for a debugger to attach
263
+ nodeOptions.push("--inspect=" + `${ip}:${inspectorPort}`); // start Miniflare listening for a debugger to attach
252
264
  }
253
- const child = (local.current = fork(
254
- miniflareCLIPath,
255
- [miniflareOptions],
256
- {
257
- cwd: path.dirname(scriptPath),
258
- execArgv: nodeOptions,
259
- stdio: "pipe",
260
- }
261
- ));
262
- child.on("message", (message) => {
265
+
266
+ const forkOptions = [miniflareOptions];
267
+
268
+ if (enablePagesAssetsServiceBinding) {
269
+ forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
270
+ }
271
+
272
+ const child = (local.current = fork(miniflareCLIPath, forkOptions, {
273
+ cwd: path.dirname(scriptPath),
274
+ execArgv: nodeOptions,
275
+ stdio: "pipe",
276
+ }));
277
+
278
+ child.on("message", async (message) => {
263
279
  if (message === "ready") {
280
+ // Let's register our presence in the dev registry
281
+ if (workerName) {
282
+ await registerWorker(workerName, {
283
+ protocol: localProtocol,
284
+ mode: "local",
285
+ port,
286
+ host: ip,
287
+ });
288
+ }
264
289
  onReady?.();
265
290
  }
266
291
  });
@@ -331,6 +356,7 @@ function useLocalWorker({
331
356
  workerName,
332
357
  format,
333
358
  port,
359
+ inspectorPort,
334
360
  ip,
335
361
  bindings.durable_objects?.bindings,
336
362
  bindings.kv_namespaces,
@@ -340,8 +366,8 @@ function useLocalWorker({
340
366
  compatibilityDate,
341
367
  compatibilityFlags,
342
368
  localPersistencePath,
369
+ liveReload,
343
370
  assetPaths,
344
- isWorkersSite,
345
371
  rules,
346
372
  bindings.wasm_modules,
347
373
  bindings.text_blobs,
@@ -351,7 +377,9 @@ function useLocalWorker({
351
377
  localUpstream,
352
378
  inspect,
353
379
  logLevel,
380
+ logPrefix,
354
381
  onReady,
382
+ enablePagesAssetsServiceBinding,
355
383
  ]);
356
384
  return { inspectorUrl };
357
385
  }
@@ -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[]>>();
@@ -69,7 +71,6 @@ export function Remote(props: {
69
71
  bindings: props.bindings,
70
72
  assetPaths: props.assetPaths,
71
73
  isWorkersSite: props.isWorkersSite,
72
- port: props.port,
73
74
  compatibilityDate: props.compatibilityDate,
74
75
  compatibilityFlags: props.compatibilityFlags,
75
76
  usageModel: props.usageModel,
@@ -79,6 +80,7 @@ export function Remote(props: {
79
80
  host: props.host,
80
81
  routes: props.routes,
81
82
  onReady: props.onReady,
83
+ sendMetrics: props.sendMetrics,
82
84
  });
83
85
 
84
86
  usePreviewServer({
@@ -98,6 +100,7 @@ export function Remote(props: {
98
100
  : undefined,
99
101
  port: props.inspectorPort,
100
102
  logToTerminal: true,
103
+ sourceMapPath: props.sourceMapPath,
101
104
  });
102
105
 
103
106
  const errorHandler = useErrorHandler();
@@ -153,7 +156,6 @@ export function useWorker(props: {
153
156
  bindings: CfWorkerInit["bindings"];
154
157
  assetPaths: AssetPaths | undefined;
155
158
  isWorkersSite: boolean;
156
- port: number;
157
159
  compatibilityDate: string | undefined;
158
160
  compatibilityFlags: string[] | undefined;
159
161
  usageModel: "bundled" | "unbound" | undefined;
@@ -163,6 +165,7 @@ export function useWorker(props: {
163
165
  host: string | undefined;
164
166
  routes: Route[] | undefined;
165
167
  onReady: (() => void) | undefined;
168
+ sendMetrics: boolean | undefined;
166
169
  }): CfPreviewToken | undefined {
167
170
  const {
168
171
  name,
@@ -175,7 +178,6 @@ export function useWorker(props: {
175
178
  compatibilityDate,
176
179
  compatibilityFlags,
177
180
  usageModel,
178
- port,
179
181
  onReady,
180
182
  } = props;
181
183
  const [session, setSession] = useState<CfPreviewSession | undefined>();
@@ -205,6 +207,7 @@ export function useWorker(props: {
205
207
  zone: props.zone,
206
208
  host: props.host,
207
209
  routes: props.routes,
210
+ sendMetrics: props.sendMetrics,
208
211
  };
209
212
 
210
213
  setSession(
@@ -233,6 +236,7 @@ export function useWorker(props: {
233
236
  props.legacyEnv,
234
237
  props.routes,
235
238
  props.zone,
239
+ props.sendMetrics,
236
240
  ]);
237
241
 
238
242
  // This effect uses the session to upload the worker and create a preview
@@ -323,17 +327,40 @@ export function useWorker(props: {
323
327
  zone: props.zone,
324
328
  host: props.host,
325
329
  routes: props.routes,
330
+ sendMetrics: props.sendMetrics,
326
331
  };
327
332
 
328
- setToken(
329
- await createWorkerPreview(
330
- init,
331
- workerAccount,
332
- workerCtx,
333
- session,
334
- abortController.signal
335
- )
333
+ const workerPreviewToken = await createWorkerPreview(
334
+ init,
335
+ workerAccount,
336
+ workerCtx,
337
+ session,
338
+ abortController.signal
336
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
+
337
364
  onReady?.();
338
365
  }
339
366
  start().catch((err) => {
@@ -366,7 +393,6 @@ export function useWorker(props: {
366
393
  bundle,
367
394
  format,
368
395
  accountId,
369
- port,
370
396
  assetPaths,
371
397
  props.isWorkersSite,
372
398
  compatibilityDate,
@@ -381,6 +407,7 @@ export function useWorker(props: {
381
407
  props.routes,
382
408
  session,
383
409
  onReady,
410
+ props.sendMetrics,
384
411
  ]);
385
412
  return token;
386
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,32 @@ 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
+ firstPartyWorkerDevFacade,
32
38
  }: {
33
39
  entry: Entry;
34
40
  destination: string | undefined;
35
41
  jsxFactory: string | undefined;
36
42
  jsxFragment: string | undefined;
37
43
  rules: Config["rules"];
44
+ assets: Config["assets"];
38
45
  define: Config["define"];
46
+ services: Config["services"];
39
47
  serveAssetsFromWorker: boolean;
40
48
  tsconfig: string | undefined;
41
49
  minify: boolean | undefined;
42
50
  nodeCompat: boolean | undefined;
43
51
  noBundle: boolean;
52
+ workerDefinitions: WorkerRegistry;
53
+ firstPartyWorkerDevFacade: boolean | undefined;
44
54
  }): EsbuildBundle | undefined {
45
55
  const [bundle, setBundle] = useState<EsbuildBundle>();
46
56
  const { exit } = useApp();
@@ -76,12 +86,14 @@ export function useEsbuild({
76
86
  bundleType,
77
87
  modules,
78
88
  stop,
89
+ sourceMapPath,
79
90
  }: Awaited<ReturnType<typeof bundleWorker>> = noBundle
80
91
  ? {
81
92
  modules: [],
82
93
  resolvedEntryPointPath: entry.file,
83
94
  bundleType: entry.format === "modules" ? "esm" : "commonjs",
84
95
  stop: undefined,
96
+ sourceMapPath: undefined,
85
97
  }
86
98
  : await bundleWorker(entry, destination, {
87
99
  serveAssetsFromWorker,
@@ -94,6 +106,14 @@ export function useEsbuild({
94
106
  nodeCompat,
95
107
  define,
96
108
  checkFetch: true,
109
+ assets: assets && {
110
+ ...assets,
111
+ // disable the cache in dev
112
+ bypassCache: true,
113
+ },
114
+ workerDefinitions,
115
+ services,
116
+ firstPartyWorkerDevFacade,
97
117
  });
98
118
 
99
119
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -119,6 +139,7 @@ export function useEsbuild({
119
139
  path: resolvedEntryPointPath,
120
140
  type: bundleType,
121
141
  modules,
142
+ sourceMapPath,
122
143
  });
123
144
  }
124
145
 
@@ -145,6 +166,10 @@ export function useEsbuild({
145
166
  minify,
146
167
  nodeCompat,
147
168
  define,
169
+ assets,
170
+ services,
171
+ workerDefinitions,
172
+ firstPartyWorkerDevFacade,
148
173
  ]);
149
174
  return bundle;
150
175
  }
@@ -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,157 @@
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
+ };
26
+
27
+ /**
28
+ * A helper function to check whether our service registry is already running
29
+ */
30
+ async function isPortAvailable() {
31
+ return new Promise((resolve, reject) => {
32
+ const netServer = net
33
+ .createServer()
34
+ .once("error", (err) => {
35
+ netServer.close();
36
+ if ((err as unknown as { code: string }).code === "EADDRINUSE") {
37
+ resolve(false);
38
+ } else {
39
+ reject(err);
40
+ }
41
+ })
42
+ .once("listening", () => {
43
+ netServer.close();
44
+ resolve(true);
45
+ });
46
+ netServer.listen(DEV_REGISTRY_PORT);
47
+ });
48
+ }
49
+
50
+ const jsonBodyParser = bodyParser.json();
51
+
52
+ /**
53
+ * Start the service registry. It's a simple server
54
+ * that exposes endpoints for registering and unregistering
55
+ * services, as well as getting the state of the registry.
56
+ */
57
+ export async function startWorkerRegistry() {
58
+ if ((await isPortAvailable()) && !server) {
59
+ const app = express();
60
+
61
+ let workers: WorkerRegistry = {};
62
+ app
63
+ .get("/workers", async (req, res) => {
64
+ res.json(workers);
65
+ })
66
+ .post("/workers/:workerId", jsonBodyParser, async (req, res) => {
67
+ workers[req.params.workerId] = req.body;
68
+ res.json(null);
69
+ })
70
+ .delete(`/workers/:workerId`, async (req, res) => {
71
+ delete workers[req.params.workerId];
72
+ res.json(null);
73
+ })
74
+ .delete("/workers", async (req, res) => {
75
+ workers = {};
76
+ res.json(null);
77
+ });
78
+ server = http.createServer(app);
79
+ terminator = createHttpTerminator({ server });
80
+ server.listen(DEV_REGISTRY_PORT);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Stop the service registry.
86
+ */
87
+ export async function stopWorkerRegistry() {
88
+ await terminator?.terminate();
89
+ }
90
+
91
+ /**
92
+ * Register a worker in the registry.
93
+ */
94
+ export async function registerWorker(
95
+ name: string,
96
+ definition: WorkerDefinition
97
+ ) {
98
+ try {
99
+ return await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ body: JSON.stringify(definition),
105
+ });
106
+ } catch (e) {
107
+ if (
108
+ !["ECONNRESET", "ECONNREFUSED"].includes(
109
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
110
+ )
111
+ ) {
112
+ logger.error("Failed to register worker in local service registry", e);
113
+ } else {
114
+ logger.debug("Failed to register worker in local service registry", e);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Unregister a worker from the registry.
121
+ */
122
+ export async function unregisterWorker(name: string) {
123
+ try {
124
+ await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
125
+ method: "DELETE",
126
+ });
127
+ } catch (e) {
128
+ if (
129
+ !["ECONNRESET", "ECONNREFUSED"].includes(
130
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
131
+ )
132
+ ) {
133
+ throw e;
134
+ // logger.error("failed to unregister worker", e);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Get the state of the service registry.
141
+ */
142
+ export async function getRegisteredWorkers(): Promise<
143
+ WorkerRegistry | undefined
144
+ > {
145
+ try {
146
+ const response = await fetch(`${DEV_REGISTRY_HOST}/workers`);
147
+ return (await response.json()) as WorkerRegistry;
148
+ } catch (e) {
149
+ if (
150
+ !["ECONNRESET", "ECONNREFUSED"].includes(
151
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
152
+ )
153
+ ) {
154
+ throw e;
155
+ }
156
+ }
157
+ }