h3 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -31,6 +31,9 @@ npm install h3
31
31
 
32
32
  # Using yarn
33
33
  yarn add h3
34
+
35
+ # Using pnpm
36
+ pnpm add h3
34
37
  ```
35
38
 
36
39
  ## Usage
@@ -116,11 +119,12 @@ Instead of adding helpers to `req` and `res`, h3 exposes them as composable util
116
119
  - `useCookies(req)`
117
120
  - `useCookie(req, name)`
118
121
  - `setCookie(res, name, value, opts?)`
122
+ - `deleteCookie(res, name, opts?)`
119
123
  - `useQuery(req)`
120
124
  - `send(res, data, type?)`
121
125
  - `sendRedirect(res, location, code=302)`
122
126
  - `appendHeader(res, name, value)`
123
- - `createError({ statusCode, statusMessage, data? }`
127
+ - `createError({ statusCode, statusMessage, data? })`
124
128
  - `sendError(res, error, debug?)`
125
129
  - `defineHandle(handle)`
126
130
  - `defineMiddleware(middlware)`
package/dist/index.cjs CHANGED
@@ -5,40 +5,36 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  const ufo = require('ufo');
6
6
  const radix3 = require('radix3');
7
7
  const destr = require('destr');
8
- const cookie = require('cookie');
8
+ const cookieEs = require('cookie-es');
9
9
 
10
10
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
11
11
 
12
12
  const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
13
13
 
14
- const defineHandle = (handler) => handler;
14
+ const defineHandler = (handler) => handler;
15
+ const defineHandle = defineHandler;
15
16
  const defineMiddleware = (middleware) => middleware;
16
- function promisifyHandle(handle) {
17
+ function promisifyHandler(handler) {
17
18
  return function(req, res) {
18
- return callHandle(handle, req, res);
19
+ return callHandler(handler, req, res);
19
20
  };
20
21
  }
21
- function callHandle(handle, req, res) {
22
+ const promisifyHandle = promisifyHandler;
23
+ function callHandler(handler, req, res) {
22
24
  return new Promise((resolve, reject) => {
23
25
  const next = (err) => err ? reject(err) : resolve(void 0);
24
26
  try {
25
- const returned = handle(req, res, next);
26
- if (returned !== void 0) {
27
- resolve(returned);
28
- } else {
29
- res.once("close", next);
30
- res.once("error", next);
31
- }
27
+ return resolve(handler(req, res, next));
32
28
  } catch (err) {
33
29
  next(err);
34
30
  }
35
31
  });
36
32
  }
37
- function lazyHandle(handle, promisify) {
33
+ function defineLazyHandler(handler, promisify) {
38
34
  let _promise;
39
35
  const resolve = () => {
40
36
  if (!_promise) {
41
- _promise = Promise.resolve(handle()).then((r) => promisify ? promisifyHandle(r.default || r) : r.default || r);
37
+ _promise = Promise.resolve(handler()).then((r) => promisify ? promisifyHandler(r.default || r) : r.default || r);
42
38
  }
43
39
  return _promise;
44
40
  };
@@ -46,26 +42,84 @@ function lazyHandle(handle, promisify) {
46
42
  return resolve().then((h) => h(req, res));
47
43
  };
48
44
  }
49
- function useBase(base, handle) {
45
+ const lazyHandle = defineLazyHandler;
46
+ function useBase(base, handler) {
50
47
  base = ufo.withoutTrailingSlash(base);
51
48
  if (!base) {
52
- return handle;
49
+ return handler;
53
50
  }
54
51
  return function(req, res) {
55
52
  req.originalUrl = req.originalUrl || req.url || "/";
56
53
  req.url = ufo.withoutBase(req.url || "/", base);
57
- return handle(req, res);
54
+ return handler(req, res);
58
55
  };
59
56
  }
60
57
 
61
- function useQuery(req) {
62
- return ufo.getQuery(req.url || "");
58
+ function defineEventHandler(handler) {
59
+ handler.__is_handler__ = true;
60
+ return handler;
63
61
  }
64
- function useMethod(req, defaultMethod = "GET") {
65
- return (req.method || defaultMethod).toUpperCase();
62
+ function defineLazyEventHandler(factory) {
63
+ let _promise;
64
+ let _resolved;
65
+ const resolveHandler = () => {
66
+ if (_resolved) {
67
+ return Promise.resolve(_resolved);
68
+ }
69
+ if (!_promise) {
70
+ _promise = Promise.resolve(factory()).then((r) => {
71
+ _resolved = r.default || r;
72
+ return _resolved;
73
+ });
74
+ }
75
+ return _promise;
76
+ };
77
+ return defineEventHandler((event) => {
78
+ if (_resolved) {
79
+ return _resolved(event);
80
+ }
81
+ return resolveHandler().then((handler) => handler(event));
82
+ });
83
+ }
84
+ function isEventHandler(input) {
85
+ return "__is_handler__" in input;
86
+ }
87
+ function toEventHandler(handler) {
88
+ if (isEventHandler(handler)) {
89
+ return handler;
90
+ }
91
+ return defineEventHandler((event) => {
92
+ return callHandler(handler, event.req, event.res);
93
+ });
94
+ }
95
+ function createEvent(req, res) {
96
+ const event = {
97
+ __is_event__: true,
98
+ req,
99
+ res
100
+ };
101
+ event.event = event;
102
+ req.event = event;
103
+ req.req = req;
104
+ req.res = res;
105
+ res.event = event;
106
+ res.res = res;
107
+ res.req.res = res;
108
+ res.req.req = req;
109
+ return event;
110
+ }
111
+ function isEvent(input) {
112
+ return "__is_event__" in input;
113
+ }
114
+
115
+ function useQuery(event) {
116
+ return ufo.getQuery(event.req.url || "");
66
117
  }
67
- function isMethod(req, expected, allowHead) {
68
- const method = useMethod(req);
118
+ function useMethod(event, defaultMethod = "GET") {
119
+ return (event.req.method || defaultMethod).toUpperCase();
120
+ }
121
+ function isMethod(event, expected, allowHead) {
122
+ const method = useMethod(event);
69
123
  if (allowHead && method === "HEAD") {
70
124
  return true;
71
125
  }
@@ -78,8 +132,8 @@ function isMethod(req, expected, allowHead) {
78
132
  }
79
133
  return false;
80
134
  }
81
- function assertMethod(req, expected, allowHead) {
82
- if (!isMethod(req, expected, allowHead)) {
135
+ function assertMethod(event, expected, allowHead) {
136
+ if (!isMethod(event, expected, allowHead)) {
83
137
  throw createError({
84
138
  statusCode: 405,
85
139
  statusMessage: "HTTP method is not allowed."
@@ -90,18 +144,18 @@ function assertMethod(req, expected, allowHead) {
90
144
  const RawBodySymbol = Symbol("h3RawBody");
91
145
  const ParsedBodySymbol = Symbol("h3RawBody");
92
146
  const PayloadMethods = ["PATCH", "POST", "PUT", "DELETE"];
93
- function useRawBody(req, encoding = "utf-8") {
94
- assertMethod(req, PayloadMethods);
95
- if (RawBodySymbol in req) {
96
- const promise2 = Promise.resolve(req[RawBodySymbol]);
147
+ function useRawBody(event, encoding = "utf-8") {
148
+ assertMethod(event, PayloadMethods);
149
+ if (RawBodySymbol in event.req) {
150
+ const promise2 = Promise.resolve(event.req[RawBodySymbol]);
97
151
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
98
152
  }
99
- if ("body" in req) {
100
- return Promise.resolve(req.body);
153
+ if ("body" in event.req) {
154
+ return Promise.resolve(event.req.body);
101
155
  }
102
- const promise = req[RawBodySymbol] = new Promise((resolve, reject) => {
156
+ const promise = event.req[RawBodySymbol] = new Promise((resolve, reject) => {
103
157
  const bodyData = [];
104
- req.on("error", (err) => {
158
+ event.req.on("error", (err) => {
105
159
  reject(err);
106
160
  }).on("data", (chunk) => {
107
161
  bodyData.push(chunk);
@@ -111,13 +165,13 @@ function useRawBody(req, encoding = "utf-8") {
111
165
  });
112
166
  return encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
113
167
  }
114
- async function useBody(req) {
115
- if (ParsedBodySymbol in req) {
116
- return req[ParsedBodySymbol];
168
+ async function useBody(event) {
169
+ if (ParsedBodySymbol in event.req) {
170
+ return event.req[ParsedBodySymbol];
117
171
  }
118
- const body = await useRawBody(req);
172
+ const body = await useRawBody(event);
119
173
  const json = destr__default(body);
120
- req[ParsedBodySymbol] = json;
174
+ event.req[ParsedBodySymbol] = json;
121
175
  return json;
122
176
  }
123
177
 
@@ -127,48 +181,64 @@ const MIMES = {
127
181
  };
128
182
 
129
183
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
130
- function send(res, data, type) {
184
+ function send(event, data, type) {
131
185
  if (type) {
132
- defaultContentType(res, type);
186
+ defaultContentType(event, type);
133
187
  }
134
188
  return new Promise((resolve) => {
135
189
  defer(() => {
136
- res.end(data);
190
+ event.res.end(data);
137
191
  resolve(void 0);
138
192
  });
139
193
  });
140
194
  }
141
- function defaultContentType(res, type) {
142
- if (type && !res.getHeader("Content-Type")) {
143
- res.setHeader("Content-Type", type);
195
+ function defaultContentType(event, type) {
196
+ if (type && !event.res.getHeader("Content-Type")) {
197
+ event.res.setHeader("Content-Type", type);
144
198
  }
145
199
  }
146
- function sendRedirect(res, location, code = 302) {
147
- res.statusCode = code;
148
- res.setHeader("Location", location);
149
- return send(res, "Redirecting to " + location, MIMES.html);
200
+ function sendRedirect(event, location, code = 302) {
201
+ event.res.statusCode = code;
202
+ event.res.setHeader("Location", location);
203
+ return send(event, "Redirecting to " + location, MIMES.html);
150
204
  }
151
- function appendHeader(res, name, value) {
152
- let current = res.getHeader(name);
205
+ function appendHeader(event, name, value) {
206
+ let current = event.res.getHeader(name);
153
207
  if (!current) {
154
- res.setHeader(name, value);
208
+ event.res.setHeader(name, value);
155
209
  return;
156
210
  }
157
211
  if (!Array.isArray(current)) {
158
212
  current = [current.toString()];
159
213
  }
160
- res.setHeader(name, current.concat(value));
214
+ event.res.setHeader(name, current.concat(value));
215
+ }
216
+ function isStream(data) {
217
+ return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
218
+ }
219
+ function sendStream(event, data) {
220
+ return new Promise((resolve, reject) => {
221
+ data.pipe(event.res);
222
+ data.on("end", () => resolve(void 0));
223
+ data.on("error", (error) => reject(createError(error)));
224
+ });
161
225
  }
162
226
 
163
- function useCookies(req) {
164
- return cookie.parse(req.headers.cookie || "");
227
+ function useCookies(event) {
228
+ return cookieEs.parse(event.req.headers.cookie || "");
165
229
  }
166
- function useCookie(req, name) {
167
- return useCookies(req)[name];
230
+ function useCookie(event, name) {
231
+ return useCookies(event)[name];
168
232
  }
169
- function setCookie(res, name, value, serializeOptions) {
170
- const cookieStr = cookie.serialize(name, value, serializeOptions);
171
- appendHeader(res, "Set-Cookie", cookieStr);
233
+ function setCookie(event, name, value, serializeOptions) {
234
+ const cookieStr = cookieEs.serialize(name, value, serializeOptions);
235
+ appendHeader(event, "Set-Cookie", cookieStr);
236
+ }
237
+ function deleteCookie(event, name, serializeOptions) {
238
+ setCookie(event, name, "", {
239
+ ...serializeOptions,
240
+ maxAge: 0
241
+ });
172
242
  }
173
243
 
174
244
  class H3Error extends Error {
@@ -194,7 +264,7 @@ function createError(input) {
194
264
  }
195
265
  return err;
196
266
  }
197
- function sendError(res, error, debug) {
267
+ function sendError(event, error, debug) {
198
268
  let h3Error;
199
269
  if (error instanceof H3Error) {
200
270
  h3Error = error;
@@ -202,37 +272,41 @@ function sendError(res, error, debug) {
202
272
  console.error(error);
203
273
  h3Error = createError(error);
204
274
  }
205
- if (res.writableEnded) {
275
+ if (event.res.writableEnded) {
206
276
  return;
207
277
  }
208
- res.statusCode = h3Error.statusCode;
209
- res.statusMessage = h3Error.statusMessage;
278
+ event.res.statusCode = h3Error.statusCode;
279
+ event.res.statusMessage = h3Error.statusMessage;
210
280
  const responseBody = {
211
- statusCode: res.statusCode,
212
- statusMessage: res.statusMessage,
281
+ statusCode: event.res.statusCode,
282
+ statusMessage: event.res.statusMessage,
213
283
  stack: [],
214
284
  data: h3Error.data
215
285
  };
216
286
  if (debug) {
217
287
  responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
218
288
  }
219
- res.setHeader("Content-Type", MIMES.json);
220
- res.end(JSON.stringify(responseBody, null, 2));
289
+ event.res.setHeader("Content-Type", MIMES.json);
290
+ event.res.end(JSON.stringify(responseBody, null, 2));
221
291
  }
222
292
 
223
293
  function createApp(options = {}) {
224
294
  const stack = [];
225
- const _handle = createHandle(stack, options);
226
- const app = function(req, res) {
227
- return _handle(req, res).catch((error) => {
295
+ const handler = createAppEventHandler(stack, options);
296
+ const nodeHandler = async function(req, res) {
297
+ const event = createEvent(req, res);
298
+ try {
299
+ await handler(event);
300
+ } catch (err) {
228
301
  if (options.onError) {
229
- return options.onError(error, req, res);
302
+ await options.onError(err, event);
230
303
  }
231
- return sendError(res, error, !!options.debug);
232
- });
304
+ await sendError(event, err, !!options.debug);
305
+ }
233
306
  };
307
+ const app = nodeHandler;
234
308
  app.stack = stack;
235
- app._handle = _handle;
309
+ app.handler = handler;
236
310
  app.use = (arg1, arg2, arg3) => use(app, arg1, arg2, arg3);
237
311
  return app;
238
312
  }
@@ -242,61 +316,70 @@ function use(app, arg1, arg2, arg3) {
242
316
  } else if (Array.isArray(arg2)) {
243
317
  arg2.forEach((i) => use(app, arg1, i, arg3));
244
318
  } else if (typeof arg1 === "string") {
245
- app.stack.push(normalizeLayer({ ...arg3, route: arg1, handle: arg2 }));
319
+ app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
246
320
  } else if (typeof arg1 === "function") {
247
- app.stack.push(normalizeLayer({ ...arg2, route: "/", handle: arg1 }));
321
+ app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
248
322
  } else {
249
323
  app.stack.push(normalizeLayer({ ...arg1 }));
250
324
  }
251
325
  return app;
252
326
  }
253
- function createHandle(stack, options) {
327
+ function createAppEventHandler(stack, options) {
254
328
  const spacing = options.debug ? 2 : void 0;
255
- return async function handle(req, res) {
256
- req.originalUrl = req.originalUrl || req.url || "/";
257
- const reqUrl = req.url || "/";
329
+ return defineEventHandler(async (event) => {
330
+ event.req.originalUrl = event.req.originalUrl || event.req.url || "/";
331
+ const reqUrl = event.req.url || "/";
258
332
  for (const layer of stack) {
259
333
  if (layer.route.length > 1) {
260
334
  if (!reqUrl.startsWith(layer.route)) {
261
335
  continue;
262
336
  }
263
- req.url = reqUrl.slice(layer.route.length) || "/";
337
+ event.req.url = reqUrl.slice(layer.route.length) || "/";
264
338
  } else {
265
- req.url = reqUrl;
339
+ event.req.url = reqUrl;
266
340
  }
267
- if (layer.match && !layer.match(req.url, req)) {
341
+ if (layer.match && !layer.match(event.req.url, event)) {
268
342
  continue;
269
343
  }
270
- const val = await layer.handle(req, res);
271
- if (res.writableEnded) {
344
+ const val = await layer.handler(event);
345
+ if (event.res.writableEnded) {
272
346
  return;
273
347
  }
274
348
  const type = typeof val;
275
349
  if (type === "string") {
276
- return send(res, val, MIMES.html);
277
- } else if (type === "object" && val !== void 0) {
350
+ return send(event, val, MIMES.html);
351
+ } else if (isStream(val)) {
352
+ return sendStream(event, val);
353
+ } else if (type === "object" || type === "boolean" || type === "number") {
278
354
  if (val && val.buffer) {
279
- return send(res, val);
355
+ return send(event, val);
280
356
  } else if (val instanceof Error) {
281
357
  throw createError(val);
282
358
  } else {
283
- return send(res, JSON.stringify(val, null, spacing), MIMES.json);
359
+ return send(event, JSON.stringify(val, null, spacing), MIMES.json);
284
360
  }
285
361
  }
286
362
  }
287
- if (!res.writableEnded) {
363
+ if (!event.res.writableEnded) {
288
364
  throw createError({ statusCode: 404, statusMessage: "Not Found" });
289
365
  }
290
- };
366
+ });
291
367
  }
292
- function normalizeLayer(layer) {
293
- if (layer.promisify === void 0) {
294
- layer.promisify = layer.handle.length > 2;
368
+ function normalizeLayer(input) {
369
+ let handler = input.handler;
370
+ if (handler.handler) {
371
+ handler = handler.handler;
372
+ }
373
+ if (!isEventHandler(handler)) {
374
+ if (input.lazy) {
375
+ handler = defineLazyHandler(handler);
376
+ }
377
+ handler = toEventHandler(handler);
295
378
  }
296
379
  return {
297
- route: ufo.withoutTrailingSlash(layer.route),
298
- match: layer.match,
299
- handle: layer.lazy ? lazyHandle(layer.handle, layer.promisify) : layer.promisify ? promisifyHandle(layer.handle) : layer.handle
380
+ route: ufo.withoutTrailingSlash(input.route),
381
+ match: input.match,
382
+ handler
300
383
  };
301
384
  }
302
385
 
@@ -305,28 +388,34 @@ function createRouter() {
305
388
  const _router = radix3.createRouter({});
306
389
  const routes = {};
307
390
  const router = {};
308
- router.add = (path, handle, method = "all") => {
391
+ const addRoute = (path, handler, method) => {
309
392
  let route = routes[path];
310
393
  if (!route) {
311
394
  routes[path] = route = { handlers: {} };
312
395
  _router.insert(path, route);
313
396
  }
314
- route.handlers[method] = handle;
397
+ route.handlers[method] = toEventHandler(handler);
315
398
  return router;
316
399
  };
400
+ router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
317
401
  for (const method of RouterMethods) {
318
402
  router[method] = (path, handle) => router.add(path, handle, method);
319
403
  }
320
- router.handle = (req, res) => {
321
- const matched = _router.lookup(req.url || "/");
404
+ router.handler = defineEventHandler((event) => {
405
+ let path = event.req.url || "/";
406
+ const queryUrlIndex = path.lastIndexOf("?");
407
+ if (queryUrlIndex > -1) {
408
+ path = path.substring(0, queryUrlIndex);
409
+ }
410
+ const matched = _router.lookup(path);
322
411
  if (!matched) {
323
412
  throw createError({
324
413
  statusCode: 404,
325
414
  name: "Not Found",
326
- statusMessage: `Cannot find any route matching ${req.url || "/"}.`
415
+ statusMessage: `Cannot find any route matching ${event.req.url || "/"}.`
327
416
  });
328
417
  }
329
- const method = (req.method || "get").toLowerCase();
418
+ const method = (event.req.method || "get").toLowerCase();
330
419
  const handler = matched.handlers[method] || matched.handlers.all;
331
420
  if (!handler) {
332
421
  throw createError({
@@ -335,9 +424,10 @@ function createRouter() {
335
424
  statusMessage: `Method ${method} is not allowed on this route.`
336
425
  });
337
426
  }
338
- req.params = matched.params || {};
339
- return handler(req, res);
340
- };
427
+ event.event.params = matched.params || {};
428
+ event.req.params = event.event.params;
429
+ return handler(event);
430
+ });
341
431
  return router;
342
432
  }
343
433
 
@@ -345,21 +435,33 @@ exports.H3Error = H3Error;
345
435
  exports.MIMES = MIMES;
346
436
  exports.appendHeader = appendHeader;
347
437
  exports.assertMethod = assertMethod;
348
- exports.callHandle = callHandle;
438
+ exports.callHandler = callHandler;
349
439
  exports.createApp = createApp;
440
+ exports.createAppEventHandler = createAppEventHandler;
350
441
  exports.createError = createError;
351
- exports.createHandle = createHandle;
442
+ exports.createEvent = createEvent;
352
443
  exports.createRouter = createRouter;
353
444
  exports.defaultContentType = defaultContentType;
445
+ exports.defineEventHandler = defineEventHandler;
354
446
  exports.defineHandle = defineHandle;
447
+ exports.defineHandler = defineHandler;
448
+ exports.defineLazyEventHandler = defineLazyEventHandler;
449
+ exports.defineLazyHandler = defineLazyHandler;
355
450
  exports.defineMiddleware = defineMiddleware;
451
+ exports.deleteCookie = deleteCookie;
452
+ exports.isEvent = isEvent;
453
+ exports.isEventHandler = isEventHandler;
356
454
  exports.isMethod = isMethod;
455
+ exports.isStream = isStream;
357
456
  exports.lazyHandle = lazyHandle;
358
457
  exports.promisifyHandle = promisifyHandle;
458
+ exports.promisifyHandler = promisifyHandler;
359
459
  exports.send = send;
360
460
  exports.sendError = sendError;
361
461
  exports.sendRedirect = sendRedirect;
462
+ exports.sendStream = sendStream;
362
463
  exports.setCookie = setCookie;
464
+ exports.toEventHandler = toEventHandler;
363
465
  exports.use = use;
364
466
  exports.useBase = useBase;
365
467
  exports.useBody = useBody;