elysia 2.0.0-beta.3 → 2.0.0-beta.5

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/dist/adapter/bun/index.d.ts +1 -1
  2. package/dist/adapter/bun/index.js +347 -90
  3. package/dist/adapter/bun/index.mjs +349 -92
  4. package/dist/adapter/utils.js +48 -33
  5. package/dist/adapter/utils.mjs +48 -33
  6. package/dist/base.d.ts +16 -5
  7. package/dist/base.js +173 -74
  8. package/dist/base.mjs +174 -75
  9. package/dist/compile/aot.d.ts +1 -0
  10. package/dist/compile/aot.js +3 -0
  11. package/dist/compile/aot.mjs +3 -0
  12. package/dist/compile/handler/frozen-validator.d.ts +4 -1
  13. package/dist/compile/handler/frozen-validator.js +8 -3
  14. package/dist/compile/handler/frozen-validator.mjs +5 -3
  15. package/dist/compile/handler/index.d.ts +1 -1
  16. package/dist/compile/handler/index.js +3 -3
  17. package/dist/compile/handler/index.mjs +3 -3
  18. package/dist/compile/handler/reconstruct.d.ts +1 -1
  19. package/dist/compile/handler/reconstruct.js +4 -3
  20. package/dist/compile/handler/reconstruct.mjs +4 -3
  21. package/dist/compile/jit-probe.d.ts +2 -2
  22. package/dist/context.d.ts +1 -1
  23. package/dist/context.js +5 -1
  24. package/dist/context.mjs +5 -1
  25. package/dist/index.d.ts +2 -2
  26. package/dist/package.js +1 -1
  27. package/dist/package.mjs +1 -1
  28. package/dist/plugin/aot/core.d.ts +11 -5
  29. package/dist/plugin/aot/core.js +86 -38
  30. package/dist/plugin/aot/core.mjs +87 -41
  31. package/dist/plugin/aot/esbuild.js +8 -2
  32. package/dist/plugin/aot/esbuild.mjs +9 -3
  33. package/dist/plugin/aot/hooks.d.ts +2 -1
  34. package/dist/plugin/aot/hooks.js +3 -3
  35. package/dist/plugin/aot/hooks.mjs +4 -4
  36. package/dist/plugin/aot/source.d.ts +4 -1
  37. package/dist/plugin/aot/source.js +21 -14
  38. package/dist/plugin/aot/source.mjs +21 -14
  39. package/dist/plugin/aot/worker.js +2 -2
  40. package/dist/plugin/aot/worker.mjs +2 -2
  41. package/dist/route-table.d.ts +3 -0
  42. package/dist/route-table.js +4 -1
  43. package/dist/route-table.mjs +4 -1
  44. package/dist/schema-snapshot.js +17 -22
  45. package/dist/schema-snapshot.mjs +17 -22
  46. package/dist/sucrose.js +6 -0
  47. package/dist/sucrose.mjs +6 -0
  48. package/dist/type/bridge-live.d.ts +1 -1
  49. package/dist/type/bridge.d.ts +1 -1
  50. package/dist/type/coerce.d.ts +1 -1
  51. package/dist/type/constants.d.ts +1 -1
  52. package/dist/type/validator/index.js +10 -7
  53. package/dist/type/validator/index.mjs +10 -7
  54. package/dist/type/validator/validator-cache.js +17 -9
  55. package/dist/type/validator/validator-cache.mjs +17 -9
  56. package/dist/types.d.ts +39 -5
  57. package/dist/utils.d.ts +10 -1
  58. package/dist/utils.js +8 -1
  59. package/dist/utils.mjs +6 -1
  60. package/dist/ws/context.d.ts +4 -1
  61. package/dist/ws/context.js +21 -3
  62. package/dist/ws/context.mjs +20 -3
  63. package/dist/ws/route.js +93 -21
  64. package/dist/ws/route.mjs +96 -24
  65. package/package.json +2 -2
@@ -1,13 +1,83 @@
1
1
  import { createAdapter } from "../index.mjs";
