elysia 2.0.0-beta.3 → 2.0.0-beta.4

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