@h3ravel/router 0.3.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @h3ravel/router
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8ceb2c1: implement the Application class directly since it already implements the IClass contract
8
+
9
+ ### Patch Changes
10
+
11
+ - a27f452: chore: fix all linting issues.
12
+ - c906050: chore: migrate tests suite to jest
13
+ - Updated dependencies [8ceb2c1]
14
+ - Updated dependencies [a27f452]
15
+ - Updated dependencies [c906050]
16
+ - @h3ravel/core@0.4.0
17
+ - @h3ravel/shared@0.4.0
18
+ - @h3ravel/support@0.4.0
19
+ - @h3ravel/http@2.0.0
20
+
3
21
  ## 0.3.0
4
22
 
5
23
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h3ravel/router",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Route facade, decorators and controller system for H3ravel.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,10 +10,10 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "h3": "^2.0.0-beta.1",
13
- "@h3ravel/core": "0.3.0",
14
- "@h3ravel/http": "1.0.0",
15
- "@h3ravel/support": "0.3.0",
16
- "@h3ravel/shared": "0.3.0"
13
+ "@h3ravel/http": "2.0.0",
14
+ "@h3ravel/support": "0.4.0",
15
+ "@h3ravel/core": "0.4.0",
16
+ "@h3ravel/shared": "0.4.0"
17
17
  },
18
18
  "scripts": {
19
19
  "barrel": "barrelsby --directory src --delete --singleQuotes",
@@ -21,6 +21,6 @@
21
21
  "dev": "tsx watch src/index.ts",
22
22
  "start": "node dist/index.js",
23
23
  "lint": "eslint . --ext .ts",
24
- "test": "vitest"
24
+ "test": "jest --passWithNoTests"
25
25
  }
26
26
  }
@@ -1,10 +1,10 @@
1
- import { readFile, stat } from "node:fs/promises";
1
+ import { readFile, stat } from 'node:fs/promises'
2
2
 
3
3
  import { ServiceProvider } from '@h3ravel/core'
4
- import { before } from "@h3ravel/support";
5
- import { join } from "node:path";
4
+ import { before } from '@h3ravel/support'
5
+ import { join } from 'node:path'
6
6
  import { serveStatic } from 'h3'
7
- import { statSync } from "node:fs";
7
+ import { statSync } from 'node:fs'
8
8
 
