@stacksjs/router 0.64.6 → 0.66.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/dist/index.js +87 -100
- package/dist/index.js.map +343 -358
- package/package.json +20 -25
- package/src/index.ts +1 -1
- package/src/middleware.ts +4 -4
- package/src/request.ts +11 -8
- package/src/router.ts +45 -30
- package/src/server.ts +98 -47
- package/src/utils.ts +9 -23
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.66.0",
|
|
5
5
|
"description": "The Stacks framework router.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
|
+
"contributors": ["Chris Breuer <chris@stacksjs.org>"],
|
|
7
8
|
"license": "MIT",
|
|
8
9
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
|
9
10
|
"homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/router#readme",
|
|
@@ -15,13 +16,7 @@
|
|
|
15
16
|
"bugs": {
|
|
16
17
|
"url": "https://github.com/stacksjs/stacks/issues"
|
|
17
18
|
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"router",
|
|
20
|
-
"stacks",
|
|
21
|
-
"framework",
|
|
22
|
-
"typescript",
|
|
23
|
-
"javascript"
|
|
24
|
-
],
|
|
19
|
+
"keywords": ["router", "stacks", "framework", "typescript", "javascript"],
|
|
25
20
|
"exports": {
|
|
26
21
|
".": {
|
|
27
22
|
"bun": "./src/index.ts",
|
|
@@ -34,27 +29,27 @@
|
|
|
34
29
|
},
|
|
35
30
|
"module": "dist/index.js",
|
|
36
31
|
"types": "dist/index.d.ts",
|
|
37
|
-
"
|
|
38
|
-
"Chris Breuer <chris@stacksjs.org>"
|
|
39
|
-
],
|
|
40
|
-
"files": [
|
|
41
|
-
"README.md",
|
|
42
|
-
"dist",
|
|
43
|
-
"src"
|
|
44
|
-
],
|
|
32
|
+
"files": ["README.md", "dist", "src"],
|
|
45
33
|
"scripts": {
|
|
46
|
-
"build": "bun
|
|
47
|
-
"build:user": "bun
|
|
48
|
-
"typecheck": "bun
|
|
49
|
-
"prepublishOnly": "bun
|
|
34
|
+
"build": "bun build.ts",
|
|
35
|
+
"build:user": "bun build2.ts",
|
|
36
|
+
"typecheck": "bun tsc --noEmit",
|
|
37
|
+
"prepublishOnly": "bun run build"
|
|
50
38
|
},
|
|
51
39
|
"dependencies": {
|
|
52
|
-
"@stacksjs/
|
|
53
|
-
"@stacksjs/
|
|
54
|
-
"
|
|
55
|
-
"
|
|
40
|
+
"@stacksjs/actions": "0.65.1",
|
|
41
|
+
"@stacksjs/config": "0.65.1",
|
|
42
|
+
"@stacksjs/error-handling": "0.65.1",
|
|
43
|
+
"@stacksjs/logging": "0.65.1",
|
|
44
|
+
"@stacksjs/orm": "0.65.1",
|
|
45
|
+
"@stacksjs/path": "0.65.1",
|
|
46
|
+
"@stacksjs/storage": "0.65.1",
|
|
47
|
+
"@stacksjs/validation": "0.65.1",
|
|
48
|
+
"unplugin-vue-router": "^0.10.8",
|
|
49
|
+
"vue-router": "^4.4.5"
|
|
56
50
|
},
|
|
57
51
|
"devDependencies": {
|
|
58
|
-
"@stacksjs/development": "
|
|
52
|
+
"@stacksjs/development": "0.65.1",
|
|
53
|
+
"@stacksjs/types": "0.65.1"
|
|
59
54
|
}
|
|
60
55
|
}
|
package/src/index.ts
CHANGED
package/src/middleware.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { userMiddlewarePath } from '@stacksjs/path'
|
|
2
1
|
import type { MiddlewareOptions } from '@stacksjs/types'
|
|
2
|
+
import { userMiddlewarePath } from '@stacksjs/path'
|
|
3
3
|
|
|
4
4
|
export class Middleware implements MiddlewareOptions {
|
|
5
5
|
name: string
|
|
6
6
|
priority: number
|
|
7
|
-
handle:
|
|
7
|
+
handle: () => Promise<void>
|
|
8
8
|
|
|
9
9
|
constructor(data: MiddlewareOptions) {
|
|
10
10
|
this.name = data.name
|
|
@@ -15,7 +15,7 @@ export class Middleware implements MiddlewareOptions {
|
|
|
15
15
|
|
|
16
16
|
// const readdir = promisify(fs.readdir)
|
|
17
17
|
|
|
18
|
-
async function importMiddlewares(directory: string) {
|
|
18
|
+
async function importMiddlewares(directory: string): Promise<string[]> {
|
|
19
19
|
// const middlewares = []
|
|
20
20
|
// TODO: somehow this breaks ./buddy dev
|
|
21
21
|
// const files = await readdir(directory)
|
|
@@ -30,6 +30,6 @@ async function importMiddlewares(directory: string) {
|
|
|
30
30
|
return [directory] // fix this: return array of middlewares
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export
|
|
33
|
+
export async function middlewares(): Promise<string[]> {
|
|
34
34
|
return await importMiddlewares(userMiddlewarePath())
|
|
35
35
|
}
|
package/src/request.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { RequestInstance, RouteParam } from '@stacksjs/types'
|
|
1
|
+
import type { RequestInstance, RouteParam, VineType } from '@stacksjs/types'
|
|
2
2
|
|
|
3
|
-
import type { VineType } from '@stacksjs/types'
|
|
4
3
|
import { customValidate, validateField } from '@stacksjs/validation'
|
|
5
4
|
|
|
6
5
|
interface RequestData {
|
|
@@ -12,6 +11,8 @@ interface ValidationField {
|
|
|
12
11
|
message: Record<string, string>
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
type AuthToken = `${number}:${number}:${string}`
|
|
15
|
+
|
|
15
16
|
interface CustomAttributes {
|
|
16
17
|
[key: string]: ValidationField
|
|
17
18
|
}
|
|
@@ -39,7 +40,7 @@ export class Request<T extends RequestData = RequestData> implements RequestInst
|
|
|
39
40
|
this.headers = headerParams
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
public get(element: string):
|
|
43
|
+
public get(element: string): any {
|
|
43
44
|
return this.query[element]
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -50,7 +51,8 @@ export class Request<T extends RequestData = RequestData> implements RequestInst
|
|
|
50
51
|
public async validate(attributes?: CustomAttributes): Promise<void> {
|
|
51
52
|
if (attributes === undefined || attributes === null) {
|
|
52
53
|
await validateField('Release', this.all())
|
|
53
|
-
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
54
56
|
await customValidate(attributes, this.all())
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -67,14 +69,15 @@ export class Request<T extends RequestData = RequestData> implements RequestInst
|
|
|
67
69
|
const pattern = new RegExp(`^${routePattern.replace(/:(\w+)/g, (match, paramName) => `(?<${paramName}>\\w+)`)}$`)
|
|
68
70
|
const match = pattern.exec(pathname)
|
|
69
71
|
|
|
70
|
-
if (match?.groups)
|
|
72
|
+
if (match?.groups)
|
|
73
|
+
this.params = match.groups
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
public header(headerParam: string): string | number | boolean | null {
|
|
74
77
|
return this.headers.get(headerParam)
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
public getHeaders() {
|
|
80
|
+
public getHeaders(): any {
|
|
78
81
|
return this.headers
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -86,7 +89,7 @@ export class Request<T extends RequestData = RequestData> implements RequestInst
|
|
|
86
89
|
return this.params ? this.params[key] || null : null
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
public bearerToken(): string | null {
|
|
92
|
+
public bearerToken(): string | null | AuthToken {
|
|
90
93
|
const authorizationHeader = this.headers.get('authorization')
|
|
91
94
|
|
|
92
95
|
if (authorizationHeader?.startsWith('Bearer ')) {
|
|
@@ -106,4 +109,4 @@ export class Request<T extends RequestData = RequestData> implements RequestInst
|
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
export const request = new Request()
|
|
112
|
+
export const request: Request = new Request()
|
package/src/router.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Action } from '@stacksjs/actions'
|
|
2
|
+
import type { Job, RedirectCode, RequestInstance, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
|
|
3
|
+
import { handleError } from '@stacksjs/error-handling'
|
|
2
4
|
import { log } from '@stacksjs/logging'
|
|
3
5
|
import { path as p } from '@stacksjs/path'
|
|
4
6
|
import { kebabCase, pascalCase } from '@stacksjs/strings'
|
|
5
|
-
import type { Job } from '@stacksjs/types'
|
|
6
|
-
import type { RedirectCode, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
|
|
7
7
|
import { customValidate, isObjectNotEmpty } from '@stacksjs/validation'
|
|
8
8
|
import { extractDefaultRequest, findRequestInstance } from './utils'
|
|
9
9
|
|
|
@@ -11,7 +11,6 @@ type ActionPath = string // TODO: narrow this by automating its generation
|
|
|
11
11
|
|
|
12
12
|
export class Router implements RouterInterface {
|
|
13
13
|
private routes: Route[] = []
|
|
14
|
-
private apiPrefix = '/api'
|
|
15
14
|
private groupPrefix = ''
|
|
16
15
|
private path = ''
|
|
17
16
|
|
|
@@ -23,7 +22,7 @@ export class Router implements RouterInterface {
|
|
|
23
22
|
): this {
|
|
24
23
|
const name = uri.replace(/\//g, '.').replace(/:/g, '') // we can improve this
|
|
25
24
|
const pattern = new RegExp(
|
|
26
|
-
`^${uri.replace(/:[a-
|
|
25
|
+
`^${uri.replace(/:[a-z]+/gi, (_match) => {
|
|
27
26
|
return '([a-zA-Z0-9-]+)'
|
|
28
27
|
})}$`,
|
|
29
28
|
)
|
|
@@ -78,7 +77,7 @@ export class Router implements RouterInterface {
|
|
|
78
77
|
public async health(): Promise<this> {
|
|
79
78
|
const healthModule = (await import(p.userActionsPath('HealthAction'))).default as Action
|
|
80
79
|
const callback = healthModule.handle
|
|
81
|
-
const path = healthModule.path ??
|
|
80
|
+
const path = healthModule.path ?? `/health`
|
|
82
81
|
|
|
83
82
|
this.addRoute('GET', path, callback, 200)
|
|
84
83
|
|
|
@@ -95,7 +94,8 @@ export class Router implements RouterInterface {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
public async action(path: ActionPath | Route['path']): Promise<this> {
|
|
98
|
-
if (!path)
|
|
97
|
+
if (!path)
|
|
98
|
+
return this
|
|
99
99
|
|
|
100
100
|
// check if action is a file anywhere in ./app/Actions/**/*.ts
|
|
101
101
|
if (path?.endsWith('.ts')) {
|
|
@@ -111,8 +111,9 @@ export class Router implements RouterInterface {
|
|
|
111
111
|
const action = (await import(p.userActionsPath(path))).default as Action
|
|
112
112
|
|
|
113
113
|
return this.addRoute(action.method ?? 'GET', this.prepareUri(path), action.handle, 200)
|
|
114
|
-
}
|
|
115
|
-
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
handleError(`Could not find Action for path: ${path}`, error)
|
|
116
117
|
|
|
117
118
|
return this
|
|
118
119
|
}
|
|
@@ -161,7 +162,8 @@ export class Router implements RouterInterface {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
public group(options: string | RouteGroupOptions, callback?: () => void): this {
|
|
164
|
-
if (typeof options === 'string')
|
|
165
|
+
if (typeof options === 'string')
|
|
166
|
+
options = options.startsWith('/') ? options.slice(1) : options
|
|
165
167
|
|
|
166
168
|
let cb: () => void
|
|
167
169
|
|
|
@@ -172,7 +174,8 @@ export class Router implements RouterInterface {
|
|
|
172
174
|
options = {}
|
|
173
175
|
}
|
|
174
176
|
|
|
175
|
-
if (!callback)
|
|
177
|
+
if (!callback)
|
|
178
|
+
throw new Error('Missing callback function for your route group.')
|
|
176
179
|
|
|
177
180
|
cb = callback
|
|
178
181
|
|
|
@@ -191,7 +194,8 @@ export class Router implements RouterInterface {
|
|
|
191
194
|
this.routes.forEach((r) => {
|
|
192
195
|
r.uri = `${prefix}${r.uri}`
|
|
193
196
|
|
|
194
|
-
if (middleware.length)
|
|
197
|
+
if (middleware.length)
|
|
198
|
+
r.middleware = middleware
|
|
195
199
|
|
|
196
200
|
originalRoutes.push(r)
|
|
197
201
|
return this
|
|
@@ -204,14 +208,12 @@ export class Router implements RouterInterface {
|
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
public name(name: string): this {
|
|
207
|
-
// @ts-expect-error - this is fine for now
|
|
208
211
|
this.routes[this.routes.length - 1].name = name
|
|
209
212
|
|
|
210
213
|
return this
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
public middleware(middleware: Route['middleware']): this {
|
|
214
|
-
// @ts-expect-error - this is fine for now
|
|
215
217
|
this.routes[this.routes.length - 1].middleware = middleware
|
|
216
218
|
|
|
217
219
|
return this
|
|
@@ -255,7 +257,6 @@ export class Router implements RouterInterface {
|
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
this.setGroupPrefix('', options)
|
|
258
|
-
return
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
public async resolveCallback(callback: Route['callback']): Promise<Route['callback']> {
|
|
@@ -264,7 +265,8 @@ export class Router implements RouterInterface {
|
|
|
264
265
|
return actionModule.default
|
|
265
266
|
}
|
|
266
267
|
|
|
267
|
-
if (typeof callback === 'string')
|
|
268
|
+
if (typeof callback === 'string')
|
|
269
|
+
return await this.importCallbackFromPath(callback, this.path)
|
|
268
270
|
|
|
269
271
|
// in this case, the callback ends up being a function
|
|
270
272
|
return callback
|
|
@@ -274,18 +276,22 @@ export class Router implements RouterInterface {
|
|
|
274
276
|
let modulePath = callbackPath
|
|
275
277
|
let importPathFunction = p.appPath // Default import path function
|
|
276
278
|
|
|
277
|
-
if (callbackPath.startsWith('../'))
|
|
278
|
-
|
|
279
|
+
if (callbackPath.startsWith('../'))
|
|
280
|
+
importPathFunction = p.routesPath
|
|
281
|
+
if (modulePath.includes('OrmAction'))
|
|
282
|
+
importPathFunction = p.storagePath
|
|
279
283
|
|
|
280
284
|
// Remove trailing .ts if present
|
|
281
285
|
modulePath = modulePath.endsWith('.ts') ? modulePath.slice(0, -3) : modulePath
|
|
282
286
|
|
|
283
287
|
let actionModule = null
|
|
284
288
|
|
|
285
|
-
if (modulePath.includes('storage/framework/orm'))
|
|
286
|
-
|
|
289
|
+
if (modulePath.includes('storage/framework/orm'))
|
|
290
|
+
actionModule = await import(modulePath)
|
|
291
|
+
else if (modulePath.includes('app/Actions'))
|
|
292
|
+
actionModule = await import(modulePath)
|
|
287
293
|
else if (modulePath.includes('OrmAction'))
|
|
288
|
-
actionModule = await import(p.storagePath(`/framework/
|
|
294
|
+
actionModule = await import(p.storagePath(`/framework/actions/src/${modulePath}.ts`))
|
|
289
295
|
else actionModule = await import(importPathFunction(modulePath))
|
|
290
296
|
|
|
291
297
|
// Use custom path from action module if available
|
|
@@ -299,21 +305,29 @@ export class Router implements RouterInterface {
|
|
|
299
305
|
// then validate
|
|
300
306
|
// if succeeds, run the handle
|
|
301
307
|
// if fails, return validation error
|
|
302
|
-
let requestInstance
|
|
308
|
+
let requestInstance: RequestInstance
|
|
303
309
|
|
|
304
310
|
if (actionModule.default.requestFile) {
|
|
305
311
|
requestInstance = await findRequestInstance(actionModule.default.requestFile)
|
|
306
|
-
}
|
|
307
|
-
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
requestInstance = await extractDefaultRequest()
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
try {
|
|
311
|
-
if (isObjectNotEmpty(actionModule.default.validations))
|
|
318
|
+
if (isObjectNotEmpty(actionModule.default.validations) && requestInstance)
|
|
312
319
|
await customValidate(actionModule.default.validations, requestInstance.all())
|
|
313
320
|
|
|
314
321
|
return await actionModule.default.handle(requestInstance)
|
|
315
|
-
}
|
|
316
|
-
|
|
322
|
+
}
|
|
323
|
+
catch (error: any) {
|
|
324
|
+
if (error.status === 422)
|
|
325
|
+
return { status: 422, errors: JSON.parse(error.message) }
|
|
326
|
+
|
|
327
|
+
if (!error.status)
|
|
328
|
+
return { status: 500, errors: error.message }
|
|
329
|
+
|
|
330
|
+
return { status: error.status, errors: error.message }
|
|
317
331
|
}
|
|
318
332
|
}
|
|
319
333
|
|
|
@@ -321,11 +335,12 @@ export class Router implements RouterInterface {
|
|
|
321
335
|
return path.endsWith('/') ? path.slice(0, -1) : path
|
|
322
336
|
}
|
|
323
337
|
|
|
324
|
-
public prepareUri(path: string) {
|
|
338
|
+
public prepareUri(path: string): string {
|
|
325
339
|
// if string starts with / then remove it because we are adding it back in the next line
|
|
326
|
-
if (path.startsWith('/'))
|
|
340
|
+
if (path.startsWith('/'))
|
|
341
|
+
path = path.slice(1)
|
|
327
342
|
|
|
328
|
-
path = `${this.
|
|
343
|
+
path = `${this.groupPrefix}/${path}`
|
|
329
344
|
|
|
330
345
|
// if path ends in "/", then remove it
|
|
331
346
|
// e.g. triggered when route is "/"
|
|
@@ -340,4 +355,4 @@ export class Router implements RouterInterface {
|
|
|
340
355
|
}
|
|
341
356
|
}
|
|
342
357
|
|
|
343
|
-
export const route = new Router()
|
|
358
|
+
export const route: Router = new Router()
|