@server/next 0.31.0 → 0.33.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 (3) hide show
  1. package/index.d.ts +13 -16
  2. package/index.js +480 -301
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -27,10 +27,12 @@ declare namespace JSX {
27
27
  [elem: string]: any;
28
28
  }
29
29
  }
30
+ type BodyMode = "parse" | "raw" | "stream";
30
31
  type RouteOptions = {
31
32
  tags?: string | string[];
32
33
  title?: string;
33
34
  description?: string;
35
+ body?: BodyMode;
34
36
  };
35
37
  type Route = {
36
38
  path: string;
@@ -48,9 +50,11 @@ type Cookie = {
48
50
  };
49
51
  type RouterMethod = "*" | Method;
50
52
  type Bucket = {
53
+ location?: string;
51
54
  read: (path: string) => Promise<ReadableStream | null>;
52
- write: (path: string, data: string | Buffer) => Promise<void | string>;
55
+ write: (path: string, data: string | Buffer | ReadableStream) => Promise<void | string>;
53
56
  delete: (path: string) => Promise<boolean>;
57
+ folder?: (prefix: string) => Bucket;
54
58
  };
55
59
  type UploadedFile = {
56
60
  name: string;
@@ -100,17 +104,16 @@ type AuthUser<T = Record<string, any>> = T & {
100
104
  strategy: Strategy;
101
105
  email: string;
102
106
  };
103
- type ProviderString = Provider | `${Provider}|${Provider}`;
104
- type AuthOption = `${Strategy}:${Provider | ProviderString}` | {
105
- provider: Provider | ProviderString | Provider[];
107
+ type AuthOption = `${Strategy}:${Provider}` | {
106
108
  strategy: Strategy;
109
+ providers: Provider | Provider[];
107
110
  session?: KVStore;
108
111
  store?: KVStore;
109
112
  redirect?: string;
110
113
  cleanUser?: <T = AuthUser>(user: T) => T | Promise<T>;
111
114
  };
112
115
  type AuthSettings = {
113
- provider: Provider[];
116
+ providers: Provider[];
114
117
  strategy: Strategy;
115
118
  store: KVStore;
116
119
  session: KVStore;
@@ -134,7 +137,6 @@ type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
134
137
  type Options = {
135
138
  port?: number;
136
139
  secret?: string;
137
- views?: string | Bucket;
138
140
  public?: string | Bucket;
139
141
  uploads?: string | Bucket | UploadPipeline;
140
142
  store?: KVStore;
@@ -149,11 +151,11 @@ type Options = {
149
151
  log?: LogLevel | boolean;
150
152
  favicon?: string | Bucket;
151
153
  security?: SecurityOptions;
154
+ body?: BodyMode;
152
155
  };
153
156
  type Settings = {
154
157
  port: number;
155
158
  secret: string;
156
- views?: Bucket;
157
159
  public?: Bucket;
158
160
  uploads?: Bucket | UploadPipeline;
159
161
  store?: KVStore;
@@ -168,6 +170,7 @@ type Settings = {
168
170
  log: Logger;
169
171
  favicon?: string | Bucket;
170
172
  security: SecuritySettings;
173
+ body: BodyMode;
171
174
  };
172
175
  type Time = {
173
176
  (name: string): void;
@@ -193,17 +196,12 @@ type PathToParams<Path extends string> = ParamsToObject<ExtractPathParams<Path>>
193
196
  type BunEnv = Record<string, string> & {
194
197
  upgrade?: (req: Request) => boolean;
195
198
  };
196
- type EventCallback = (data: Context & SerializableValue) => void;
197
- type Events = Record<string, EventCallback[]> & {
198
- on?: (key: string, cb: (value?: Context & SerializableValue) => void) => void;
199
- trigger?: (key: string, value?: Partial<Context & SerializableValue>) => void;
200
- };
201
199
  type Context<Params extends Record<string, string | undefined> = Record<string, string>, O extends ServerConfig = object> = {
202
200
  method: Method;
203
201
  ip: string;
204
202
  headers: Record<string, string | string[]>;
205
203
  cookies: Record<string, string>;
206
- body?: SerializableValue;
204
+ body?: SerializableValue | Buffer | ReadableStream;
207
205
  url: URL & {
208
206
  params: Params;
209
207
  query: Record<string, string>;
@@ -220,7 +218,6 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
220
218
  User?: infer U;
221
219
  } ? U extends Record<"User", infer Inner> ? Inner : AuthUser : AuthUser;
222
220
  init: number;
223
- events: Events;
224
221
  req?: Request;
225
222
  res?: Response & {
226
223
  cookies?: Record<string, string>;
@@ -230,7 +227,7 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
230
227
  type InlineReply = Response | {
231
228
  body: string;
232
229
  headers?: Headers;
233
- } | SerializableValue | JSX.Element;
230
+ } | SerializableValue | JSX.Element | Buffer | ReadableStream;
234
231
  type Body = InlineReply;
235
232
  type Middleware<O extends ServerConfig = object, Params extends Record<string, string | undefined> = Record<string, string>> = (ctx: Context<Params, O>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
236
233
 
@@ -454,4 +451,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
454
451
  }
455
452
  declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
456
453
 
457
- export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
454
+ export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
package/index.js CHANGED
@@ -54,7 +54,7 @@ ServerError_default.extend({
54
54
  message: "Invalid Authorization type '{strategy}', valid one is '{valid}'"
55
55
  },
56
56
  AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
57
- AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
57
+ AUTH_NO_PROVIDER: "No provider passed to the option 'auth.providers'",
58
58
  AUTH_INVALID_PROVIDER: {
59
59
  status: 401,
60
60
  message: "Invalid provider '{provider}', valid ones are: '{valid}'"
@@ -89,6 +89,403 @@ if (typeof process !== "undefined") {
89
89
  Object.assign(globalThis.env, process.env);
90
90
  }
91
91
 
92
+ // src/helpers/createId.ts
93
+ var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
94
+ var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
95
+ var cyrb53 = (str, seed = 0) => {
96
+ if (typeof str !== "string") str = String(str);
97
+ let h1 = 3735928559 ^ seed;
98
+ let h2 = 1103547991 ^ seed;
99
+ for (let i = 0, ch; i < str.length; i++) {
100
+ ch = str.charCodeAt(i);
101
+ h1 = Math.imul(h1 ^ ch, 2654435761);
102
+ h2 = Math.imul(h2 ^ ch, 1597334677);
103
+ }
104
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
105
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
106
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
107
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
108
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
109
+ };
110
+ var hash = (str, size) => {
111
+ let chars = "";
112
+ let num = cyrb53(str);
113
+ for (let i = 0; i < size; i++) {
114
+ if (num < alphabet.length) num = cyrb53(str, i);
115
+ chars += alphabet[num % alphabet.length];
116
+ num = Math.floor(num / alphabet.length);
117
+ }
118
+ return chars;
119
+ };
120
+ var randomId = (size = 16) => {
121
+ let id = "";
122
+ const bytes = random(size);
123
+ while (size--) {
124
+ id += alphabet[bytes[size] & 61];
125
+ }
126
+ return id;
127
+ };
128
+ function createId(source, size = 16) {
129
+ if (source) return hash(source, size);
130
+ return randomId(size);
131
+ }
132
+
133
+ // src/helpers/upload.ts
134
+ function parseBytes(value) {
135
+ if (typeof value === "number") return value;
136
+ const units = {
137
+ b: 1,
138
+ kb: 1024,
139
+ mb: 1024 ** 2,
140
+ gb: 1024 ** 3
141
+ };
142
+ const match = value.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/);
143
+ if (!match) throw new Error(`Invalid size: "${value}"`);
144
+ return parseFloat(match[1]) * (units[match[2]] ?? 1);
145
+ }
146
+ function getExt(filename) {
147
+ const i = filename.lastIndexOf(".");
148
+ if (i <= 0) return ".bin";
149
+ return filename.slice(i).toLowerCase();
150
+ }
151
+ async function saveFileToBucket(originalName, data, bucket, contentType) {
152
+ const ext = getExt(originalName);
153
+ const id = `${createId()}${ext}`;
154
+ const path2 = await bucket.write(id, data);
155
+ return { name: originalName, id, path: path2, type: contentType, size: data.length };
156
+ }
157
+ var UploadPipeline = class {
158
+ _bucket;
159
+ _limits = {};
160
+ constructor(bucket) {
161
+ this._bucket = bucket ?? null;
162
+ }
163
+ limit(options) {
164
+ this._limits = { ...this._limits, ...options };
165
+ return this;
166
+ }
167
+ store(bucket) {
168
+ this._bucket = bucket;
169
+ return this;
170
+ }
171
+ async processFile(originalName, data, contentType) {
172
+ const { maxSize, minSize, fileType } = this._limits;
173
+ if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
174
+ throw new Error(
175
+ `File "${originalName}" is too large (${data.length} bytes, limit is ${maxSize})`
176
+ );
177
+ }
178
+ if (minSize !== void 0 && data.length < parseBytes(minSize)) {
179
+ throw new Error(
180
+ `File "${originalName}" is too small (${data.length} bytes, minimum is ${minSize})`
181
+ );
182
+ }
183
+ if (fileType && fileType.length > 0) {
184
+ const ext = getExt(originalName);
185
+ const mime = contentType.toLowerCase();
186
+ const allowed = fileType.some(
187
+ (t) => t.toLowerCase() === mime || t.toLowerCase() === ext
188
+ );
189
+ if (!allowed) {
190
+ throw new Error(
191
+ `File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType.join(", ")})`
192
+ );
193
+ }
194
+ }
195
+ if (!this._bucket) {
196
+ throw new Error(
197
+ `No destination configured. Pass a bucket to upload() or call .store()`
198
+ );
199
+ }
200
+ return saveFileToBucket(originalName, data, this._bucket, contentType);
201
+ }
202
+ };
203
+ function upload(bucket) {
204
+ return new UploadPipeline(bucket);
205
+ }
206
+
207
+ // src/helpers/parseBody.ts
208
+ function getBoundary(header) {
209
+ if (!header) return null;
210
+ if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
211
+ console.error("Do not set the `Content-Type` manually for FormData");
212
+ }
213
+ const items = header.split(";");
214
+ for (const item of items) {
215
+ const trimmedItem = item.trim();
216
+ if (trimmedItem.startsWith("boundary=")) {
217
+ return trimmedItem.split("=")[1].trim();
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ function getMatching(string, regex) {
223
+ const matches = string.match(regex);
224
+ return matches?.[1] ?? "";
225
+ }
226
+ function isProbablyText(buffer) {
227
+ for (let i = 0; i < Math.min(buffer.length, 512); i++) {
228
+ const byte = buffer[i];
229
+ if (byte === 0) return false;
230
+ if (byte < 7 || byte > 13 && byte < 32) return false;
231
+ }
232
+ return true;
233
+ }
234
+ var MIME_EXT = {
235
+ "application/json": ".json",
236
+ "application/pdf": ".pdf",
237
+ "application/zip": ".zip",
238
+ "text/plain": ".txt",
239
+ "text/html": ".html",
240
+ "text/csv": ".csv",
241
+ "image/jpeg": ".jpg",
242
+ "image/png": ".png",
243
+ "image/gif": ".gif",
244
+ "image/webp": ".webp",
245
+ "image/svg+xml": ".svg",
246
+ "video/mp4": ".mp4",
247
+ "audio/mpeg": ".mp3"
248
+ };
249
+ function extFromType(type2) {
250
+ const base = (type2 || "").split(";")[0].trim().toLowerCase();
251
+ if (MIME_EXT[base]) return MIME_EXT[base];
252
+ const sub = base.split("/")[1];
253
+ return sub && /^[a-z0-9]+$/.test(sub) ? `.${sub}` : ".bin";
254
+ }
255
+ var asIterable = (s) => s;
256
+ function toStream(input) {
257
+ if (input instanceof ReadableStream) return input;
258
+ return new ReadableStream({
259
+ start(controller) {
260
+ controller.enqueue(input);
261
+ controller.close();
262
+ }
263
+ });
264
+ }
265
+ async function toBuffer(input) {
266
+ if (!(input instanceof ReadableStream)) return input;
267
+ const chunks = [];
268
+ for await (const chunk of asIterable(input)) chunks.push(Buffer.from(chunk));
269
+ return Buffer.concat(chunks);
270
+ }
271
+ function parseUrlEncoded(text) {
272
+ const out = {};
273
+ for (const [key, value] of new URLSearchParams(text)) {
274
+ const existing = out[key];
275
+ if (existing === void 0) out[key] = value;
276
+ else if (Array.isArray(existing)) existing.push(value);
277
+ else out[key] = [existing, value];
278
+ }
279
+ return out;
280
+ }
281
+ function addField(body, name, value) {
282
+ if (body[name] === void 0) {
283
+ body[name] = value;
284
+ return;
285
+ }
286
+ if (!Array.isArray(body[name])) body[name] = [body[name]];
287
+ body[name].push(value);
288
+ }
289
+ function startPart(headerStr, dest) {
290
+ const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
291
+ if (!name) return { kind: "skip" };
292
+ const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
293
+ if (!filename) return { kind: "text", name, chunks: [] };
294
+ const type2 = getMatching(headerStr, /Content-Type:\s*([^\r\n]+)/i).trim() || "application/octet-stream";
295
+ if (!dest) return { kind: "drop" };
296
+ if (dest instanceof UploadPipeline) {
297
+ return { kind: "pipefile", name, filename, type: type2, pipeline: dest, chunks: [] };
298
+ }
299
+ const id = `${createId()}${getExt(filename)}`;
300
+ let controller;
301
+ const readable = new ReadableStream({
302
+ start(c) {
303
+ controller = c;
304
+ }
305
+ });
306
+ return {
307
+ kind: "file",
308
+ name,
309
+ filename,
310
+ type: type2,
311
+ id,
312
+ controller,
313
+ write: dest.write(id, readable),
314
+ size: 0
315
+ };
316
+ }
317
+ function feedPart(part, data) {
318
+ if (data.length === 0) return;
319
+ if (part.kind === "text" || part.kind === "pipefile") part.chunks.push(data);
320
+ else if (part.kind === "file") {
321
+ part.controller.enqueue(data);
322
+ part.size += data.length;
323
+ }
324
+ }
325
+ async function endPart(part, body) {
326
+ if (part.kind === "text") {
327
+ const buf = Buffer.concat(part.chunks);
328
+ const value = isProbablyText(buf) ? buf.toString("utf-8").trim() : buf;
329
+ addField(body, part.name, value);
330
+ } else if (part.kind === "pipefile") {
331
+ const buf = Buffer.concat(part.chunks);
332
+ const ref = await part.pipeline.processFile(part.filename, buf, part.type);
333
+ addField(body, part.name, ref);
334
+ } else if (part.kind === "file") {
335
+ part.controller.close();
336
+ const path2 = await part.write;
337
+ addField(body, part.name, {
338
+ name: part.filename,
339
+ id: part.id,
340
+ path: path2,
341
+ type: part.type,
342
+ size: part.size
343
+ });
344
+ }
345
+ }
346
+ var BREAK = Buffer.from("\r\n\r\n");
347
+ async function parseMultipart(stream, boundary, dest) {
348
+ const delim = Buffer.from(`\r
349
+ --${boundary}`);
350
+ const body = {};
351
+ let buf = Buffer.from("\r\n");
352
+ let state = "boundary";
353
+ let part = null;
354
+ for await (const chunk of asIterable(stream)) {
355
+ buf = Buffer.concat([buf, Buffer.from(chunk)]);
356
+ let advanced = true;
357
+ while (advanced) {
358
+ advanced = false;
359
+ if (state === "boundary") {
360
+ const i = buf.indexOf(delim);
361
+ if (i === -1) {
362
+ if (buf.length >= delim.length) {
363
+ buf = buf.subarray(buf.length - delim.length + 1);
364
+ }
365
+ break;
366
+ }
367
+ if (buf.length < i + delim.length + 2) break;
368
+ const after = i + delim.length;
369
+ if (buf[after] === 45 && buf[after + 1] === 45) return body;
370
+ buf = buf.subarray(after + 2);
371
+ state = "headers";
372
+ advanced = true;
373
+ } else if (state === "headers") {
374
+ const i = buf.indexOf(BREAK);
375
+ if (i === -1) break;
376
+ part = startPart(buf.subarray(0, i).toString("utf-8"), dest);
377
+ buf = buf.subarray(i + BREAK.length);
378
+ state = "body";
379
+ advanced = true;
380
+ } else {
381
+ const i = buf.indexOf(delim);
382
+ if (i === -1) {
383
+ const safe = buf.length - (delim.length - 1);
384
+ if (safe > 0 && part) {
385
+ feedPart(part, buf.subarray(0, safe));
386
+ buf = buf.subarray(safe);
387
+ }
388
+ break;
389
+ }
390
+ if (part) {
391
+ feedPart(part, buf.subarray(0, i));
392
+ await endPart(part, body);
393
+ part = null;
394
+ }
395
+ buf = buf.subarray(i);
396
+ state = "boundary";
397
+ advanced = true;
398
+ }
399
+ }
400
+ }
401
+ if (part) await endPart(part, body);
402
+ return body;
403
+ }
404
+ async function streamToBucket(stream, type2, bucket) {
405
+ const id = `${createId()}${extFromType(type2)}`;
406
+ let size = 0;
407
+ let controller;
408
+ const readable = new ReadableStream({
409
+ start(c) {
410
+ controller = c;
411
+ }
412
+ });
413
+ const write = bucket.write(id, readable);
414
+ for await (const chunk of asIterable(stream)) {
415
+ controller.enqueue(chunk);
416
+ size += chunk.byteLength;
417
+ }
418
+ controller.close();
419
+ const path2 = await write;
420
+ if (!size) return void 0;
421
+ return { name: id, id, path: path2, type: type2, size };
422
+ }
423
+ async function parseBody(input, contentType, dest) {
424
+ const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
425
+ const boundary = type2 && /multipart\/form-data/i.test(type2) ? getBoundary(type2) : null;
426
+ if (boundary) return parseMultipart(toStream(input), boundary, dest);
427
+ if (!type2 || /^text\//i.test(type2)) {
428
+ const buf = await toBuffer(input);
429
+ return buf.length ? buf.toString("utf-8") : void 0;
430
+ }
431
+ if (/application\/json/i.test(type2)) {
432
+ const buf = await toBuffer(input);
433
+ return buf.length ? JSON.parse(buf.toString("utf-8")) : void 0;
434
+ }
435
+ if (/application\/x-www-form-urlencoded/i.test(type2)) {
436
+ const buf = await toBuffer(input);
437
+ return buf.length ? parseUrlEncoded(buf.toString("utf-8")) : void 0;
438
+ }
439
+ if (!dest) {
440
+ const buf = await toBuffer(input);
441
+ return buf.length ? buf : void 0;
442
+ }
443
+ if (dest instanceof UploadPipeline) {
444
+ const buf = await toBuffer(input);
445
+ return buf.length ? dest.processFile(`upload${extFromType(type2)}`, buf, type2) : void 0;
446
+ }
447
+ return streamToBucket(toStream(input), type2, dest);
448
+ }
449
+
450
+ // src/helpers/body.ts
451
+ var sources = /* @__PURE__ */ new WeakMap();
452
+ function setBodySource(ctx, source) {
453
+ sources.set(ctx, source);
454
+ }
455
+ async function resolveBody(ctx, mode) {
456
+ const source = sources.get(ctx);
457
+ if (!source) return void 0;
458
+ if (mode === "stream") return source.getStream();
459
+ if (mode === "raw") {
460
+ const raw = await source.getBuffer();
461
+ if (!raw.length) return void 0;
462
+ if (!ctx.headers["content-length"]) {
463
+ ctx.headers["content-length"] = String(raw.length);
464
+ }
465
+ return raw;
466
+ }
467
+ const stream = source.getStream();
468
+ if (!stream) return void 0;
469
+ let size = 0;
470
+ const counted = stream.pipeThrough(
471
+ new TransformStream({
472
+ transform(chunk, controller) {
473
+ size += chunk.byteLength;
474
+ controller.enqueue(chunk);
475
+ }
476
+ })
477
+ );
478
+ const body = await parseBody(
479
+ counted,
480
+ ctx.headers["content-type"],
481
+ ctx.options.uploads
482
+ );
483
+ if (size && !ctx.headers["content-length"]) {
484
+ ctx.headers["content-length"] = String(size);
485
+ }
486
+ return body;
487
+ }
488
+
92
489
  // src/helpers/clientIp.ts
93
490
  var first = (v) => (Array.isArray(v) ? v[0] : v) || "";
94
491
  var normalize = (ip) => ip.replace(/^::ffff:/, "");
@@ -558,7 +955,7 @@ async function emailRegister(ctx) {
558
955
  strategy: ctx.options.auth.strategy,
559
956
  provider: "email",
560
957
  email,
561
- password: await hash(password),
958
+ password: await hash2(password),
562
959
  time,
563
960
  ...data
564
961
  };
@@ -579,7 +976,7 @@ async function emailUpdatePassword(ctx) {
579
976
  if (!fullUser) throw ServerError_default.AUTH_NO_USER();
580
977
  const isValid = await verify(passwords.previous, fullUser.password);
581
978
  if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
582
- fullUser.password = await hash(passwords.updated);
979
+ fullUser.password = await hash2(passwords.updated);
583
980
  await updateUser(fullUser, ctx.user, ctx.options.auth.store);
584
981
  return 200;
585
982
  }
@@ -716,37 +1113,27 @@ function defaultCleanUser(fullUser) {
716
1113
  const { password: _password, ...user } = fullUser;
717
1114
  return user;
718
1115
  }
719
- var providersKeys = Object.keys(providers_default);
720
- function getProviders(provider) {
721
- if (typeof provider === "string") {
722
- provider = provider.split("|");
723
- }
724
- const invalidProvider = provider.find((p) => !providersKeys.includes(p));
725
- if (invalidProvider) {
726
- throw new Error(
727
- `Provider "${invalidProvider}" not found, available ones are "${providersKeys.join('", "')}"`
728
- );
729
- }
730
- return provider;
731
- }
1116
+ var available = Object.keys(providers_default);
732
1117
  function parseAuthOptions(auth2, all) {
733
1118
  if (!auth2) return null;
734
1119
  if (typeof auth2 === "string") {
735
- const [strategy2, providerRaw] = auth2.split(":");
736
- const provider2 = providerRaw && providerRaw.split("|");
737
- auth2 = { strategy: strategy2, provider: provider2 };
1120
+ const [strategy2, provider] = auth2.split(":");
1121
+ auth2 = { strategy: strategy2, providers: provider ? [provider] : [] };
738
1122
  }
739
- if (!auth2.strategy) {
740
- throw new Error("Auth options needs a strategy");
741
- }
742
- if (!auth2.strategy.length) {
1123
+ if (!auth2.strategy?.length) {
743
1124
  throw new Error("Auth options needs a strategy");
744
1125
  }
745
1126
  const strategy = auth2.strategy;
746
- if (!auth2.provider?.length) {
1127
+ const list = Array.isArray(auth2.providers) ? auth2.providers : auth2.providers ? [auth2.providers] : [];
1128
+ if (!list.length) {
747
1129
  throw new Error("Auth options needs a provider");
748
1130
  }
749
- const provider = getProviders(auth2.provider);
1131
+ const invalid = list.find((p) => !available.includes(p));
1132
+ if (invalid) {
1133
+ throw new Error(
1134
+ `Provider "${invalid}" not found, available ones are "${available.join('", "')}"`
1135
+ );
1136
+ }
750
1137
  const redirect2 = auth2.redirect || defaultRedirect;
751
1138
  const cleanUser = auth2.cleanUser || defaultCleanUser;
752
1139
  if (!auth2.store && !all.store) {
@@ -758,13 +1145,10 @@ function parseAuthOptions(auth2, all) {
758
1145
  const store = auth2.store || all.store.prefix("user:");
759
1146
  const session2 = auth2.session || all.store.prefix("auth:");
760
1147
  return {
761
- // Base main configuration
762
1148
  strategy,
763
- provider,
764
- // Extra configuration
1149
+ providers: list,
765
1150
  redirect: redirect2,
766
1151
  cleanUser,
767
- // Stores for the auth session and users
768
1152
  store,
769
1153
  session: session2
770
1154
  };
@@ -797,12 +1181,23 @@ function thinLocalBucket(root) {
797
1181
  }
798
1182
  });
799
1183
  },
800
- write: (name, value, type2) => {
1184
+ write: async (name, value, type2) => {
801
1185
  const fullPath = absolute(name);
802
- if (value) {
803
- return fsp.writeFile(fullPath, value, type2).then(() => fullPath);
1186
+ if (!value) return fs.createWriteStream(fullPath);
1187
+ await fsp.mkdir(path.dirname(fullPath), { recursive: true });
1188
+ if (value instanceof ReadableStream) {
1189
+ const writable = fs.createWriteStream(fullPath);
1190
+ for await (const chunk of value) {
1191
+ writable.write(chunk);
1192
+ }
1193
+ await new Promise((resolve2, reject) => {
1194
+ writable.on("error", reject);
1195
+ writable.end(resolve2);
1196
+ });
1197
+ return fullPath;
804
1198
  }
805
- return fs.createWriteStream(fullPath);
1199
+ await fsp.writeFile(fullPath, value, type2);
1200
+ return fullPath;
806
1201
  },
807
1202
  delete: async (name) => {
808
1203
  const fullPath = absolute(name);
@@ -812,29 +1207,35 @@ function thinLocalBucket(root) {
812
1207
  } catch {
813
1208
  return false;
814
1209
  }
815
- }
1210
+ },
1211
+ folder: (prefix) => thinLocalBucket(path.join(root, prefix))
816
1212
  };
817
1213
  }
818
- function thinBunBucket(s3) {
1214
+ function thinBunBucket(s3, prefix = "") {
1215
+ const key = (name) => prefix ? `${prefix}/${name}` : name;
819
1216
  return {
820
1217
  read: async (name) => {
821
- const file2 = s3.file(name);
1218
+ const file2 = s3.file(key(name));
822
1219
  if (!await file2.exists()) return null;
823
1220
  return await file2.stream();
824
1221
  },
825
1222
  write: async (name, value) => {
826
- const file2 = s3.file(name);
1223
+ const file2 = s3.file(key(name));
827
1224
  if (value) {
828
1225
  await file2.write(value);
829
- return name;
1226
+ return key(name);
830
1227
  }
831
- return s3.presign(name, { expiresIn: 3600, acl: "public-read-write" });
1228
+ return s3.presign(key(name), {
1229
+ expiresIn: 3600,
1230
+ acl: "public-read-write"
1231
+ });
832
1232
  },
833
1233
  delete: async (name) => {
834
- const file2 = s3.file(name);
1234
+ const file2 = s3.file(key(name));
835
1235
  if (!await file2.exists()) return null;
836
1236
  return await file2.delete();
837
- }
1237
+ },
1238
+ folder: (sub) => thinBunBucket(s3, key(sub))
838
1239
  };
839
1240
  }
840
1241
  function bucket_default(root) {
@@ -848,47 +1249,6 @@ function bucket_default(root) {
848
1249
  return root;
849
1250
  }
850
1251
 
851
- // src/helpers/createId.ts
852
- var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
853
- var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
854
- var cyrb53 = (str, seed = 0) => {
855
- if (typeof str !== "string") str = String(str);
856
- let h1 = 3735928559 ^ seed;
857
- let h2 = 1103547991 ^ seed;
858
- for (let i = 0, ch; i < str.length; i++) {
859
- ch = str.charCodeAt(i);
860
- h1 = Math.imul(h1 ^ ch, 2654435761);
861
- h2 = Math.imul(h2 ^ ch, 1597334677);
862
- }
863
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
864
- h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
865
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
866
- h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
867
- return 4294967296 * (2097151 & h2) + (h1 >>> 0);
868
- };
869
- var hash2 = (str, size) => {
870
- let chars = "";
871
- let num = cyrb53(str);
872
- for (let i = 0; i < size; i++) {
873
- if (num < alphabet.length) num = cyrb53(str, i);
874
- chars += alphabet[num % alphabet.length];
875
- num = Math.floor(num / alphabet.length);
876
- }
877
- return chars;
878
- };
879
- var randomId = (size = 16) => {
880
- let id = "";
881
- const bytes = random(size);
882
- while (size--) {
883
- id += alphabet[bytes[size] & 61];
884
- }
885
- return id;
886
- };
887
- function createId(source, size = 16) {
888
- if (source) return hash2(source, size);
889
- return randomId(size);
890
- }
891
-
892
1252
  // src/helpers/color.ts
893
1253
  var map = {
894
1254
  reset: 0,
@@ -998,80 +1358,6 @@ function createLogger(level) {
998
1358
  };
999
1359
  }
1000
1360
 
1001
- // src/helpers/upload.ts
1002
- function parseBytes(value) {
1003
- if (typeof value === "number") return value;
1004
- const units = {
1005
- b: 1,
1006
- kb: 1024,
1007
- mb: 1024 ** 2,
1008
- gb: 1024 ** 3
1009
- };
1010
- const match = value.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/);
1011
- if (!match) throw new Error(`Invalid size: "${value}"`);
1012
- return parseFloat(match[1]) * (units[match[2]] ?? 1);
1013
- }
1014
- function getExt(filename) {
1015
- const i = filename.lastIndexOf(".");
1016
- if (i <= 0) return ".bin";
1017
- return filename.slice(i).toLowerCase();
1018
- }
1019
- async function saveFileToBucket(originalName, data, bucket, contentType) {
1020
- const ext = getExt(originalName);
1021
- const id = `${createId()}${ext}`;
1022
- const path2 = await bucket.write(id, data);
1023
- return { name: originalName, id, path: path2, type: contentType, size: data.length };
1024
- }
1025
- var UploadPipeline = class {
1026
- _bucket;
1027
- _limits = {};
1028
- constructor(bucket) {
1029
- this._bucket = bucket ?? null;
1030
- }
1031
- limit(options) {
1032
- this._limits = { ...this._limits, ...options };
1033
- return this;
1034
- }
1035
- store(bucket) {
1036
- this._bucket = bucket;
1037
- return this;
1038
- }
1039
- async processFile(originalName, data, contentType) {
1040
- const { maxSize, minSize, fileType } = this._limits;
1041
- if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
1042
- throw new Error(
1043
- `File "${originalName}" is too large (${data.length} bytes, limit is ${maxSize})`
1044
- );
1045
- }
1046
- if (minSize !== void 0 && data.length < parseBytes(minSize)) {
1047
- throw new Error(
1048
- `File "${originalName}" is too small (${data.length} bytes, minimum is ${minSize})`
1049
- );
1050
- }
1051
- if (fileType && fileType.length > 0) {
1052
- const ext = getExt(originalName);
1053
- const mime = contentType.toLowerCase();
1054
- const allowed = fileType.some(
1055
- (t) => t.toLowerCase() === mime || t.toLowerCase() === ext
1056
- );
1057
- if (!allowed) {
1058
- throw new Error(
1059
- `File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType.join(", ")})`
1060
- );
1061
- }
1062
- }
1063
- if (!this._bucket) {
1064
- throw new Error(
1065
- `No destination configured. Pass a bucket to upload() or call .store()`
1066
- );
1067
- }
1068
- return saveFileToBucket(originalName, data, this._bucket, contentType);
1069
- }
1070
- };
1071
- function upload(bucket) {
1072
- return new UploadPipeline(bucket);
1073
- }
1074
-
1075
1361
  // src/helpers/config.ts
1076
1362
  function config(options = {}) {
1077
1363
  const env2 = globalThis.env;
@@ -1082,6 +1368,9 @@ function config(options = {}) {
1082
1368
  port: options.port || env2.PORT || 3e3,
1083
1369
  secret: options.secret || env2.SECRET || `unsafe-${createId()}`,
1084
1370
  log,
1371
+ // How request bodies are read: parsed into ctx.body by default; `raw` keeps
1372
+ // the Buffer, `stream` hands the handler the unread web ReadableStream.
1373
+ body: options.body ?? "parse",
1085
1374
  // Trust X-Forwarded-* headers for ctx.ip (on by default; set it to false
1086
1375
  // when clients connect directly so a client can't spoof its IP).
1087
1376
  security: {
@@ -1124,7 +1413,6 @@ function config(options = {}) {
1124
1413
  }
1125
1414
  settings.cors = cors2;
1126
1415
  }
1127
- settings.views = options.views ? bucket_default(options.views) : null;
1128
1416
  settings.public = options.public ? bucket_default(options.public) : null;
1129
1417
  settings.uploads = options.uploads instanceof UploadPipeline ? options.uploads : options.uploads ? bucket_default(options.uploads) : null;
1130
1418
  if (options.favicon) settings.favicon = options.favicon;
@@ -1151,10 +1439,9 @@ function config(options = {}) {
1151
1439
  });
1152
1440
  const loc = (v) => typeof v === "string" ? v : "enabled";
1153
1441
  if (settings.auth) {
1154
- log.message("auth", `${settings.auth.provider.join(", ")} auth enabled`);
1442
+ log.message("auth", `${settings.auth.providers.join(", ")} auth enabled`);
1155
1443
  }
1156
1444
  if (settings.public) log.message("public", loc(options.public));
1157
- if (settings.views) log.message("views", loc(options.views));
1158
1445
  if (settings.uploads) log.message("uploads", loc(options.uploads));
1159
1446
  if (settings.session) log.message("session", "enabled");
1160
1447
  if (settings.cors) {
@@ -1276,6 +1563,9 @@ async function parseResponse(out, ctx) {
1276
1563
  if (out instanceof ReadableStream) {
1277
1564
  out = new Response(out);
1278
1565
  }
1566
+ if (out instanceof Uint8Array) {
1567
+ out = new Response(out);
1568
+ }
1279
1569
  if (typeof out === "number") {
1280
1570
  out = new Response(void 0, { status: out });
1281
1571
  }
@@ -1442,6 +1732,7 @@ async function getResponse(app, ctx) {
1442
1732
  if (Object.keys(route.options).length) {
1443
1733
  ctx.options = { ...app.settings, ...route.options };
1444
1734
  }
1735
+ ctx.body = await resolveBody(ctx, ctx.options.body);
1445
1736
  for (const cb of route.fns) {
1446
1737
  if (typeof cb === "function") {
1447
1738
  const res = await cb(ctx);
@@ -1454,6 +1745,7 @@ async function getResponse(app, ctx) {
1454
1745
  break;
1455
1746
  }
1456
1747
  if (!matched) {
1748
+ ctx.body = await resolveBody(ctx, ctx.options.body);
1457
1749
  for (const mw of app.middleware) {
1458
1750
  const out = await parseResponse(await mw(ctx), ctx);
1459
1751
  if (out) return out;
@@ -1472,7 +1764,7 @@ async function getResponse(app, ctx) {
1472
1764
  import * as crypto2 from "crypto";
1473
1765
  import { getRandomValues } from "crypto";
1474
1766
  import { promisify } from "util";
1475
- async function hash(password) {
1767
+ async function hash2(password) {
1476
1768
  if ("argon2" in crypto2) {
1477
1769
  const argon23 = promisify(crypto2.argon2);
1478
1770
  const buf = await argon23("argon2id", {
@@ -1536,102 +1828,6 @@ function iteratorToReadable(generator) {
1536
1828
  });
1537
1829
  }
1538
1830
 
1539
- // src/helpers/parseBody.ts
1540
- function getBoundary(header) {
1541
- if (!header) return null;
1542
- if (header.includes("multipart/form-data") && !header.includes("boundary=")) {
1543
- console.error("Do not set the `Content-Type` manually for FormData");
1544
- }
1545
- const items = header.split(";");
1546
- for (const item of items) {
1547
- const trimmedItem = item.trim();
1548
- if (trimmedItem.startsWith("boundary=")) {
1549
- return trimmedItem.split("=")[1].trim();
1550
- }
1551
- }
1552
- return null;
1553
- }
1554
- function getMatching(string, regex) {
1555
- const matches = string.match(regex);
1556
- return matches?.[1] ?? "";
1557
- }
1558
- function splitBuffer(buffer, delimiter) {
1559
- const result = [];
1560
- let start = 0;
1561
- let index = buffer.indexOf(delimiter);
1562
- while (index !== -1) {
1563
- result.push(buffer.slice(start, index));
1564
- start = index + delimiter.length;
1565
- index = buffer.indexOf(delimiter, start);
1566
- }
1567
- result.push(buffer.slice(start));
1568
- return result;
1569
- }
1570
- var BREAK_BUFFER = Buffer.from("\r\n\r\n");
1571
- var END_BUFFER = Buffer.from("--\r\n");
1572
- function isProbablyText(buffer) {
1573
- for (let i = 0; i < Math.min(buffer.length, 512); i++) {
1574
- const byte = buffer[i];
1575
- if (byte === 0) return false;
1576
- if (byte < 7 || byte > 13 && byte < 32) return false;
1577
- }
1578
- return true;
1579
- }
1580
- async function parseBody(raw, contentType, bucket) {
1581
- const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType;
1582
- if (!raw || raw.length === 0) return {};
1583
- if (!contentTypeStr || /^text\//.test(contentTypeStr)) {
1584
- return raw.toString("utf-8");
1585
- }
1586
- if (/application\/json/.test(contentTypeStr)) {
1587
- return JSON.parse(raw.toString("utf-8"));
1588
- }
1589
- const boundary = getBoundary(contentTypeStr);
1590
- if (!boundary) return null;
1591
- const body = {};
1592
- const boundaryBuffer = Buffer.from(`--${boundary}`);
1593
- const parts = splitBuffer(raw, boundaryBuffer);
1594
- for (const part of parts) {
1595
- if (part.length === 0 || part.equals(END_BUFFER)) continue;
1596
- const idx = part.indexOf(BREAK_BUFFER);
1597
- if (idx === -1) continue;
1598
- const headerStr = part.slice(0, idx).toString("utf-8");
1599
- const contentBuf = part.slice(idx + BREAK_BUFFER.length, part.length - 2);
1600
- const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
1601
- if (!name) continue;
1602
- const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
1603
- if (filename) {
1604
- const partContentType = getMatching(headerStr, /Content-Type:\s*([^\r\n]+)/i).trim() || "application/octet-stream";
1605
- if (!bucket) {
1606
- continue;
1607
- }
1608
- if (bucket instanceof UploadPipeline) {
1609
- body[name] = await bucket.processFile(
1610
- filename,
1611
- contentBuf,
1612
- partContentType
1613
- );
1614
- } else {
1615
- body[name] = await saveFileToBucket(
1616
- filename,
1617
- contentBuf,
1618
- bucket,
1619
- partContentType
1620
- );
1621
- }
1622
- } else {
1623
- const value = isProbablyText(contentBuf) ? contentBuf.toString("utf-8").trim() : contentBuf;
1624
- if (body[name]) {
1625
- if (!Array.isArray(body[name])) body[name] = [body[name]];
1626
- body[name].push(value);
1627
- } else {
1628
- body[name] = value;
1629
- }
1630
- }
1631
- }
1632
- return body;
1633
- }
1634
-
1635
1831
  // src/helpers/parseCookies.ts
1636
1832
  function parseCookies(cookies2) {
1637
1833
  if (!cookies2) return {};
@@ -1853,10 +2049,10 @@ async function getUser(ctx) {
1853
2049
  valid: options.strategy
1854
2050
  });
1855
2051
  }
1856
- if (!options.provider.includes(auth2.provider)) {
2052
+ if (!options.providers.includes(auth2.provider)) {
1857
2053
  throw ServerError_default.AUTH_INVALID_PROVIDER({
1858
2054
  provider: auth2.provider,
1859
- valid: options.provider
2055
+ valid: options.providers
1860
2056
  });
1861
2057
  }
1862
2058
  const user = await ctx.options.auth.store.get(auth2.user);
@@ -1899,13 +2095,13 @@ function auth(app) {
1899
2095
  app.use(async function middle(ctx) {
1900
2096
  ctx.user = await getUser(ctx);
1901
2097
  });
1902
- const enabled = app.settings.auth.provider;
2098
+ app.post("/auth/logout", logout);
2099
+ const enabled = app.settings.auth.providers;
1903
2100
  for (const name of oauth2) {
1904
2101
  if (!enabled.includes(name)) continue;
1905
2102
  const key = name.toUpperCase();
1906
2103
  if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
1907
2104
  if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
1908
- app.get("/auth/logout", logout);
1909
2105
  app.get(`/auth/login/${name}`, providers_default[name].login);
1910
2106
  app.get(`/auth/callback/${name}`, providers_default[name].callback);
1911
2107
  }
@@ -1914,12 +2110,10 @@ function auth(app) {
1914
2110
  for (const key of keys) {
1915
2111
  if (!env[key]) throw new Error(`${key} not defined`);
1916
2112
  }
1917
- app.get("/auth/logout", logout);
1918
2113
  app.get("/auth/login/apple", providers_default.apple.login);
1919
2114
  app.post("/auth/callback/apple", providers_default.apple.callback);
1920
2115
  }
1921
2116
  if (enabled.includes("email")) {
1922
- app.post("/auth/logout", logout);
1923
2117
  app.post("/auth/register/email", providers_default.email.register);
1924
2118
  app.post("/auth/login/email", providers_default.email.login);
1925
2119
  app.put("/auth/password/email", providers_default.email.password);
@@ -2177,22 +2371,6 @@ function timer(ctx) {
2177
2371
  // src/context/node.ts
2178
2372
  import { TLSSocket } from "tls";
2179
2373
 
2180
- // src/context/createEvents.ts
2181
- function createEvents() {
2182
- const events = {};
2183
- events.on = (name, callback3) => {
2184
- events[name] = events[name] || [];
2185
- events[name].push(callback3);
2186
- };
2187
- events.trigger = (name, data) => {
2188
- if (!events[name]) return;
2189
- for (const cb of events[name]) {
2190
- cb(data);
2191
- }
2192
- };
2193
- return events;
2194
- }
2195
-
2196
2374
  // src/context/isValidMethod.ts
2197
2375
  var methods = [
2198
2376
  "get",
@@ -2229,32 +2407,31 @@ async function createNode(req, app) {
2229
2407
  "query",
2230
2408
  (url2) => Object.fromEntries(url2.searchParams.entries())
2231
2409
  );
2232
- const rawBody = await new Promise((resolve2, reject) => {
2233
- const body2 = [];
2234
- req.on("data", (chunk) => body2.push(chunk)).on("end", () => resolve2(Buffer.concat(body2))).on("error", reject);
2235
- });
2236
- if (rawBody.length && !headers2["content-length"]) {
2237
- headers2["content-length"] = String(rawBody.length);
2238
- }
2239
- const body = rawBody ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
2240
- const events = createEvents();
2241
- return {
2410
+ const source = {
2411
+ getBuffer: () => new Promise((resolve2, reject) => {
2412
+ const chunks2 = [];
2413
+ req.on("data", (chunk) => chunks2.push(chunk)).on("end", () => resolve2(Buffer.concat(chunks2))).on("error", reject);
2414
+ }),
2415
+ getStream: () => toWeb(req)
2416
+ };
2417
+ const ctx = {
2242
2418
  options: app.settings,
2243
2419
  platform: app.platform,
2244
2420
  url,
2245
2421
  method,
2246
- body,
2422
+ body: void 0,
2247
2423
  headers: headers2,
2248
2424
  cookies: cookies2,
2249
2425
  session: {},
2250
2426
  init,
2251
- events,
2252
2427
  app,
2253
2428
  ip: clientIp(headers2, {
2254
2429
  remoteAddress: req.socket.remoteAddress || "",
2255
2430
  trustProxy: app.settings.security.trustProxy
2256
2431
  })
2257
2432
  };
2433
+ setBodySource(ctx, source);
2434
+ return ctx;
2258
2435
  }
2259
2436
 
2260
2437
  // src/context/winter.ts
@@ -2273,29 +2450,28 @@ async function createWinter(req, app, server2) {
2273
2450
  "query",
2274
2451
  (url2) => Object.fromEntries(url2.searchParams.entries())
2275
2452
  );
2276
- const rawBody = Buffer.from(await req.arrayBuffer());
2277
- if (rawBody.length && !headers2["content-length"]) {
2278
- headers2["content-length"] = String(rawBody.length);
2279
- }
2280
- const body = req.body ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
2281
- const events = createEvents();
2282
- return {
2453
+ const source = {
2454
+ getBuffer: async () => Buffer.from(await req.arrayBuffer()),
2455
+ getStream: () => req.body ?? void 0
2456
+ };
2457
+ const ctx = {
2283
2458
  options: app.settings,
2284
2459
  platform: app.platform,
2285
2460
  url,
2286
2461
  method,
2287
- body,
2462
+ body: void 0,
2288
2463
  headers: headers2,
2289
2464
  cookies: cookies2,
2290
2465
  session: {},
2291
2466
  init,
2292
- events,
2293
2467
  app,
2294
2468
  ip: clientIp(headers2, {
2295
2469
  remoteAddress: server2?.requestIP?.(req)?.address || "",
2296
2470
  trustProxy: app.settings.security.trustProxy
2297
2471
  })
2298
2472
  };
2473
+ setBodySource(ctx, source);
2474
+ return ctx;
2299
2475
  }
2300
2476
 
2301
2477
  // src/context/handlers.ts
@@ -2304,7 +2480,6 @@ var Winter = async (app, request, env2) => {
2304
2480
  Object.assign(globalThis.env, env2);
2305
2481
  const ctx = await createWinter(request, app, env2);
2306
2482
  const res = await handleRequest(app, ctx);
2307
- ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
2308
2483
  return res;
2309
2484
  };
2310
2485
  var Node = async (app) => {
@@ -2322,6 +2497,11 @@ var Node = async (app) => {
2322
2497
  response.end();
2323
2498
  }).listen(app.settings.port, () => {
2324
2499
  app.settings.log.start(`http://localhost:${app.settings.port}/`);
2500
+ if (app.handlers.socket.length) {
2501
+ console.warn(
2502
+ "[server] WebSockets (.socket()) are only supported on Bun, not Node"
2503
+ );
2504
+ }
2325
2505
  });
2326
2506
  };
2327
2507
  var Netlify = async (app, request, context) => {
@@ -2331,7 +2511,6 @@ var Netlify = async (app, request, context) => {
2331
2511
  }
2332
2512
  const ctx = await createWinter(request, app);
2333
2513
  const res = await handleRequest(app, ctx);
2334
- ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
2335
2514
  return res;
2336
2515
  };
2337
2516
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
5
5
  "homepage": "https://server-js.com/",
6
6
  "repository": "github:franciscop/server-next",