2
- import { isDynamicRegex, needEncodeRegex } from "../../constants.mjs";
3
- import { flattenChain, getLoosePath, nullObject } from "../../utils.mjs";
2
+ import { flattenChain, getLoosePath, isSocketQuiet, nullObject, throwLifecycleErrors } from "../../utils.mjs";
4
3
  import { WebStandardAdapter } from "../web-standard/index.mjs";
5
4
  import { frozenRootOf, resolvedWsOf } from "../../generation.mjs";
6
5
  import { origin } from "../origin.mjs";
7
6
  import { buildNativeStaticResponse } from "../../compile/handler/index.mjs";
8
- import { routeRow } from "../../route-table.mjs";
7
+ import { RouteFlag, routeRow } from "../../route-table.mjs";
9
8
 
10
9
  //#region src/adapter/bun/index.ts
10
+ function runLifecycle(epoch, callback, ...args) {
11
+ const invoking = epoch.invoking;
12
+ epoch.invoking = true;
13
+ try {
14
+ return callback(...args);
15
+ } finally {
16
+ epoch.invoking = invoking;
17
+ }
18
+ }
19
+ const unavailableFetch = () => new Response(null, {
20
+ headers: { connection: "close" },
21
+ status: 503
22
+ });
23
+ function reloadServer(server, serve) {
24
+ try {
25
+ server.reload(serve);
26
+ } catch (error) {
27
+ if (!serve.routes) throw error;
28
+ delete serve.routes;
29
+ console.warn("[Elysia] Native static promotion was skipped:", error);
30
+ try {
31
+ server.reload(serve);
32
+ } catch (fallbackError) {
33
+ console.error("[Elysia] Failed to reload Bun server:", fallbackError);
34
+ throw fallbackError;
35
+ }
36
+ }
37
+ }
38
+ function releaseLifecycle(app, ext, createdExt, stop, registerCleanup) {
39
+ if (ext.stop === stop) delete ext.stop;
40
+ if (ext.cleanupEpoch === registerCleanup) delete ext.cleanupEpoch;
41
+ if (createdExt && app["~ext"] === ext && !Object.keys(ext).length) app["~ext"] = void 0;
42
+ }
43
+ function clearAppServer(app, server) {
44
+ if (app.server === server) app.server = void 0;
45
+ }
46
+ async function waitForServerRequests(server, abandoned) {
47
+ while (server.pendingRequests && !abandoned()) await Bun.sleep(1);
48
+ }
49
+ async function settleServerWebSockets(lifecycle, errors) {
50
+ if (!lifecycle) return true;
51
+ lifecycle.closing = true;
52
+ let failed = false;
53
+ do {
54
+ for (const socket of lifecycle.sockets) if (socket.readyState < 3) try {
55
+ socket.terminate();
56
+ } catch (error) {
57
+ errors.push(error);
58
+ failed = true;
59
+ }
60
+ if (failed) return false;
61
+ await Bun.sleep(1);
62
+ const abandon = lifecycle.abandon === true;
63
+ for (const socket of lifecycle.sockets) if (abandon || isSocketQuiet(socket)) {
64
+ delete socket.data["~lifecycleRun"];
65
+ lifecycle.sockets.delete(socket);
66
+ }
67
+ } while (lifecycle.sockets.size);
68
+ return true;
69
+ }
70
+ const closeServerIdle = (server, errors) => {
71
+ const close = server.closeIdleConnections;
72
+ if (!close) return true;
73
+ try {
74
+ close.call(server);
75
+ return true;
76
+ } catch (error) {
77
+ errors.push(error);
78
+ return false;
79
+ }
80
+ };
11
81
  /**
12
82
  * ! This may looks like it would cause race condition, but it is not
13
83
  * Bun is single-threaded and synchronous, so the `finally` block will
@@ -25,15 +95,7 @@ const withOrigin = (fetch) => (request, server) => {
25
95
  origin.request = void 0;
26
96
  }
27
97
  };
28
- const nativeStaticMethods = new Set([
29
- "GET",
30
- "POST",
31
- "PUT",
32
- "DELETE",
33
- "PATCH",
34
- "HEAD",
35
- "OPTIONS"
36
- ]);
98
+ const isNativeStaticMethod = (method) => method === "GET" || method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH" || method === "HEAD" || method === "OPTIONS";
37
99
  function collectStaticRoutes(app) {
38
100
  if (app["~config"]?.nativeStaticResponse === false) return;
39
101
  app.fetch;
@@ -43,59 +105,58 @@ function collectStaticRoutes(app) {
43
105
  const table = app["~generation"]?.routeTable ?? app["~routeTable"];
44
106
  const length = table?.length ?? 0;
45
107
  if (!table || !length) return;
46
- const methods = table.method;
47
- const paths = table.path;
48
- const handlers = table.handler;
108
+ const { method: methods, path: paths, handler: handlers, flags } = table;
49
109
  let hasCandidate = false;
50
110
  for (let i = 0; i < length; i++) {
111
+ if (!isNativeStaticMethod(methods[i]) || (flags[i] & RouteFlag.Dynamic) !== 0) continue;
51
112
  const h = handlers[i];
52
- if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
53
- if (!nativeStaticMethods.has(methods[i])) continue;
54
- hasCandidate = true;
55
- break;
113
+ if (typeof h !== "function" && !(h instanceof Error) && !(h instanceof Promise)) {
114
+ hasCandidate = true;
115
+ break;
116
+ }
56
117
  }
57
118
  if (!hasCandidate) return;
58
- const ready = nullObject();
59
119
  const strictPath = frozenRoot["~config"]?.strictPath === true;
60
- const seen = /* @__PURE__ */ new Map();
61
- for (let i = 0; i < length; i++) seen.set(methods[i] + " " + paths[i], i);
62
- let explicitPaths;
63
- if (!strictPath) {
64
- explicitPaths = /* @__PURE__ */ new Map();
65
- for (let i = 0; i < length; i++) {
66
- const method = methods[i];
67
- const path = paths[i];
68
- let set = explicitPaths.get(method);
69
- if (!set) explicitPaths.set(method, set = /* @__PURE__ */ new Set());
70
- set.add(path);
71
- if (needEncodeRegex.test(path)) {
72
- const encoded = encodeURI(path);
73
- if (encoded !== path) set.add(encoded);
74
- }
120
+ const routeIndex = /* @__PURE__ */ new Map();
121
+ for (let i = 0; i < length; i++) {
122
+ const method = methods[i];
123
+ if (!isNativeStaticMethod(method) || (flags[i] & RouteFlag.Dynamic) !== 0) continue;
124
+ const path = paths[i];
125
+ let pathsByMethod = routeIndex.get(method);
126
+ if (!pathsByMethod) routeIndex.set(method, pathsByMethod = /* @__PURE__ */ new Map());
127
+ pathsByMethod.set(path, i);
128
+ if (!strictPath && (flags[i] & RouteFlag.Encode) !== 0) {
129
+ const encoded = encodeURI(path);
130
+ if (encoded !== path && !pathsByMethod.has(encoded)) pathsByMethod.set(encoded, -1);
75
131
  }
76
132
  }
77
- const add = (method, path, value) => {
78
- if (needEncodeRegex.test(path)) path = encodeURI(path);
133
+ const ready = nullObject();
134
+ let hasReady = false;
135
+ const add = (method, path, value, needsEncode) => {
136
+ if (needsEncode) path = encodeURI(path);
79
137
  (ready[path] ??= nullObject())[method] = value;
138
+ hasReady = true;
80
139
  };
81
140
  for (let i = 0; i < length; i++) {
82
141
  const method = methods[i];
83
- if (method === "WS") continue;
142
+ const routeFlags = flags[i];
143
+ if (!isNativeStaticMethod(method) || (routeFlags & RouteFlag.Dynamic) !== 0) continue;
144
+ const pathsByMethod = routeIndex.get(method);
84
145
  const path = paths[i];
85
- if (seen.get(method + " " + path) !== i) continue;
86
- if (!nativeStaticMethods.has(method)) continue;
146
+ if (pathsByMethod.get(path) !== i) continue;
87
147
  const h = handlers[i];
88
148
  if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
89
149
  const value = buildNativeStaticResponse(routeRow(table, i), app);
90
150
  if (!value) continue;
91
- add(method, path, value);
92
- if (!strictPath && !isDynamicRegex.test(path)) {
151
+ const needsEncode = (routeFlags & RouteFlag.Encode) !== 0;
152
+ add(method, path, value, needsEncode);
153
+ if (!strictPath) {
93
154
  const loose = getLoosePath(path);
94
- if (loose !== path && !explicitPaths?.get(method)?.has(loose)) add(method, loose, value);
155
+ if (loose !== path && !pathsByMethod.has(loose)) add(method, loose, value, needsEncode);
95
156
  }
96
157
  }
97
- if (!Object.keys(ready).length) return;
98
- return [ready, []];
158
+ if (!hasReady) return;
159
+ return ready;
99
160
  }
100
161
  const BunAdapter = createAdapter({
101
162
  name: "bun",
@@ -104,23 +165,41 @@ const BunAdapter = createAdapter({
104
165
  parse: WebStandardAdapter.parse,
105
166
  response: WebStandardAdapter.response,
106
167
  listen(app, options, callback) {
107
- const _config = app["~config"]?.serve;
168
+ if (app.server || app["~ext"]?.stop) throw new Error("[Elysia] Cannot call listen() while a server or teardown is active");
169
+ function gatedFetch(request, server) {
170
+ return live ? live(request, server) : Promise.resolve(ready).then(async () => {
171
+ if (!live) {
172
+ await startupShutdown;
173
+ throw new Error("[Elysia] Server was stopped before it was ready");
174
+ }
175
+ return live(request, server);
176
+ });
177
+ }
108
178
  const optionsIsObject = typeof options === "object";
109
- let live;
110
- const gatedFetch = (request, server) => live ? live(request, server) : Promise.resolve(ready).then(() => {
111
- if (!live) throw new Error("[Elysia] Server was stopped before it was ready");
112
- return live(request, server);
113
- });
114
179
  const _options = optionsIsObject ? { ...options } : {
115
180
  port: +options,
116
181
  fetch: gatedFetch
117
182
  };
118
183
  if (optionsIsObject) _options.fetch = gatedFetch;
184
+ const _config = app["~config"]?.serve;
119
185
  const serve = _config ? {
120
186
  ..._config,
121
187
  ..._options
122
188
  } : _options;
189
+ const server = app.server = Bun.serve(serve.routes || serve.error ? {
190
+ ...serve,
191
+ error: unavailableFetch,
192
+ routes: {}
193
+ } : serve);
194
+ let live;
195
+ let cancelled = false;
196
+ let startupShutdown;
197
+ let shutdownAttempt;
123
198
  let ready;
199
+ let wsLifecycle;
200
+ let modulesReady;
201
+ let built;
202
+ let pendingSetups;
124
203
  const build = () => {
125
204
  const fetch = app.fetch;
126
205
  let routes;
@@ -133,7 +212,17 @@ const BunAdapter = createAdapter({
133
212
  if (app["~hasWS"]) {
134
213
  const resolved = resolvedWsOf(app);
135
214
  if (!resolved) throw new Error("[Elysia] internal: WebSocket routes are present but no capability provider was resolved");
136
- websocket = resolved.config ? Object.assign(resolved.provider.buildGlobalWSHandler(), resolved.config) : resolved.provider.buildGlobalWSHandler();
215
+ const handler = resolved.provider.buildGlobalWSHandler();
216
+ websocket = resolved.config ? Object.assign(handler, resolved.config) : handler;
217
+ const setter = Object.getOwnPropertyDescriptor(handler, "~lifecycle")?.set;
218
+ if (setter) {
219
+ wsLifecycle = {
220
+ closing: false,
221
+ sockets: /* @__PURE__ */ new Set(),
222
+ run: runLifecycle.bind(null, epoch)
223
+ };
224
+ setter.call(handler, wsLifecycle);
225
+ }
137
226
  }
138
227
  return {
139
228
  fetch,
@@ -141,75 +230,243 @@ const BunAdapter = createAdapter({
141
230
  websocket
142
231
  };
143
232
  };
144
- let built;
145
- const server = app.server = Bun.serve(serve);
146
- const reload = () => {
233
+ let startupFailure;
234
+ let forceRequested = false;
235
+ let forceDone = false;
236
+ let nativeStopped = false;
237
+ let abandoned = false;
238
+ let published = false;
239
+ let cleanupCompleted = false;
240
+ let idleRequired = false;
241
+ let cleanupFailures;
242
+ let observedOutcome;
243
+ let persistentCleanup;
244
+ let persistentCleanupLength = -1;
245
+ const epochCleanup = [];
246
+ const createdExt = app["~ext"] === void 0;
247
+ const ext = app["~ext"] ??= nullObject();
248
+ const epoch = {
249
+ setup: false,
250
+ invoking: false
251
+ };
252
+ const snapshotCleanup = () => {
253
+ if (persistentCleanupLength !== -1) return;
254
+ persistentCleanup = ext.cleanup;
255
+ persistentCleanupLength = persistentCleanup?.length ?? 0;
256
+ };
257
+ const registerCleanup = (handler) => {
258
+ if (ext.cleanupEpoch !== registerCleanup) return false;
259
+ if (!epoch.setup) throw new Error("[Elysia] .cleanup() called after its setup epoch settled");
260
+ if (Array.isArray(handler)) epochCleanup.push(...handler);
261
+ else epochCleanup.push(handler);
262
+ return true;
263
+ };
264
+ const gate = () => {
265
+ if (!published) return;
266
+ live = void 0;
267
+ if (wsLifecycle) wsLifecycle.closing = true;
147
268
  try {
148
- server.reload(serve);
269
+ server.reload({
270
+ ...serve,
271
+ fetch: unavailableFetch,
272
+ error: unavailableFetch,
273
+ routes: {}
274
+ });
149
275
  } catch (error) {
150
- if (!serve.routes) throw error;
151
- delete serve.routes;
152
- console.warn("[Elysia] Native static promotion was skipped:", error);
153
- try {
154
- server.reload(serve);
155
- } catch (fallbackError) {
156
- console.error("[Elysia] Failed to reload Bun server:", fallbackError);
157
- throw fallbackError;
276
+ return { error };
277
+ }
278
+ };
279
+ const nativeStop = async (closeActiveConnections, errors) => {
280
+ try {
281
+ await server.stop(closeActiveConnections);
282
+ nativeStopped = true;
283
+ if (closeActiveConnections) {
284
+ forceDone = true;
285
+ forceRequested = false;
286
+ }
287
+ return true;
288
+ } catch (error) {
289
+ if (closeActiveConnections) forceRequested = true;
290
+ errors.push(error);
291
+ return false;
292
+ }
293
+ };
294
+ const isAbandoned = () => abandoned;
295
+ const quiesce = async (force) => {
296
+ const errors = [];
297
+ if (!published) return {
298
+ errors,
299
+ releasable: await nativeStop(true, errors),
300
+ safe: true
301
+ };
302
+ if (force && !idleRequired && !wsLifecycle?.closing) {
303
+ const stopped = await nativeStop(true, errors);
304
+ return {
305
+ errors,
306
+ releasable: stopped,
307
+ safe: stopped
308
+ };
309
+ }
310
+ const gateFailure = gate();
311
+ if (gateFailure) errors.push(gateFailure.error);
312
+ let socketsSettled = await settleServerWebSockets(wsLifecycle, errors);
313
+ await Promise.resolve();
314
+ let close = forceRequested || gateFailure !== void 0 || !socketsSettled;
315
+ if (!close && nativeStopped) await waitForServerRequests(server, isAbandoned);
316
+ idleRequired = true;
317
+ let stopped = true;
318
+ while (!nativeStopped || forceRequested && !forceDone) {
319
+ if (nativeStopped) close = true;
320
+ stopped = await nativeStop(close, errors);
321
+ if (stopped) {
322
+ if (socketsSettled) socketsSettled = await settleServerWebSockets(wsLifecycle, errors);
323
+ continue;
158
324
  }
325
+ if (close) break;
326
+ await waitForServerRequests(server, isAbandoned);
327
+ close = true;
159
328
  }
329
+ let idleClosed = true;
330
+ if (idleRequired) {
331
+ idleClosed = closeServerIdle(server, errors);
332
+ if (idleClosed && !gateFailure) idleRequired = false;
333
+ }
334
+ const safe = socketsSettled && stopped && idleClosed && (!gateFailure || forceDone);
335
+ return {
336
+ errors,
337
+ releasable: safe,
338
+ safe
339
+ };
160
340
  };
161
341
  const setup = () => {
162
- const onSetup = app["~ext"]?.setup;
342
+ snapshotCleanup();
343
+ const onSetup = ext.setup;
163
344
  if (!onSetup) return;
164
- let pendingSetups;
345
+ epoch.setup = true;
346
+ ext.cleanupEpoch = registerCleanup;
165
347
  for (let i = 0; i < onSetup.length; i++) {
166
- const result = onSetup[i](app);
167
- if (result && typeof result.then === "function") (pendingSetups ??= []).push(result);
348
+ if (cancelled) break;
349
+ try {
350
+ const result = runLifecycle(epoch, onSetup[i], app);
351
+ if (result && typeof result.then === "function") (pendingSetups ??= []).push(Promise.resolve(result));
352
+ } catch (error) {
353
+ if (!pendingSetups) epoch.setup = false;
354
+ stop(true, { error }).catch(() => {});
355
+ throw error;
356
+ }
168
357
  }
169
- if (pendingSetups) return Promise.all(pendingSetups);
358
+ if (pendingSetups) return Promise.all(pendingSetups).then(() => epoch.setup = false);
359
+ epoch.setup = false;
170
360
  };
171
- const rollback = (error) => {
172
- if (app.server !== server) return;
173
- try {
174
- server.stop(true);
175
- } catch (stopError) {
176
- console.error(stopError);
177
- } finally {
178
- app.server = void 0;
361
+ const stop = (closeActiveConnections, failure) => {
362
+ const reentrant = epoch.invoking;
363
+ if (failure) startupFailure ??= failure;
364
+ if (closeActiveConnections === true && !forceDone) forceRequested = true;
365
+ cancelled = true;
366
+ clearAppServer(app, server);
367
+ let outcome;
368
+ if (shutdownAttempt) {
369
+ if (closeActiveConnections === true && !forceDone) {
370
+ abandoned = true;
371
+ if (wsLifecycle) wsLifecycle.abandon = true;
372
+ }
373
+ outcome = shutdownAttempt;
374
+ } else {
375
+ const { promise, resolve, reject } = Promise.withResolvers();
376
+ outcome = shutdownAttempt = promise;
377
+ const quiescence = quiesce(forceRequested || !published);
378
+ let releasable = false;
379
+ (async () => {
380
+ try {
381
+ await Promise.resolve();
382
+ if (modulesReady) try {
383
+ await modulesReady;
384
+ } catch (error) {
385
+ startupFailure ??= { error };
386
+ }
387
+ if (pendingSetups) {
388
+ const setupResults = await Promise.allSettled(pendingSetups);
389
+ if (!startupFailure) for (let i = 0; i < setupResults.length; i++) {
390
+ const result = setupResults[i];
391
+ if (result.status === "rejected") {
392
+ startupFailure = { error: result.reason };
393
+ break;
394
+ }
395
+ }
396
+ }
397
+ epoch.setup = false;
398
+ const errors = [];
399
+ const quiesced = await quiescence;
400
+ if (startupFailure) errors.push(startupFailure.error);
401
+ errors.push(...quiesced.errors);
402
+ releasable = quiesced.releasable;
403
+ if (!quiesced.safe) {
404
+ if (cleanupFailures) errors.push(...cleanupFailures);
405
+ throwLifecycleErrors(errors);
406
+ }
407
+ if (!cleanupCompleted) {
408
+ snapshotCleanup();
409
+ if (ext.stop === stop) {
410
+ const length = persistentCleanupLength + epochCleanup.length;
411
+ for (let n = 0; n < length; n++) {
412
+ if (ext.stop !== stop) break;
413
+ const i = startupFailure ? length - 1 - n : n;
414
+ const handler = i < persistentCleanupLength ? persistentCleanup[i] : epochCleanup[i - persistentCleanupLength];
415
+ try {
416
+ await runLifecycle(epoch, handler, app);
417
+ } catch (error) {
418
+ (cleanupFailures ??= []).push(error);
419
+ }
420
+ }
421
+ }
422
+ cleanupCompleted = true;
423
+ epochCleanup.length = 0;
424
+ }
425
+ if (cleanupFailures) errors.push(...cleanupFailures);
426
+ if (releasable) releaseLifecycle(app, ext, createdExt, stop, registerCleanup);
427
+ throwLifecycleErrors(errors);
428
+ } finally {
429
+ epoch.setup = false;
430
+ if (!releasable) shutdownAttempt = void 0;
431
+ }
432
+ })().then(resolve, reject);
179
433
  }
180
- const cleanup = app["~ext"]?.cleanup;
181
- if (cleanup) for (let i = cleanup.length - 1; i >= 0; i--) try {
182
- const result = cleanup[i](app);
183
- if (result && typeof result.then === "function") Promise.resolve(result).catch(console.error);
184
- } catch (cleanupError) {
185
- console.error(cleanupError);
434
+ if (!published) startupShutdown ??= outcome;
435
+ if (!reentrant) return outcome;
436
+ if (observedOutcome !== outcome) {
437
+ observedOutcome = outcome;
438
+ outcome.catch((error) => {
439
+ console.error("[Elysia] stop() failed:", error);
440
+ });
186
441
  }
187
- return error;
442
+ return Promise.resolve();
188
443
  };
444
+ ext.stop = stop;
189
445
  const publish = () => {
190
- if (app.server !== server) return;
446
+ if (cancelled || app.server !== server) return;
191
447
  built ??= build();
192
448
  live = serve.fetch = withOrigin(built.fetch);
449
+ published = true;
193
450
  if (built.websocket) serve.websocket = built.websocket;
194
- if (built.routes) serve.routes = built.routes[0];
195
- reload();
451
+ if (built.routes) serve.routes = built.routes;
452
+ reloadServer(server, serve);
196
453
  if (callback) callback(server);
197
454
  };
198
455
  const start = () => {
199
- if (app.server !== server) return;
456
+ modulesReady = void 0;
457
+ if (cancelled || app.server !== server) return;
200
458
  const setupReady = setup();
201
459
  if (setupReady && typeof setupReady.then === "function") return Promise.resolve(setupReady).then(publish);
202
460
  publish();
203
461
  };
204
462
  try {
205
- if (app.pending) ready = app.modules.then(start);
206
- else ready = Promise.resolve().then(start);
463
+ ready = (modulesReady = app.modules).then(start);
464
+ ready = ready.catch((error) => stop(true, { error }));
207
465
  ready.catch((error) => {
208
466
  console.error("[Elysia] listen() failed:", error);
209
- return rollback(error);
210
467
  });
211
468
  } catch (error) {
212
- rollback(error);
469
+ stop(true, { error }).catch(console.error);
213
470
  throw error;
214
471
  }
215
472
  }