yedra 0.9.4 → 0.9.6

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.
@@ -72,15 +72,15 @@ export class App {
72
72
  let invalidMethod = false;
73
73
  let result = undefined;
74
74
  for (const endpoint of this.endpoints) {
75
- const params = endpoint.path.match(url);
76
- if (!params) {
75
+ const match = endpoint.path.match(url);
76
+ if (match === undefined) {
77
77
  continue;
78
78
  }
79
+ const { params, score } = match;
79
80
  if (endpoint.method !== method) {
80
81
  invalidMethod = true;
81
82
  continue;
82
83
  }
83
- const score = Object.keys(params).length;
84
84
  const previous = result?.score;
85
85
  if (previous === undefined || score < previous) {
86
86
  // if there was no previous match or this one is better, use it
@@ -70,7 +70,8 @@ export const listen = (app, port) => {
70
70
  req.on('end', async () => {
71
71
  const body = Buffer.concat(chunks);
72
72
  const url = new URL(req.url ?? '', `http://${req.headers.host}`);
73
- console.info(`[${new Date().toISOString()}] ${req.method} ${url.pathname} (${context.connectionCount()} connections)`);
73
+ const begin = Date.now();
74
+ const concurrentConnections = context.connectionCount();
74
75
  const response = await app.handle({
75
76
  method: req.method ?? 'GET',
76
77
  url: url.pathname,
@@ -78,6 +79,8 @@ export const listen = (app, port) => {
78
79
  headers: parseHeaders(req.headers),
79
80
  body,
80
81
  });
82
+ const duration = Date.now() - begin;
83
+ console.info(`[${new Date().toISOString()}] ${req.method} ${url.pathname} -> ${response.status} [${duration}ms, ${concurrentConnections}cc]`);
81
84
  res.writeHead(response.status, response.headers);
82
85
  res.end(response.body);
83
86
  removeConn();
@@ -33,7 +33,10 @@ export declare class Path {
33
33
  * undefined.
34
34
  * @param path - The string path to match.
35
35
  */
36
- match(path: string): Record<string, string> | undefined;
36
+ match(path: string): {
37
+ params: Record<string, string>;
38
+ score: number;
39
+ } | undefined;
37
40
  /**
38
41
  * Remove `:` and `?` from a parameter path segment.
39
42
  * @param param - The path segment.
@@ -16,9 +16,13 @@ export class Path {
16
16
  throw new Error(`API path ${path} is invalid: Must start with '/'.`);
17
17
  }
18
18
  this.expected = path.substring(1).split('/');
19
- const invalidSegment = this.expected.find((part) => part.match(/^(:?[a-z0-9-]+\??)$/) === null);
19
+ const invalidSegment = this.expected.find((part) => part.match(/^((:?[a-z0-9-]+\??)|\*)$/) === null);
20
20
  if (invalidSegment) {
21
- throw new Error(`API path ${path} is invalid: Segment ${invalidSegment} does not match regex /^(:?[a-z0-9-]+\\??)$/.`);
21
+ throw new Error(`API path ${path} is invalid: Segment ${invalidSegment} does not match regex /^((:?[a-z0-9-]+\\??)|\\*)$/.`);
22
+ }
23
+ const wildcard = this.expected.findIndex((part) => part === '*');
24
+ if (wildcard !== -1 && wildcard !== this.expected.length - 1) {
25
+ throw new Error(`API path ${path} is invalid: * must be the last path segment.`);
22
26
  }
23
27
  const firstOptional = this.expected.findIndex((part) => part.endsWith('?'));
24
28
  if (firstOptional !== -1 &&
@@ -53,11 +57,15 @@ export class Path {
53
57
  match(path) {
54
58
  const params = {};
55
59
  const actual = path.substring(1).split('/');
56
- if (this.expected.length < actual.length) {
57
- // path cannot be longer than expected
60
+ if (this.expected.length < actual.length && !this.expected.includes('*')) {
61
+ // path cannot be longer than expected, unless it contains a wildcard
58
62
  return undefined;
59
63
  }
60
64
  for (let i = 0; i < actual.length; ++i) {
65
+ if (this.expected[i] === '*') {
66
+ // wildcard, parsing successful
67
+ return { params, score: Number.POSITIVE_INFINITY };
68
+ }
61
69
  if (this.expected[i].startsWith(':')) {
62
70
  // parameter, accept anything
63
71
  params[Path.normalizeParam(this.expected[i])] = actual[i];
@@ -68,11 +76,17 @@ export class Path {
68
76
  }
69
77
  }
70
78
  if (actual.length < this.expected.length &&
71
- !this.expected[actual.length].endsWith('?')) {
79
+ !this.expected[actual.length].endsWith('?') &&
80
+ this.expected[actual.length] !== '*') {
72
81
  // path is incomplete
73
82
  return undefined;
74
83
  }
75
- return params;
84
+ return {
85
+ params,
86
+ score: this.expected.includes('*')
87
+ ? Number.POSITIVE_INFINITY
88
+ : Object.keys(params).length,
89
+ };
76
90
  }
77
91
  /**
78
92
  * Remove `:` and `?` from a parameter path segment.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "yedra",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "repository": "github:0codekit/yedra",
5
5
  "main": "dist/index.js",
6
6
  "devDependencies": {
7
7
  "@biomejs/biome": "^1.8.3",
8
8
  "@types/bun": "^1.1.6",
9
- "@types/node": "^20.14.10",
9
+ "@types/node": "^20.14.11",
10
10
  "typescript": "^5.5.3"
11
11
  },
12
12
  "bugs": "https://github.com/0codekit/yedra/issues",