@eggjs/router 2.0.1 → 3.0.1

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.
@@ -0,0 +1,7 @@
1
+ import { Router } from './Router.js';
2
+ export type * from './types.js';
3
+ export * from './Layer.js';
4
+ export * from './Router.js';
5
+ export * from './EggRouter.js';
6
+ export declare const KoaRouter: typeof Router;
7
+ export default Router;
@@ -0,0 +1,7 @@
1
+ import { Router } from './Router.js';
2
+ export * from './Layer.js';
3
+ export * from './Router.js';
4
+ export * from './EggRouter.js';
5
+ export const KoaRouter = Router;
6
+ export default Router;
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUdyQyxjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGdCQUFnQixDQUFDO0FBRS9CLE1BQU0sQ0FBQyxNQUFNLFNBQVMsR0FBRyxNQUFNLENBQUM7QUFDaEMsZUFBZSxNQUFNLENBQUMifQ==
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,18 @@
1
+ export type Next = () => Promise<void>;
2
+ export type MiddlewareFunc = (ctx: any, next: Next) => Promise<void> | void;
3
+ export type MiddlewareFuncWithParamProperty = MiddlewareFunc & {
4
+ param?: string;
5
+ };
6
+ export type ParamMiddlewareFunc = (param: string, ctx: any, next: Next) => Promise<void> | void;
7
+ export type MiddlewareFuncWithRouter<T> = MiddlewareFunc & {
8
+ router: T;
9
+ };
10
+ export interface ResourcesController {
11
+ index?: MiddlewareFunc;
12
+ new?: MiddlewareFunc;
13
+ create?: MiddlewareFunc;
14
+ show?: MiddlewareFunc;
15
+ edit?: MiddlewareFunc;
16
+ update?: MiddlewareFunc;
17
+ destroy?: MiddlewareFunc;
18
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
package/package.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "@eggjs/router",
3
- "version": "2.0.1",
3
+ "version": "3.0.1",
4
+ "engines": {
5
+ "node": ">= 18.7.0"
6
+ },
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
4
10
  "description": "Router middleware for egg/koa. Provides RESTful resource routing.",
5
11
  "repository": {
6
12
  "type": "git",
@@ -9,10 +15,6 @@
9
15
  "bugs": {
10
16
  "url": "https://github.com/eggjs/egg/issues"
11
17
  },
12
- "files": [
13
- "lib",
14
- "index.js"
15
- ],
16
18
  "author": "eggjs",
17
19
  "keywords": [
18
20
  "koa",
@@ -21,41 +23,70 @@
21
23
  "route"
22
24
  ],
23
25
  "dependencies": {
24
- "co": "^4.6.0",
25
- "debug": "^3.1.0",
26
- "http-errors": "^1.3.1",
27
- "inflection": "^1.12.0",
28
- "is-type-of": "^1.2.1",
29
- "koa-compose": "^3.0.0",
30
- "koa-convert": "^1.2.0",
31
- "methods": "^1.0.1",
26
+ "http-errors": "^2.0.0",
27
+ "inflection": "^3.0.0",
28
+ "is-type-of": "^2.1.0",
29
+ "koa-compose": "^4.1.0",
30
+ "methods": "^1.1.2",
32
31
  "path-to-regexp": "^1.1.1",
33
- "urijs": "^1.19.0",
34
- "utility": "^1.15.0"
32
+ "urijs": "^1.19.11",
33
+ "utility": "^2.1.0"
35
34
  },
36
35
  "devDependencies": {
37
- "egg-bin": "^4.10.0",
38
- "egg-ci": "^1.11.0",
39
- "eslint": "^5.13.0",
40
- "eslint-config-egg": "^7.1.0",
41
- "expect.js": "^0.3.1",
42
- "koa": "^2.7.0",
43
- "mocha": "^2.0.1",
44
- "should": "^6.0.3",
45
- "supertest": "^1.0.1"
36
+ "@eggjs/koa": "^2.18.1",
37
+ "@eggjs/tsconfig": "^1.3.3",
38
+ "@types/koa-compose": "^3.2.8",
39
+ "@types/methods": "^1.1.4",
40
+ "@types/mocha": "^10.0.6",
41
+ "@types/supertest": "^6.0.2",
42
+ "@types/urijs": "^1.19.25",
43
+ "egg-bin": "6",
44
+ "eslint": "8",
45
+ "eslint-config-egg": "13",
46
+ "git-contributor": "^2.1.5",
47
+ "supertest": "^1.0.1",
48
+ "tshy": "^1.15.1",
49
+ "tshy-after": "^1.0.0",
50
+ "typescript": "^5.4.5"
46
51
  },
47
52
  "scripts": {
53
+ "lint": "eslint src test --ext ts",
54
+ "pretest": "npm run lint -- --fix && npm run prepublishOnly",
55
+ "test": "egg-bin test",
48
56
  "test-local": "egg-bin test",
49
- "test": "npm run lint && egg-bin test",
50
- "ci": "npm run lint && egg-bin cov",
51
- "lint": "eslint ."
57
+ "preci": "npm run lint && npm run prepublishOnly",
58
+ "ci": "egg-bin cov",
59
+ "contributor": "git-contributor",
60
+ "prepublishOnly": "tshy && tshy-after",
61
+ "bench": "cd bench && make"
52
62
  },
53
- "ci": {
54
- "type": "github",
55
- "version": "8, 10, 11"
63
+ "license": "MIT",
64
+ "files": [
65
+ "dist",
66
+ "src"
67
+ ],
68
+ "type": "module",
69
+ "tshy": {
70
+ "exports": {
71
+ "./package.json": "./package.json",
72
+ ".": "./src/index.ts"
73
+ }
56
74
  },
57
- "engines": {
58
- "node": ">= 8"
75
+ "exports": {
76
+ "./package.json": "./package.json",
77
+ ".": {
78
+ "import": {
79
+ "source": "./src/index.ts",
80
+ "types": "./dist/esm/index.d.ts",
81
+ "default": "./dist/esm/index.js"
82
+ },
83
+ "require": {
84
+ "source": "./src/index.ts",
85
+ "types": "./dist/commonjs/index.d.ts",
86
+ "default": "./dist/commonjs/index.js"
87
+ }
88
+ }
59
89
  },
60
- "license": "MIT"
90
+ "main": "./dist/commonjs/index.js",
91
+ "types": "./dist/commonjs/index.d.ts"
61
92
  }
@@ -0,0 +1,338 @@
1
+ import assert from 'node:assert';
2
+ import { encodeURIComponent as safeEncodeURIComponent } from 'utility';
3
+ import inflection from 'inflection';
4
+ import methods from 'methods';
5
+ import { RegisterOptions, Router, RouterMethod, RouterOptions } from './Router.js';
6
+ import { MiddlewareFunc, ResourcesController } from './types.js';
7
+
8
+ interface RestfulOptions {
9
+ suffix?: string;
10
+ namePrefix?: string;
11
+ method: string | string[];
12
+ member?: true;
13
+ }
14
+
15
+ const REST_MAP: Record<string, RestfulOptions> = {
16
+ index: {
17
+ suffix: '',
18
+ method: 'GET',
19
+ },
20
+ new: {
21
+ namePrefix: 'new_',
22
+ member: true,
23
+ suffix: 'new',
24
+ method: 'GET',
25
+ },
26
+ create: {
27
+ suffix: '',
28
+ method: 'POST',
29
+ },
30
+ show: {
31
+ member: true,
32
+ suffix: ':id',
33
+ method: 'GET',
34
+ },
35
+ edit: {
36
+ member: true,
37
+ namePrefix: 'edit_',
38
+ suffix: ':id/edit',
39
+ method: 'GET',
40
+ },
41
+ update: {
42
+ member: true,
43
+ namePrefix: '',
44
+ suffix: ':id',
45
+ method: [ 'PATCH', 'PUT' ],
46
+ },
47
+ destroy: {
48
+ member: true,
49
+ namePrefix: 'destroy_',
50
+ suffix: ':id',
51
+ method: 'DELETE',
52
+ },
53
+ };
54
+
55
+ interface Application {
56
+ controller: Record<string, any>;
57
+ }
58
+
59
+ /**
60
+ * FIXME: move these patch into @eggjs/router
61
+ */
62
+ export class EggRouter extends Router {
63
+ readonly app: Application;
64
+
65
+ /**
66
+ * @class
67
+ * @param {Object} opts - Router options.
68
+ * @param {Application} app - Application object.
69
+ */
70
+ constructor(opts: RouterOptions, app: Application) {
71
+ super(opts);
72
+ this.app = app;
73
+ }
74
+
75
+ verb(method: RouterMethod | RouterMethod[],
76
+ nameOrPath: string | RegExp | (string | RegExp)[],
77
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
78
+ ...middleware: (MiddlewareFunc | string)[]) {
79
+ const { path, middlewares, options } = this._formatRouteParams(nameOrPath, pathOrMiddleware, middleware);
80
+ if (typeof method === 'string') {
81
+ method = [ method ];
82
+ }
83
+ this.register(path, method, middlewares, options);
84
+ return this;
85
+ }
86
+
87
+ // const METHODS = [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete', 'all' ];
88
+ head(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
89
+ head(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
90
+ head(nameOrPath: string | RegExp | (string | RegExp)[],
91
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
92
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
93
+ return this.verb('head', nameOrPath, pathOrMiddleware, ...middlewares);
94
+ }
95
+ options(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
96
+ options(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
97
+ options(nameOrPath: string | RegExp | (string | RegExp)[],
98
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
99
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
100
+ return this.verb('options', nameOrPath, pathOrMiddleware, ...middlewares);
101
+ }
102
+ get(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
103
+ get(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
104
+ get(nameOrPath: string | RegExp | (string | RegExp)[],
105
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
106
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
107
+ return this.verb('get', nameOrPath, pathOrMiddleware, ...middlewares);
108
+ }
109
+ put(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
110
+ put(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
111
+ put(nameOrPath: string | RegExp | (string | RegExp)[],
112
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
113
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
114
+ return this.verb('put', nameOrPath, pathOrMiddleware, ...middlewares);
115
+ }
116
+ patch(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
117
+ patch(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
118
+ patch(nameOrPath: string | RegExp | (string | RegExp)[],
119
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
120
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
121
+ return this.verb('patch', nameOrPath, pathOrMiddleware, ...middlewares);
122
+ }
123
+ post(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
124
+ post(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
125
+ post(nameOrPath: string | RegExp | (string | RegExp)[],
126
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
127
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
128
+ return this.verb('post', nameOrPath, pathOrMiddleware, ...middlewares);
129
+ }
130
+ delete(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
131
+ delete(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
132
+ delete(nameOrPath: string | RegExp | (string | RegExp)[],
133
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
134
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
135
+ return this.verb('delete', nameOrPath, pathOrMiddleware, ...middlewares);
136
+ }
137
+ all(path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
138
+ all(name: string, path: string | RegExp | (string | RegExp)[], ...middlewares: (MiddlewareFunc | string)[]): Router;
139
+ all(nameOrPath: string | RegExp | (string | RegExp)[],
140
+ pathOrMiddleware: string | RegExp | (string | RegExp)[] | MiddlewareFunc,
141
+ ...middlewares: (MiddlewareFunc | string)[]): Router {
142
+ return this.verb(methods, nameOrPath, pathOrMiddleware, ...middlewares);
143
+ }
144
+
145
+ register(path: string | RegExp | (string | RegExp)[],
146
+ methods: string[],
147
+ middleware: MiddlewareFunc | string | (MiddlewareFunc | string | ResourcesController)[],
148
+ opts?: RegisterOptions) {
149
+ // patch register to support generator function middleware and string controller
150
+ middleware = Array.isArray(middleware) ? middleware : [ middleware ];
151
+ const middlewares = convertMiddlewares(middleware, this.app);
152
+ return super.register(path, methods, middlewares, opts);
153
+ }
154
+
155
+ /**
156
+ * restful router api
157
+ * @param {String} name - Router name
158
+ * @param {String} prefix - url prefix
159
+ * @param {Function} middleware - middleware or controller
160
+ * @example
161
+ * ```js
162
+ * app.resources('/posts', 'posts')
163
+ * app.resources('posts', '/posts', 'posts')
164
+ * app.resources('posts', '/posts', app.role.can('user'), app.controller.posts)
165
+ * ```
166
+ *
167
+ * Examples:
168
+ *
169
+ * ```js
170
+ * app.resources('/posts', 'posts')
171
+ * ```
172
+ *
173
+ * yield router mapping
174
+ *
175
+ * Method | Path | Route Name | Controller.Action
176
+ * -------|-----------------|----------------|-----------------------------
177
+ * GET | /posts | posts | app.controller.posts.index
178
+ * GET | /posts/new | new_post | app.controller.posts.new
179
+ * GET | /posts/:id | post | app.controller.posts.show
180
+ * GET | /posts/:id/edit | edit_post | app.controller.posts.edit
181
+ * POST | /posts | posts | app.controller.posts.create
182
+ * PATCH | /posts/:id | post | app.controller.posts.update
183
+ * DELETE | /posts/:id | post | app.controller.posts.destroy
184
+ *
185
+ * app.router.url can generate url based on arguments
186
+ * ```js
187
+ * app.router.url('posts')
188
+ * => /posts
189
+ * app.router.url('post', { id: 1 })
190
+ * => /posts/1
191
+ * app.router.url('new_post')
192
+ * => /posts/new
193
+ * app.router.url('edit_post', { id: 1 })
194
+ * => /posts/1/edit
195
+ * ```
196
+ * @return {Router} return route object.
197
+ * @since 1.0.0
198
+ */
199
+ resources(prefix: string, controller: string | ResourcesController): Router;
200
+ resources(prefix: string, middleware: MiddlewareFunc, controller: string | ResourcesController): Router;
201
+ resources(name: string, prefix: string, controller: string | ResourcesController): Router;
202
+ resources(name: string, prefix: string, middleware: MiddlewareFunc, controller: string | ResourcesController): Router;
203
+ resources(nameOrPath: string | RegExp, pathOrMiddleware: string | RegExp | MiddlewareFunc | ResourcesController,
204
+ ...middleware: (MiddlewareFunc | string | ResourcesController)[]): Router {
205
+ const { path, middlewares, options } = this._formatRouteParams(nameOrPath, pathOrMiddleware, middleware);
206
+ // last argument is Controller object
207
+ const controller = resolveController(middlewares.pop()!, this.app);
208
+ for (const key in REST_MAP) {
209
+ const action = controller[key] as MiddlewareFunc;
210
+ if (!action) continue;
211
+
212
+ const opts = REST_MAP[key];
213
+ let routeName;
214
+ if (opts.member) {
215
+ routeName = inflection.singularize(options.name ?? '');
216
+ } else {
217
+ routeName = inflection.pluralize(options.name ?? '');
218
+ }
219
+ if (opts.namePrefix) {
220
+ routeName = opts.namePrefix + routeName;
221
+ }
222
+ const prefix = (path as string).replace(/\/$/, '');
223
+ const urlPath = opts.suffix ? `${prefix}/${opts.suffix}` : prefix;
224
+ const method = Array.isArray(opts.method) ? opts.method : [ opts.method ];
225
+ this.register(urlPath, method, middlewares.concat(action), { name: routeName });
226
+ }
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * @param {String} name - Router name
232
+ * @param {Object} params - more parameters
233
+ * @example
234
+ * ```js
235
+ * router.url('edit_post', { id: 1, name: 'foo', page: 2 })
236
+ * => /posts/1/edit?name=foo&page=2
237
+ * router.url('posts', { name: 'foo&1', page: 2 })
238
+ * => /posts?name=foo%261&page=2
239
+ * ```
240
+ * @return {String} url by path name and query params.
241
+ * @since 1.0.0
242
+ */
243
+ url(name: string, params?: Record<string, string | number | (string | number)[]>): string {
244
+ const route = this.route(name);
245
+ if (!route) return '';
246
+
247
+ const args = params;
248
+ let url = route.path;
249
+
250
+ assert(!(url instanceof RegExp), `Can't get the url for regExp ${url} for by name '${name}'`);
251
+
252
+ const queries = [];
253
+ if (typeof args === 'object' && args !== null) {
254
+ const replacedParams: string[] = [];
255
+ url = url.replace(/:([a-zA-Z_]\w*)/g, ($0, key) => {
256
+ if (key in args) {
257
+ const values = args[key];
258
+ replacedParams.push(key);
259
+ return safeEncodeURIComponent(Array.isArray(values) ? String(values[0]) : String(values));
260
+ }
261
+ return $0;
262
+ });
263
+
264
+ for (const key in args) {
265
+ if (replacedParams.includes(key)) {
266
+ continue;
267
+ }
268
+ const values = args[key];
269
+ const encodedKey = safeEncodeURIComponent(key);
270
+ if (Array.isArray(values)) {
271
+ for (const val of values) {
272
+ queries.push(`${encodedKey}=${safeEncodeURIComponent(String(val))}`);
273
+ }
274
+ } else {
275
+ queries.push(`${encodedKey}=${safeEncodeURIComponent(String(values))}`);
276
+ }
277
+ }
278
+ }
279
+
280
+ if (queries.length > 0) {
281
+ const queryStr = queries.join('&');
282
+ if (!url.includes('?')) {
283
+ url = `${url}?${queryStr}`;
284
+ } else {
285
+ url = `${url}&${queryStr}`;
286
+ }
287
+ }
288
+
289
+ return url;
290
+ }
291
+
292
+ /**
293
+ * @alias to url()
294
+ */
295
+ pathFor(name: string, params?: Record<string, string | number | (string | number)[]>) {
296
+ return this.url(name, params);
297
+ }
298
+ }
299
+
300
+ /**
301
+ * resolve controller from string to function
302
+ * @param {String|Function} controller input controller
303
+ * @param {Application} app egg application instance
304
+ */
305
+ function resolveController(controller: string | MiddlewareFunc | ResourcesController, app: Application) {
306
+ if (typeof controller === 'string') {
307
+ // resolveController('foo.bar.Home', app)
308
+ const actions = controller.split('.');
309
+ let obj = app.controller;
310
+ actions.forEach(key => {
311
+ obj = obj[key];
312
+ if (!obj) throw new Error(`app.controller.${controller} not exists`);
313
+ });
314
+ controller = obj as any;
315
+ }
316
+ // ensure controller is exists
317
+ if (!controller) throw new Error('controller not exists');
318
+ return controller as any;
319
+ }
320
+
321
+ /**
322
+ * 1. ensure controller(last argument) support string
323
+ * - [url, controller]: app.get('/home', 'home');
324
+ * - [name, url, controller(string)]: app.get('posts', '/posts', 'posts.list');
325
+ * - [name, url, controller]: app.get('posts', '/posts', app.controller.posts.list);
326
+ * - [name, url(regexp), controller]: app.get('regRouter', /\/home\/index/, 'home.index');
327
+ * - [name, url, middleware, [...], controller]: `app.get(/user/:id', hasLogin, canGetUser, 'user.show');`
328
+ *
329
+ * 2. make middleware support generator function
330
+ *
331
+ * @param {Array} middlewares middlewares and controller(last middleware)
332
+ * @param {Application} app egg application instance
333
+ */
334
+ function convertMiddlewares(middlewares: (MiddlewareFunc | string | ResourcesController)[], app: Application) {
335
+ // ensure controller is resolved
336
+ const controller = resolveController(middlewares.pop()!, app);
337
+ return [ ...middlewares as MiddlewareFunc[], controller ];
338
+ }