@kevisual/router 0.0.4 → 0.0.5

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.
@@ -5745,10 +5745,11 @@ class QueryRouter {
5745
5745
  catch (e) {
5746
5746
  if (route?.isDebug) {
5747
5747
  console.error('=====debug====:middlerware error');
5748
+ console.error('=====debug====:', e);
5748
5749
  console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
5749
5750
  console.error('=====debug====:', e.message);
5750
5751
  }
5751
- if (e instanceof CustomError) {
5752
+ if (e instanceof CustomError || e?.code) {
5752
5753
  ctx.code = e.code;
5753
5754
  ctx.message = e.message;
5754
5755
  ctx.body = null;
@@ -0,0 +1,25 @@
1
+ import { Key } from 'path-to-regexp';
2
+ import { ServerResponse, IncomingMessage } from 'http';
3
+
4
+ type Req = IncomingMessage & {
5
+ params?: Record<string, string>;
6
+ };
7
+ interface Route {
8
+ method: string;
9
+ regexp: RegExp;
10
+ keys: Key[];
11
+ handlers: Array<(req: Req, res: ServerResponse) => Promise<void> | void>;
12
+ }
13
+ /**
14
+ * SimpleRouter
15
+ */
16
+ declare class SimpleRouter {
17
+ routes: Route[];
18
+ constructor();
19
+ use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
20
+ get(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
21
+ post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
22
+ parse(req: Req, res: ServerResponse): Promise<void> | "not_found";
23
+ }
24
+
25
+ export { SimpleRouter };
@@ -0,0 +1,457 @@
1
+ var dist = {};
2
+
3
+ var hasRequiredDist;
4
+
5
+ function requireDist () {
6
+ if (hasRequiredDist) return dist;
7
+ hasRequiredDist = 1;
8
+ Object.defineProperty(dist, "__esModule", { value: true });
9
+ dist.TokenData = void 0;
10
+ dist.parse = parse;
11
+ dist.compile = compile;
12
+ dist.match = match;
13
+ dist.pathToRegexp = pathToRegexp;
14
+ dist.stringify = stringify;
15
+ const DEFAULT_DELIMITER = "/";
16
+ const NOOP_VALUE = (value) => value;
17
+ const ID_START = /^[$_\p{ID_Start}]$/u;
18
+ const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
19
+ const DEBUG_URL = "https://git.new/pathToRegexpError";
20
+ const SIMPLE_TOKENS = {
21
+ // Groups.
22
+ "{": "{",
23
+ "}": "}",
24
+ // Reserved.
25
+ "(": "(",
26
+ ")": ")",
27
+ "[": "[",
28
+ "]": "]",
29
+ "+": "+",
30
+ "?": "?",
31
+ "!": "!",
32
+ };
33
+ /**
34
+ * Escape text for stringify to path.
35
+ */
36
+ function escapeText(str) {
37
+ return str.replace(/[{}()\[\]+?!:*]/g, "\\$&");
38
+ }
39
+ /**
40
+ * Escape a regular expression string.
41
+ */
42
+ function escape(str) {
43
+ return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
44
+ }
45
+ /**
46
+ * Tokenize input string.
47
+ */
48
+ function* lexer(str) {
49
+ const chars = [...str];
50
+ let i = 0;
51
+ function name() {
52
+ let value = "";
53
+ if (ID_START.test(chars[++i])) {
54
+ value += chars[i];
55
+ while (ID_CONTINUE.test(chars[++i])) {
56
+ value += chars[i];
57
+ }
58
+ }
59
+ else if (chars[i] === '"') {
60
+ let pos = i;
61
+ while (i < chars.length) {
62
+ if (chars[++i] === '"') {
63
+ i++;
64
+ pos = 0;
65
+ break;
66
+ }
67
+ if (chars[i] === "\\") {
68
+ value += chars[++i];
69
+ }
70
+ else {
71
+ value += chars[i];
72
+ }
73
+ }
74
+ if (pos) {
75
+ throw new TypeError(`Unterminated quote at ${pos}: ${DEBUG_URL}`);
76
+ }
77
+ }
78
+ if (!value) {
79
+ throw new TypeError(`Missing parameter name at ${i}: ${DEBUG_URL}`);
80
+ }
81
+ return value;
82
+ }
83
+ while (i < chars.length) {
84
+ const value = chars[i];
85
+ const type = SIMPLE_TOKENS[value];
86
+ if (type) {
87
+ yield { type, index: i++, value };
88
+ }
89
+ else if (value === "\\") {
90
+ yield { type: "ESCAPED", index: i++, value: chars[i++] };
91
+ }
92
+ else if (value === ":") {
93
+ const value = name();
94
+ yield { type: "PARAM", index: i, value };
95
+ }
96
+ else if (value === "*") {
97
+ const value = name();
98
+ yield { type: "WILDCARD", index: i, value };
99
+ }
100
+ else {
101
+ yield { type: "CHAR", index: i, value: chars[i++] };
102
+ }
103
+ }
104
+ return { type: "END", index: i, value: "" };
105
+ }
106
+ class Iter {
107
+ constructor(tokens) {
108
+ this.tokens = tokens;
109
+ }
110
+ peek() {
111
+ if (!this._peek) {
112
+ const next = this.tokens.next();
113
+ this._peek = next.value;
114
+ }
115
+ return this._peek;
116
+ }
117
+ tryConsume(type) {
118
+ const token = this.peek();
119
+ if (token.type !== type)
120
+ return;
121
+ this._peek = undefined; // Reset after consumed.
122
+ return token.value;
123
+ }
124
+ consume(type) {
125
+ const value = this.tryConsume(type);
126
+ if (value !== undefined)
127
+ return value;
128
+ const { type: nextType, index } = this.peek();
129
+ throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`);
130
+ }
131
+ text() {
132
+ let result = "";
133
+ let value;
134
+ while ((value = this.tryConsume("CHAR") || this.tryConsume("ESCAPED"))) {
135
+ result += value;
136
+ }
137
+ return result;
138
+ }
139
+ }
140
+ /**
141
+ * Tokenized path instance.
142
+ */
143
+ class TokenData {
144
+ constructor(tokens) {
145
+ this.tokens = tokens;
146
+ }
147
+ }
148
+ dist.TokenData = TokenData;
149
+ /**
150
+ * Parse a string for the raw tokens.
151
+ */
152
+ function parse(str, options = {}) {
153
+ const { encodePath = NOOP_VALUE } = options;
154
+ const it = new Iter(lexer(str));
155
+ function consume(endType) {
156
+ const tokens = [];
157
+ while (true) {
158
+ const path = it.text();
159
+ if (path)
160
+ tokens.push({ type: "text", value: encodePath(path) });
161
+ const param = it.tryConsume("PARAM");
162
+ if (param) {
163
+ tokens.push({
164
+ type: "param",
165
+ name: param,
166
+ });
167
+ continue;
168
+ }
169
+ const wildcard = it.tryConsume("WILDCARD");
170
+ if (wildcard) {
171
+ tokens.push({
172
+ type: "wildcard",
173
+ name: wildcard,
174
+ });
175
+ continue;
176
+ }
177
+ const open = it.tryConsume("{");
178
+ if (open) {
179
+ tokens.push({
180
+ type: "group",
181
+ tokens: consume("}"),
182
+ });
183
+ continue;
184
+ }
185
+ it.consume(endType);
186
+ return tokens;
187
+ }
188
+ }
189
+ const tokens = consume("END");
190
+ return new TokenData(tokens);
191
+ }
192
+ /**
193
+ * Compile a string to a template function for the path.
194
+ */
195
+ function compile(path, options = {}) {
196
+ const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } = options;
197
+ const data = path instanceof TokenData ? path : parse(path, options);
198
+ const fn = tokensToFunction(data.tokens, delimiter, encode);
199
+ return function path(data = {}) {
200
+ const [path, ...missing] = fn(data);
201
+ if (missing.length) {
202
+ throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
203
+ }
204
+ return path;
205
+ };
206
+ }
207
+ function tokensToFunction(tokens, delimiter, encode) {
208
+ const encoders = tokens.map((token) => tokenToFunction(token, delimiter, encode));
209
+ return (data) => {
210
+ const result = [""];
211
+ for (const encoder of encoders) {
212
+ const [value, ...extras] = encoder(data);
213
+ result[0] += value;
214
+ result.push(...extras);
215
+ }
216
+ return result;
217
+ };
218
+ }
219
+ /**
220
+ * Convert a single token into a path building function.
221
+ */
222
+ function tokenToFunction(token, delimiter, encode) {
223
+ if (token.type === "text")
224
+ return () => [token.value];
225
+ if (token.type === "group") {
226
+ const fn = tokensToFunction(token.tokens, delimiter, encode);
227
+ return (data) => {
228
+ const [value, ...missing] = fn(data);
229
+ if (!missing.length)
230
+ return [value];
231
+ return [""];
232
+ };
233
+ }
234
+ const encodeValue = encode || NOOP_VALUE;
235
+ if (token.type === "wildcard" && encode !== false) {
236
+ return (data) => {
237
+ const value = data[token.name];
238
+ if (value == null)
239
+ return ["", token.name];
240
+ if (!Array.isArray(value) || value.length === 0) {
241
+ throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
242
+ }
243
+ return [
244
+ value
245
+ .map((value, index) => {
246
+ if (typeof value !== "string") {
247
+ throw new TypeError(`Expected "${token.name}/${index}" to be a string`);
248
+ }
249
+ return encodeValue(value);
250
+ })
251
+ .join(delimiter),
252
+ ];
253
+ };
254
+ }
255
+ return (data) => {
256
+ const value = data[token.name];
257
+ if (value == null)
258
+ return ["", token.name];
259
+ if (typeof value !== "string") {
260
+ throw new TypeError(`Expected "${token.name}" to be a string`);
261
+ }
262
+ return [encodeValue(value)];
263
+ };
264
+ }
265
+ /**
266
+ * Transform a path into a match function.
267
+ */
268
+ function match(path, options = {}) {
269
+ const { decode = decodeURIComponent, delimiter = DEFAULT_DELIMITER } = options;
270
+ const { regexp, keys } = pathToRegexp(path, options);
271
+ const decoders = keys.map((key) => {
272
+ if (decode === false)
273
+ return NOOP_VALUE;
274
+ if (key.type === "param")
275
+ return decode;
276
+ return (value) => value.split(delimiter).map(decode);
277
+ });
278
+ return function match(input) {
279
+ const m = regexp.exec(input);
280
+ if (!m)
281
+ return false;
282
+ const path = m[0];
283
+ const params = Object.create(null);
284
+ for (let i = 1; i < m.length; i++) {
285
+ if (m[i] === undefined)
286
+ continue;
287
+ const key = keys[i - 1];
288
+ const decoder = decoders[i - 1];
289
+ params[key.name] = decoder(m[i]);
290
+ }
291
+ return { path, params };
292
+ };
293
+ }
294
+ function pathToRegexp(path, options = {}) {
295
+ const { delimiter = DEFAULT_DELIMITER, end = true, sensitive = false, trailing = true, } = options;
296
+ const keys = [];
297
+ const sources = [];
298
+ const flags = sensitive ? "" : "i";
299
+ const paths = Array.isArray(path) ? path : [path];
300
+ const items = paths.map((path) => path instanceof TokenData ? path : parse(path, options));
301
+ for (const { tokens } of items) {
302
+ for (const seq of flatten(tokens, 0, [])) {
303
+ const regexp = sequenceToRegExp(seq, delimiter, keys);
304
+ sources.push(regexp);
305
+ }
306
+ }
307
+ let pattern = `^(?:${sources.join("|")})`;
308
+ if (trailing)
309
+ pattern += `(?:${escape(delimiter)}$)?`;
310
+ pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
311
+ const regexp = new RegExp(pattern, flags);
312
+ return { regexp, keys };
313
+ }
314
+ /**
315
+ * Generate a flat list of sequence tokens from the given tokens.
316
+ */
317
+ function* flatten(tokens, index, init) {
318
+ if (index === tokens.length) {
319
+ return yield init;
320
+ }
321
+ const token = tokens[index];
322
+ if (token.type === "group") {
323
+ const fork = init.slice();
324
+ for (const seq of flatten(token.tokens, 0, fork)) {
325
+ yield* flatten(tokens, index + 1, seq);
326
+ }
327
+ }
328
+ else {
329
+ init.push(token);
330
+ }
331
+ yield* flatten(tokens, index + 1, init);
332
+ }
333
+ /**
334
+ * Transform a flat sequence of tokens into a regular expression.
335
+ */
336
+ function sequenceToRegExp(tokens, delimiter, keys) {
337
+ let result = "";
338
+ let backtrack = "";
339
+ let isSafeSegmentParam = true;
340
+ for (let i = 0; i < tokens.length; i++) {
341
+ const token = tokens[i];
342
+ if (token.type === "text") {
343
+ result += escape(token.value);
344
+ backtrack += token.value;
345
+ isSafeSegmentParam || (isSafeSegmentParam = token.value.includes(delimiter));
346
+ continue;
347
+ }
348
+ if (token.type === "param" || token.type === "wildcard") {
349
+ if (!isSafeSegmentParam && !backtrack) {
350
+ throw new TypeError(`Missing text after "${token.name}": ${DEBUG_URL}`);
351
+ }
352
+ if (token.type === "param") {
353
+ result += `(${negate(delimiter, isSafeSegmentParam ? "" : backtrack)}+)`;
354
+ }
355
+ else {
356
+ result += `([\\s\\S]+)`;
357
+ }
358
+ keys.push(token);
359
+ backtrack = "";
360
+ isSafeSegmentParam = false;
361
+ continue;
362
+ }
363
+ }
364
+ return result;
365
+ }
366
+ function negate(delimiter, backtrack) {
367
+ if (backtrack.length < 2) {
368
+ if (delimiter.length < 2)
369
+ return `[^${escape(delimiter + backtrack)}]`;
370
+ return `(?:(?!${escape(delimiter)})[^${escape(backtrack)}])`;
371
+ }
372
+ if (delimiter.length < 2) {
373
+ return `(?:(?!${escape(backtrack)})[^${escape(delimiter)}])`;
374
+ }
375
+ return `(?:(?!${escape(backtrack)}|${escape(delimiter)})[\\s\\S])`;
376
+ }
377
+ /**
378
+ * Stringify token data into a path string.
379
+ */
380
+ function stringify(data) {
381
+ return data.tokens
382
+ .map(function stringifyToken(token, index, tokens) {
383
+ if (token.type === "text")
384
+ return escapeText(token.value);
385
+ if (token.type === "group") {
386
+ return `{${token.tokens.map(stringifyToken).join("")}}`;
387
+ }
388
+ const isSafe = isNameSafe(token.name) && isNextNameSafe(tokens[index + 1]);
389
+ const key = isSafe ? token.name : JSON.stringify(token.name);
390
+ if (token.type === "param")
391
+ return `:${key}`;
392
+ if (token.type === "wildcard")
393
+ return `*${key}`;
394
+ throw new TypeError(`Unexpected token: ${token}`);
395
+ })
396
+ .join("");
397
+ }
398
+ function isNameSafe(name) {
399
+ const [first, ...rest] = name;
400
+ if (!ID_START.test(first))
401
+ return false;
402
+ return rest.every((char) => ID_CONTINUE.test(char));
403
+ }
404
+ function isNextNameSafe(token) {
405
+ if ((token === null || token === void 0 ? void 0 : token.type) !== "text")
406
+ return true;
407
+ return !ID_CONTINUE.test(token.value[0]);
408
+ }
409
+
410
+ return dist;
411
+ }
412
+
413
+ var distExports = requireDist();
414
+
415
+ /**
416
+ * SimpleRouter
417
+ */
418
+ class SimpleRouter {
419
+ routes = [];
420
+ constructor() {
421
+ // console.log('AppSimple initialized');
422
+ }
423
+ use(method, route, ...fns) {
424
+ const handlers = Array.isArray(fns) ? fns.flat() : [];
425
+ const pattern = distExports.pathToRegexp(route);
426
+ this.routes.push({ method: method.toLowerCase(), regexp: pattern.regexp, keys: pattern.keys, handlers });
427
+ return this;
428
+ }
429
+ get(route, ...fns) {
430
+ return this.use('get', route, ...fns);
431
+ }
432
+ post(route, ...fns) {
433
+ return this.use('post', route, ...fns);
434
+ }
435
+ parse(req, res) {
436
+ const { pathname } = new URL(req.url, 'http://localhost');
437
+ const method = req.method.toLowerCase();
438
+ const route = this.routes.find((route) => {
439
+ const matchResult = route.regexp.exec(pathname);
440
+ if (matchResult && route.method === method) {
441
+ const params = {};
442
+ route.keys.forEach((key, i) => {
443
+ params[key.name] = matchResult[i + 1];
444
+ });
445
+ req.params = params;
446
+ return true;
447
+ }
448
+ });
449
+ if (route) {
450
+ const { handlers } = route;
451
+ return handlers.reduce((promiseChain, handler) => promiseChain.then(() => Promise.resolve(handler(req, res))), Promise.resolve());
452
+ }
453
+ return 'not_found';
454
+ }
455
+ }
456
+
457
+ export { SimpleRouter };
package/dist/router.js CHANGED
@@ -5764,10 +5764,11 @@ class QueryRouter {
5764
5764
  catch (e) {
5765
5765
  if (route?.isDebug) {
5766
5766
  console.error('=====debug====:middlerware error');
5767
+ console.error('=====debug====:', e);
5767
5768
  console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
5768
5769
  console.error('=====debug====:', e.message);
5769
5770
  }
5770
- if (e instanceof CustomError) {
5771
+ if (e instanceof CustomError || e?.code) {
5771
5772
  ctx.code = e.code;
5772
5773
  ctx.message = e.message;
5773
5774
  ctx.body = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@kevisual/router",
4
- "version": "0.0.4",
4
+ "version": "0.0.5",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -41,6 +41,7 @@
41
41
  "url": "git+https://github.com/abearxiong/kevisual-router.git"
42
42
  },
43
43
  "dependencies": {
44
+ "path-to-regexp": "^8.2.0",
44
45
  "selfsigned": "^2.4.1",
45
46
  "ws": "^8.18.0"
46
47
  },
@@ -59,6 +60,10 @@
59
60
  "./sign": {
60
61
  "import": "./dist/router-sign.js",
61
62
  "require": "./dist/router-sign.js"
63
+ },
64
+ "./simple": {
65
+ "import": "./dist/router-simple.js",
66
+ "require": "./dist/router-simple.js"
62
67
  }
63
68
  }
64
69
  }