9
9
  /**
10
10
  * Handles public assets loading
@@ -20,22 +20,22 @@ export class AssetsServiceProvider extends ServiceProvider {
20
20
 
21
21
  app.middleware(`/${fsconfig.public_mask}/**`, (event) => {
22
22
  return serveStatic(event, {
23
- indexNames: ["/index.html"],
23
+ indexNames: ['/index.html'],
24
24
  getContents: (id) => {
25
25
  const newId = id.replace(`/${fsconfig.public_mask}/`, '')
26
26
  return readFile(join(before(publicPath, newId), newId))
27
27
  },
28
28
  getMeta: async (id) => {
29
29
  const newId = id.replace(`/${fsconfig.public_mask}/`, '')
30
- const stats = await stat(join(before(publicPath, newId), newId)).catch(() => { });
30
+ const stats = await stat(join(before(publicPath, newId), newId)).catch(() => { })
31
31
  if (stats?.isFile()) {
32
32
  return {
33
33
  size: stats.size,
34
34
  mtime: stats.mtimeMs,
35
- };
35
+ }
36
36
  }
37
37
  },
38
- });
38
+ })
39
39
  })
40
40
 
41
41
  this.app.singleton('asset', () => {
@@ -1,4 +1,4 @@
1
- import { Router } from '../Router'
1
+ import { Router } from '../Route'
2
2
  import { ServiceProvider } from '@h3ravel/core'
3
3
  import path from 'node:path'
4
4
  import { readdir } from 'node:fs/promises'
@@ -27,7 +27,7 @@ export class RouteServiceProvider extends ServiceProvider {
27
27
  try {
28
28
  const routePath = this.app.getPath('routes')
29
29
 
30
- const files = await readdir(routePath);
30
+ const files = await readdir(routePath)
31
31
 
32
32
  for (let i = 0; i < files.length; i++) {
33
33
  const routesModule = await import(path.join(routePath, files[i]))
package/src/Route.ts CHANGED
@@ -1 +1,182 @@
1
- export default class { }
1
+ import { H3Event, Middleware, MiddlewareOptions, type H3 } from 'h3'
2
+ import { Request, Response } from '@h3ravel/http'
3
+ import { Application, Controller, Kernel } from '@h3ravel/core'
4
+ import { afterLast } from '@h3ravel/support'
5
+ import { EventHandler, IController, IMiddleware } from '@h3ravel/shared'
6
+
7
+ interface RouteDefinition {
8
+ method: string
9
+ path: string
10
+ name?: string
11
+ handler: EventHandler
12
+ }
13
+
14
+ export class Router {
15
+ private routes: RouteDefinition[] = []
16
+ private groupPrefix = ''
17
+ private groupMiddleware: EventHandler[] = []
18
+
19
+ constructor(protected h3App: H3, private app: Application) { }
20
+
21
+ /**
22
+ * Route Resolver
23
+ *
24
+ * @param handler
25
+ * @param middleware
26
+ * @returns
27
+ */
28
+ private resolveHandler (handler: EventHandler, middleware: IMiddleware[] = []) {
29
+ return async (event: H3Event) => {
30
+ const kernel = new Kernel(() => ({
31
+ request: new Request(event),
32
+ response: new Response(event)
33
+ }), middleware)
34
+
35
+ return kernel.handle(event, (ctx) => Promise.resolve(handler(ctx)))
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Add a route to the stack
41
+ *
42
+ * @param method
43
+ * @param path
44
+ * @param handler
45
+ * @param name
46
+ * @param middleware
47
+ */
48
+ private addRoute (
49
+ method: string,
50
+ path: string,
51
+ handler: EventHandler,
52
+ name?: string,
53
+ middleware: IMiddleware[] = []
54
+ ) {
55
+ const fullPath = `${this.groupPrefix}${path}`.replace(/\/+/g, '/')
56
+ this.routes.push({ method, path: fullPath, name, handler })
57
+ this.h3App[method as 'get'](fullPath, this.resolveHandler(handler, middleware))
58
+ }
59
+
60
+ private resolveControllerOrHandler (
61
+ handler: EventHandler | (new (...args: any[]) => IController),
62
+ methodName?: string
63
+ ): EventHandler {
64
+ if (typeof handler === 'function' && (handler as any).prototype instanceof Controller) {
65
+ return (ctx) => {
66
+ const controller = new (handler as new (...args: any[]) => IController)(this.app)
67
+ const action = (methodName || 'index') as keyof IController
68
+
69
+ if (typeof controller[action] !== 'function') {
70
+ throw new Error(`Method "${String(action)}" not found on controller ${handler.name}`)
71
+ }
72
+
73
+ return controller[action](ctx)
74
+ }
75
+ }
76
+
77
+ return handler as EventHandler
78
+ }
79
+
80
+
81
+ get (
82
+ path: string,
83
+ handler: EventHandler | (new (...args: any[]) => IController),
84
+ methodName?: string, name?: string, middleware: IMiddleware[] = []
85
+ ) {
86
+ this.addRoute('get', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
87
+ }
88
+
89
+ post (
90
+ path: string,
91
+ handler: EventHandler | (new (...args: any[]) => IController),
92
+ methodName?: string, name?: string, middleware: IMiddleware[] = []
93
+ ) {
94
+ this.addRoute('post', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
95
+ }
96
+
97
+ put (
98
+ path: string,
99
+ handler: EventHandler | (new (...args: any[]) => IController),
100
+ methodName?: string, name?: string, middleware: IMiddleware[] = []
101
+ ) {
102
+ this.addRoute('put', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
103
+ }
104
+
105
+ delete (
106
+ path: string,
107
+ handler: EventHandler | (new (...args: any[]) => IController),
108
+ methodName?: string, name?: string, middleware: IMiddleware[] = []
109
+ ) {
110
+ this.addRoute('delete', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
111
+ }
112
+
113
+ /**
114
+ * API Resource support
115
+ *
116
+ * @param path
117
+ * @param controller
118
+ */
119
+ apiResource (
120
+ path: string,
121
+ Controller: new (app: Application) => IController,
122
+ middleware: IMiddleware[] = []
123
+ ) {
124
+ path = path.replace(/\//g, '/')
125
+
126
+ const name = afterLast(path, '/')
127
+ const basePath = `/${path}`.replace(/\/+/g, '/')
128
+
129
+ const controller = new Controller(this.app)
130
+
131
+ this.addRoute('get', basePath, controller.index.bind(controller), `${name}.index`, middleware)
132
+ this.addRoute('post', basePath, controller.store.bind(controller), `${name}.store`, middleware)
133
+ this.addRoute('get', `${basePath}/:id`, controller.show.bind(controller), `${name}.show`, middleware)
134
+ this.addRoute('put', `${basePath}/:id`, controller.update.bind(controller), `${name}.update`, middleware)
135
+ this.addRoute('patch', `${basePath}/:id`, controller.update.bind(controller), `${name}.update`, middleware)
136
+ this.addRoute('delete', `${basePath}/:id`, controller.destroy.bind(controller), `${name}.destroy`, middleware)
137
+ }
138
+
139
+ /**
140
+ * Named route URL generator
141
+ *
142
+ * @param name
143
+ * @param params
144
+ * @returns
145
+ */
146
+ route (name: string, params: Record<string, string> = {}): string | undefined {
147
+ const found = this.routes.find(r => r.name === name)
148
+ if (!found) return undefined
149
+
150
+ let url = found.path
151
+ for (const [key, value] of Object.entries(params)) {
152
+ url = url.replace(`:${key}`, value)
153
+ }
154
+ return url
155
+ }
156
+
157
+ /**
158
+ * Grouping
159
+ *
160
+ * @param options
161
+ * @param callback
162
+ */
163
+ group (options: { prefix?: string; middleware?: EventHandler[] }, callback: () => void) {
164
+ const prevPrefix = this.groupPrefix
165
+ const prevMiddleware = [...this.groupMiddleware]
166
+
167
+ this.groupPrefix += options.prefix || ''
168
+ this.groupMiddleware.push(...(options.middleware || []))
169
+
170
+ callback()
171
+
172
+ /**
173
+ * Restore state after group
174
+ */
175
+ this.groupPrefix = prevPrefix
176
+ this.groupMiddleware = prevMiddleware
177
+ }
178
+
179
+ middleware (path: string, handler: Middleware, opts?: MiddlewareOptions) {
180
+ this.h3App.use(path, handler, opts)
181
+ }
182
+ }
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  export * from './Controller';
6
6
  export * from './Route';
7
- export * from './Router';
8
7
  export * from './Decorators/ApiResource';
9
8
  export * from './Decorators/Controller';
10
9
  export * from './Decorators/Get';
package/src/Router.ts DELETED
@@ -1,182 +0,0 @@
1
- import { H3Event, Middleware, MiddlewareOptions, type H3 } from 'h3'
2
- import { Request, Response } from '@h3ravel/http'
3
- import { Controller, Kernel } from '@h3ravel/core'
4
- import { afterLast } from '@h3ravel/support'
5
- import { EventHandler, HttpContext, IApplication, IController, IMiddleware } from '@h3ravel/shared'
6
-
7
- interface RouteDefinition {
8
- method: string
9
- path: string
10
- name?: string
11
- handler: EventHandler
12
- }
13
-
14
- export class Router {
15
- private routes: RouteDefinition[] = []
16
- private groupPrefix = ''
17
- private groupMiddleware: EventHandler[] = []
18
-
19
- constructor(protected h3App: H3, private app: IApplication) { }
20
-
21
- /**
22
- * Route Resolver
23
- *
24
- * @param handler
25
- * @param middleware
26
- * @returns
27
- */
28
- private resolveHandler (handler: EventHandler, middleware: IMiddleware[] = []) {
29
- return async (event: H3Event) => {
30
- const kernel = new Kernel(() => ({
31
- request: new Request(event),
32
- response: new Response(event)
33
- }), middleware)
34
-
35
- return kernel.handle(event, (ctx) => Promise.resolve(handler(ctx)))
36
- }
37
- }
38
-
39
- /**
40
- * Add a route to the stack
41
- *
42
- * @param method
43
- * @param path
44
- * @param handler
45
- * @param name
46
- * @param middleware
47
- */
48
- private addRoute (
49
- method: string,
50
- path: string,
51
- handler: EventHandler,
52
- name?: string,
53
- middleware: IMiddleware[] = []
54
- ) {
55
- const fullPath = `${this.groupPrefix}${path}`.replace(/\/+/g, '/')
56
- this.routes.push({ method, path: fullPath, name, handler })
57
- this.h3App[method as 'get'](fullPath, this.resolveHandler(handler, middleware))
58
- }
59
-
60
- private resolveControllerOrHandler (
61
- handler: EventHandler | (new (...args: any[]) => IController),
62
- methodName?: string
63
- ): EventHandler {
64
- if (typeof handler === 'function' && (handler as any).prototype instanceof Controller) {
65
- return (ctx) => {
66
- const controller = new (handler as new (...args: any[]) => IController)(this.app)
67
- const action = (methodName || 'index') as keyof IController
68
-
69
- if (typeof controller[action] !== 'function') {
70
- throw new Error(`Method "${String(action)}" not found on controller ${handler.name}`)
71
- }
72
-
73
- return controller[action](ctx)
74
- }
75
- }
76
-
77
- return handler as EventHandler
78
- }
79
-
80
-
81
- get (
82
- path: string,
83
- handler: EventHandler | (new (...args: any[]) => IController),
84
- methodName?: string, name?: string, middleware: IMiddleware[] = []
85
- ) {
86
- this.addRoute('get', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
87
- }
88
-
89
- post (
90
- path: string,
91
- handler: EventHandler | (new (...args: any[]) => IController),
92
- methodName?: string, name?: string, middleware: IMiddleware[] = []
93
- ) {
94
- this.addRoute('post', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
95
- }
96
-
97
- put (
98
- path: string,
99
- handler: EventHandler | (new (...args: any[]) => IController),
100
- methodName?: string, name?: string, middleware: IMiddleware[] = []
101
- ) {
102
- this.addRoute('put', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
103
- }
104
-
105
- delete (
106
- path: string,
107
- handler: EventHandler | (new (...args: any[]) => IController),
108
- methodName?: string, name?: string, middleware: IMiddleware[] = []
109
- ) {
110
- this.addRoute('delete', path, this.resolveControllerOrHandler(handler, methodName), name, middleware)
111
- }
112
-
113
- /**
114
- * API Resource support
115
- *
116
- * @param path
117
- * @param controller
118
- */
119
- apiResource (
120
- path: string,
121
- Controller: new (app: IApplication) => IController,
122
- middleware: IMiddleware[] = []
123
- ) {
124
- path = path.replace(/\//g, '/')
125
-
126
- const name = afterLast(path, '/')
127
- const basePath = `/${path}`.replace(/\/+/g, '/')
128
-
129
- const controller = new Controller(this.app)
130
-
131
- this.addRoute('get', basePath, controller.index.bind(controller), `${name}.index`, middleware)
132
- this.addRoute('post', basePath, controller.store.bind(controller), `${name}.store`, middleware)
133
- this.addRoute('get', `${basePath}/:id`, controller.show.bind(controller), `${name}.show`, middleware)
134
- this.addRoute('put', `${basePath}/:id`, controller.update.bind(controller), `${name}.update`, middleware)
135
- this.addRoute('patch', `${basePath}/:id`, controller.update.bind(controller), `${name}.update`, middleware)
136
- this.addRoute('delete', `${basePath}/:id`, controller.destroy.bind(controller), `${name}.destroy`, middleware)
137
- }
138
-
139
- /**
140
- * Named route URL generator
141
- *
142
- * @param name
143
- * @param params
144
- * @returns
145
- */
146
- route (name: string, params: Record<string, string> = {}): string | undefined {
147
- const found = this.routes.find(r => r.name === name)
148
- if (!found) return undefined
149
-
150
- let url = found.path
151
- for (const [key, value] of Object.entries(params)) {
152
- url = url.replace(`:${key}`, value)
153
- }
154
- return url
155
- }
156
-
157
- /**
158
- * Grouping
159
- *
160
- * @param options
161
- * @param callback
162
- */
163
- group (options: { prefix?: string; middleware?: EventHandler[] }, callback: () => void) {
164
- const prevPrefix = this.groupPrefix
165
- const prevMiddleware = [...this.groupMiddleware]
166
-
167
- this.groupPrefix += options.prefix || ''
168
- this.groupMiddleware.push(...(options.middleware || []))
169
-
170
- callback()
171
-
172
- /**
173
- * Restore state after group
174
- */
175
- this.groupPrefix = prevPrefix
176
- this.groupMiddleware = prevMiddleware
177
- }
178
-
179
- middleware (path: string, handler: Middleware, opts?: MiddlewareOptions) {
180
- this.h3App.use(path, handler, opts)
181
- }
182
- }