@thetally/httptree 0.2.0 → 0.3.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 (2) hide show
  1. package/dist/router.js +74 -133
  2. package/package.json +1 -1
package/dist/router.js CHANGED
@@ -101,8 +101,6 @@ function finishResponse(res, result) {
101
101
  if (res.writableEnded)
102
102
  return;
103
103
  if (!result) {
104
- res.statusCode = 204;
105
- res.end();
106
104
  return;
107
105
  }
108
106
  if (result[responseSymbol]) {
@@ -125,6 +123,25 @@ function finishResponse(res, result) {
125
123
  res.statusCode = 204;
126
124
  res.end();
127
125
  }
126
+ function finishThrownResponse(res, err) {
127
+ if (res.writableEnded)
128
+ return;
129
+ if (err && typeof err === "object" && err[responseSymbol]) {
130
+ const r = err;
131
+ res.statusCode = r.status;
132
+ for (const [k, v] of Object.entries(r.headers || {})) {
133
+ try {
134
+ res.setHeader(k, v);
135
+ }
136
+ catch { }
137
+ }
138
+ res.end(r.body);
139
+ return;
140
+ }
141
+ res.statusCode = 500;
142
+ res.end("Internal Server Error");
143
+ console.error("Error in handler:", err);
144
+ }
128
145
  class Router {
129
146
  base;
130
147
  routes;
@@ -141,7 +158,7 @@ class Router {
141
158
  this.errorHandlers = new Map();
142
159
  this.parent = undefined;
143
160
  if (!root) {
144
- this.branches = [];
161
+ use(this.handle);
145
162
  }
146
163
  }
147
164
  handleError(type, handler) {
@@ -204,16 +221,12 @@ class Router {
204
221
  handle = async (req, res, next) => {
205
222
  const pathname = url.parse(req.url || "/").pathname || "/";
206
223
  const method = (req.method || "GET").toUpperCase();
207
- debug(`Handling request: method='${method}', path='${pathname}'`);
208
- let matchedRoute = null;
209
224
  for (const r of this.routes) {
210
225
  if (r.method !== "*" && r.method !== method)
211
226
  continue;
212
227
  const m = r.info.regex.exec(pathname);
213
228
  if (!m)
214
229
  continue;
215
- debug(`Route matched: method='${r.method}', regex='${r.info.regex}', params=[${r.info.params.join(", ")}]`);
216
- matchedRoute = r;
217
230
  const params = {};
218
231
  r.info.params.forEach((p, i) => {
219
232
  debug(`Extracting param '${p}' from segment '${m[i + 1]}'`);
@@ -226,10 +239,8 @@ class Router {
226
239
  }
227
240
  });
228
241
  for (const chk of r.prechecks) {
229
- debug(`Running precheck for route: method='${r.method}', path regex='${r.info.regex}'`);
230
242
  const ok = await chk(req, res);
231
243
  if (!ok) {
232
- debug(`Precheck failed for route: method='${r.method}', path regex='${r.info.regex}'`);
233
244
  res.statusCode = 403;
234
245
  res.end("Forbidden");
235
246
  return;
@@ -237,129 +248,14 @@ class Router {
237
248
  }
238
249
  try {
239
250
  req.params = params;
240
- debug(`Invoking handler for route: method='${r.method}', path regex='${r.info.regex}'`);
241
- const result = await r.handler(req, res);
242
- if (result && typeof result === "object" && result[responseSymbol]) {
243
- finishResponse(res, result);
244
- return;
245
- }
246
- if (!res.writableEnded) {
247
- res.end();
248
- }
249
- return;
251
+ await r.handler(req, res);
250
252
  }
251
253
  catch (e) {
252
- if (e && typeof e === "object" && e[responseSymbol]) {
253
- finishResponse(res, e);
254
- return;
255
- }
256
- let errType = (e && typeof e === "object" && e.name) || (e && typeof e === "object" && e.constructor && e.constructor.name) || typeof e;
257
- let lastError = e;
258
- let current = this;
259
- if (process.env.HTTPTREE_DEBUG) {
260
- debug(`Error thrown: type='${errType}' on base='${this.base}'`);
261
- }
262
- while (current) {
263
- const handler = current.errorHandlers.get(String(errType)) || current.errorHandlers.get("*");
264
- if (handler) {
265
- if (process.env.HTTPTREE_DEBUG) {
266
- debug(`Using error handler for type='${errType}' on base='${current.base}'`);
267
- }
268
- try {
269
- handler(lastError, req, res);
270
- }
271
- catch (err) {
272
- lastError = err;
273
- if (process.env.HTTPTREE_DEBUG) {
274
- debug(`Handler on base='${current.base}' threw, trying parent`);
275
- }
276
- current = current.parent;
277
- continue;
278
- }
279
- return;
280
- }
281
- else if (process.env.HTTPTREE_DEBUG) {
282
- debug(`No handler for type='${errType}' on base='${current.base}', moving to parent`);
283
- }
284
- current = current.parent;
285
- }
286
- if (!res.writableEnded) {
287
- res.statusCode = 500;
288
- res.end("Internal Server Error");
289
- }
290
- if (lastError &&
291
- ((typeof lastError !== "object" || !('suppressLog' in lastError)) &&
292
- !(lastError && typeof lastError === "object" && lastError.name === "NotFound"))) {
293
- console.error("Uncaught handler error:", lastError);
294
- }
295
- return;
296
- }
297
- }
298
- if (!res.writableEnded) {
299
- debug(`No route matched for method='${method}', path='${pathname}'. Resolving most specific branch.`);
300
- let mostSpecific = this;
301
- let current = this;
302
- if (process.env.HTTPTREE_DEBUG) {
303
- debug(`NotFound: resolving most specific branch for path='${pathname}'`);
304
- }
305
- while (true) {
306
- let found = false;
307
- if (current.branches) {
308
- let bestBranch = null;
309
- let bestLen = -1;
310
- for (const branch of current.branches) {
311
- if (pathname.startsWith(branch.base) && branch.base.length > bestLen) {
312
- bestBranch = branch;
313
- bestLen = branch.base.length;
314
- }
315
- }
316
- if (bestBranch) {
317
- if (process.env.HTTPTREE_DEBUG) {
318
- debug(`Path '${pathname}' matches branch base='${bestBranch.base}'`);
319
- }
320
- mostSpecific = bestBranch;
321
- current = bestBranch;
322
- found = true;
323
- }
324
- }
325
- if (!found)
326
- break;
327
- }
328
- let errType = "NotFound";
329
- let lastError = Object.assign(new Error("Not Found"), { name: "NotFound" });
330
- current = mostSpecific;
331
- while (current) {
332
- const handler = current.errorHandlers.get(errType) || current.errorHandlers.get("*");
333
- if (handler) {
334
- if (process.env.HTTPTREE_DEBUG) {
335
- debug(`Using NotFound handler on base='${current.base}' for path='${pathname}'`);
336
- }
337
- try {
338
- handler(lastError, req, res);
339
- }
340
- catch (err) {
341
- lastError = err;
342
- if (process.env.HTTPTREE_DEBUG) {
343
- debug(`NotFound handler on base='${current.base}' threw, trying parent`);
344
- }
345
- current = current.parent;
346
- continue;
347
- }
348
- return;
349
- }
350
- else if (process.env.HTTPTREE_DEBUG) {
351
- debug(`No NotFound handler on base='${current.base}', moving to parent`);
352
- }
353
- current = current.parent;
354
- }
355
- if (!res.writableEnded) {
356
- res.statusCode = 404;
357
- res.end("Not Found");
358
- }
359
- if (lastError && (typeof lastError !== "object" || !('suppressLog' in lastError))) {
360
- console.error("Uncaught handler error:", lastError);
254
+ return next(e instanceof Error ? e : new Error(String(e)));
361
255
  }
256
+ return;
362
257
  }
258
+ next();
363
259
  };
364
260
  handleWS = async (ws, req) => {
365
261
  const pathname = url.parse(req.url || "/").pathname || "/";
@@ -390,10 +286,20 @@ class Router {
390
286
  const full = join(this.base, path);
391
287
  const info = compilePattern(full);
392
288
  debug(`Adding route: method='${method}', full path='${full}', regex='${info.regex}', params=[${info.params.join(", ")}]`);
289
+ const wrappedHandler = async (req, res) => {
290
+ try {
291
+ const result = await handler(req, res);
292
+ finishResponse(res, result);
293
+ }
294
+ catch (e) {
295
+ // throw e instanceof Error ? e : new Error(String(e));
296
+ finishThrownResponse(res, e);
297
+ }
298
+ };
393
299
  this.routes.push({
394
300
  method,
395
301
  info,
396
- handler,
302
+ handler: wrappedHandler,
397
303
  prechecks: [...this.inheritedChecks],
398
304
  });
399
305
  this.routes.sort((a, b) => b.info.score - a.info.score ||
@@ -415,10 +321,45 @@ export class BaseRouter extends Router {
415
321
  res.end("Internal Error");
416
322
  }
417
323
  else {
418
- this.handle(req, res, (nextErr) => {
419
- if (nextErr && !res.writableEnded) {
420
- res.statusCode = 500;
421
- res.end("Internal Error");
324
+ if (res.writableEnded)
325
+ return;
326
+ this.handle(req, res, (err) => {
327
+ if (err) {
328
+ const handler = this.errorHandlers.get(err.name) ||
329
+ this.errorHandlers.get("InternalError") ||
330
+ this.errorHandlers.get("*");
331
+ if (handler) {
332
+ try {
333
+ handler(err, req, res);
334
+ }
335
+ catch (e) {
336
+ res.statusCode = 500;
337
+ res.end("Internal Error");
338
+ }
339
+ }
340
+ else {
341
+ res.statusCode = 500;
342
+ res.end("Internal Error");
343
+ }
344
+ }
345
+ else {
346
+ if (!res.writableEnded) {
347
+ const notFoundHandler = this.errorHandlers.get("NotFound") ||
348
+ this.errorHandlers.get("*");
349
+ if (notFoundHandler) {
350
+ try {
351
+ notFoundHandler({ name: "NotFound", message: "Not Found" }, req, res);
352
+ }
353
+ catch (e) {
354
+ res.statusCode = 500;
355
+ res.end("Internal Error");
356
+ }
357
+ }
358
+ else {
359
+ res.statusCode = 404;
360
+ res.end("Not Found");
361
+ }
362
+ }
422
363
  }
423
364
  });
424
365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thetally/httptree",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A TypeScript library for creating and managing HTTP request trees.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",