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