@stacksjs/router 0.63.1 → 0.64.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 +45 -45
- package/dist/index.js.map +14 -14
- package/package.json +1 -1
- package/src/middleware.ts +3 -1
- package/src/request.ts +12 -26
- package/src/router.ts +10 -11
- package/src/server.ts +24 -24
- package/src/utils.ts +2 -12
package/package.json
CHANGED
package/src/middleware.ts
CHANGED
|
@@ -30,4 +30,6 @@ async function importMiddlewares(directory: string) {
|
|
|
30
30
|
return [directory] // fix this: return array of middlewares
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export const middlewares =
|
|
33
|
+
export const middlewares = async () => {
|
|
34
|
+
return await importMiddlewares(userMiddlewarePath())
|
|
35
|
+
}
|
package/src/request.ts
CHANGED
|
@@ -4,41 +4,27 @@ import type { VineType } from '@stacksjs/types'
|
|
|
4
4
|
import { customValidate, validateField } from '@stacksjs/validation'
|
|
5
5
|
|
|
6
6
|
interface RequestData {
|
|
7
|
-
[key: string]:
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface ValidationType {
|
|
11
|
-
rule: VineType
|
|
12
|
-
message: { [key: string]: string }
|
|
7
|
+
[key: string]: any
|
|
13
8
|
}
|
|
14
9
|
|
|
15
10
|
interface ValidationField {
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
rule: VineType
|
|
12
|
+
message: Record<string, string>
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
interface CustomAttributes {
|
|
21
16
|
[key: string]: ValidationField
|
|
22
17
|
}
|
|
23
18
|
|
|
24
|
-
type RouteParams = { [key: string]: string } | null
|
|
19
|
+
type RouteParams = { [key: string]: string | number } | null
|
|
25
20
|
|
|
26
|
-
export class Request implements RequestInstance {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
private headers: any = {}
|
|
31
|
-
|
|
32
|
-
// An attempt to singleston instance, might be needed at some point
|
|
33
|
-
public static getInstance(): Request {
|
|
34
|
-
if (!Request.instance) {
|
|
35
|
-
Request.instance = new Request()
|
|
36
|
-
}
|
|
37
|
-
return Request.instance
|
|
38
|
-
}
|
|
21
|
+
export class Request<T extends RequestData = RequestData> implements RequestInstance {
|
|
22
|
+
public query: T = {} as T
|
|
23
|
+
public params: RouteParams = null
|
|
24
|
+
public headers: any = {}
|
|
39
25
|
|
|
40
26
|
public addQuery(url: URL): void {
|
|
41
|
-
this.query = Object.fromEntries(url.searchParams)
|
|
27
|
+
this.query = Object.fromEntries(url.searchParams) as unknown as T
|
|
42
28
|
}
|
|
43
29
|
|
|
44
30
|
public addBodies(params: any): void {
|
|
@@ -57,7 +43,7 @@ export class Request implements RequestInstance {
|
|
|
57
43
|
return this.query[element]
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
public all():
|
|
46
|
+
public all(): T {
|
|
61
47
|
return this.query
|
|
62
48
|
}
|
|
63
49
|
|
|
@@ -115,8 +101,8 @@ export class Request implements RequestInstance {
|
|
|
115
101
|
}
|
|
116
102
|
|
|
117
103
|
public getParamAsInt(key: string): number | null {
|
|
118
|
-
const value = this.
|
|
119
|
-
return value ? Number.parseInt(value) : null
|
|
104
|
+
const value = this.getParam(key)
|
|
105
|
+
return value ? Number.parseInt(value.toString()) : null
|
|
120
106
|
}
|
|
121
107
|
}
|
|
122
108
|
|
package/src/router.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Action } from '@stacksjs/actions'
|
|
2
2
|
import { log } from '@stacksjs/logging'
|
|
3
|
-
import { path as p
|
|
3
|
+
import { path as p } from '@stacksjs/path'
|
|
4
4
|
import { kebabCase, pascalCase } from '@stacksjs/strings'
|
|
5
5
|
import type { Job } from '@stacksjs/types'
|
|
6
6
|
import type { RedirectCode, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
|
|
7
|
-
import {
|
|
7
|
+
import { customValidate, isObjectNotEmpty } from '@stacksjs/validation'
|
|
8
|
+
import { extractDefaultRequest, findRequestInstance } from './utils'
|
|
8
9
|
|
|
9
10
|
type ActionPath = string // TODO: narrow this by automating its generation
|
|
10
11
|
|
|
@@ -66,7 +67,7 @@ export class Router implements RouterInterface {
|
|
|
66
67
|
public async email(path: Route['url']): Promise<this> {
|
|
67
68
|
path = pascalCase(path)
|
|
68
69
|
|
|
69
|
-
const emailModule = (await import(p.userNotificationsPath(
|
|
70
|
+
const emailModule = (await import(p.userNotificationsPath(path))).default as Action
|
|
70
71
|
const callback = emailModule.handle
|
|
71
72
|
const uri = this.prepareUri(path)
|
|
72
73
|
this.addRoute('GET', uri, callback, 200)
|
|
@@ -75,7 +76,7 @@ export class Router implements RouterInterface {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
public async health(): Promise<this> {
|
|
78
|
-
const healthModule = (await import(p.userActionsPath('HealthAction
|
|
79
|
+
const healthModule = (await import(p.userActionsPath('HealthAction'))).default as Action
|
|
79
80
|
const callback = healthModule.handle
|
|
80
81
|
const path = healthModule.path ?? `${this.apiPrefix}/health`
|
|
81
82
|
|
|
@@ -107,7 +108,7 @@ export class Router implements RouterInterface {
|
|
|
107
108
|
path = pascalCase(path) // actions are PascalCase
|
|
108
109
|
|
|
109
110
|
try {
|
|
110
|
-
const action = (await import(p.userActionsPath(
|
|
111
|
+
const action = (await import(p.userActionsPath(path))).default as Action
|
|
111
112
|
|
|
112
113
|
return this.addRoute(action.method ?? 'GET', this.prepareUri(path), action.handle, 200)
|
|
113
114
|
} catch (error) {
|
|
@@ -274,8 +275,7 @@ export class Router implements RouterInterface {
|
|
|
274
275
|
let importPathFunction = p.appPath // Default import path function
|
|
275
276
|
|
|
276
277
|
if (callbackPath.startsWith('../')) importPathFunction = p.routesPath
|
|
277
|
-
|
|
278
|
-
if (modulePath.includes('OrmAction')) importPathFunction = p.projectStoragePath
|
|
278
|
+
if (modulePath.includes('OrmAction')) importPathFunction = p.storagePath
|
|
279
279
|
|
|
280
280
|
// Remove trailing .ts if present
|
|
281
281
|
modulePath = modulePath.endsWith('.ts') ? modulePath.slice(0, -3) : modulePath
|
|
@@ -301,17 +301,16 @@ export class Router implements RouterInterface {
|
|
|
301
301
|
// if fails, return validation error
|
|
302
302
|
let requestInstance
|
|
303
303
|
|
|
304
|
-
console.log('actionModule', actionModule)
|
|
305
|
-
|
|
306
304
|
if (actionModule.default.requestFile) {
|
|
307
305
|
requestInstance = await findRequestInstance(actionModule.default.requestFile)
|
|
308
306
|
} else {
|
|
309
307
|
requestInstance = await extractDefaultRequest(modulePath)
|
|
310
308
|
}
|
|
311
309
|
|
|
312
|
-
console.log(actionModule.default.requestFile)
|
|
313
|
-
|
|
314
310
|
try {
|
|
311
|
+
if (isObjectNotEmpty(actionModule.default.validations))
|
|
312
|
+
await customValidate(actionModule.default.validations, requestInstance.all())
|
|
313
|
+
|
|
315
314
|
return await actionModule.default.handle(requestInstance)
|
|
316
315
|
} catch (error: any) {
|
|
317
316
|
return { status: error.status, errors: error.errors }
|
package/src/server.ts
CHANGED
|
@@ -40,23 +40,22 @@ export async function serve(options: ServeOptions = {}) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export async function serverResponse(req: Request, body: string) {
|
|
43
|
-
log.
|
|
44
|
-
log.
|
|
45
|
-
log.
|
|
46
|
-
// log.
|
|
47
|
-
// log.
|
|
48
|
-
// log.
|
|
43
|
+
log.debug(`Incoming Request: ${req.method} ${req.url}`)
|
|
44
|
+
log.debug(`Headers: ${JSON.stringify(req.headers)}`)
|
|
45
|
+
log.debug(`Body: ${JSON.stringify(req.body)}`)
|
|
46
|
+
// log.debug(`Query: ${JSON.stringify(req.query)}`)
|
|
47
|
+
// log.debug(`Params: ${JSON.stringify(req.params)}`)
|
|
48
|
+
// log.debug(`Cookies: ${JSON.stringify(req.cookies)}`)
|
|
49
49
|
|
|
50
50
|
// Trim trailing slash from the URL if it's not the root '/'
|
|
51
51
|
// This automatically allows for route definitions, like
|
|
52
52
|
// '/about' and '/about/' to be treated as the same
|
|
53
53
|
const trimmedUrl = req.url.endsWith('/') && req.url.length > 1 ? req.url.slice(0, -1) : req.url
|
|
54
|
-
|
|
55
54
|
const url = new URL(trimmedUrl)
|
|
56
|
-
|
|
57
55
|
const routesList: Route[] = await route.getRoutes()
|
|
58
|
-
|
|
59
|
-
log.info(`
|
|
56
|
+
|
|
57
|
+
log.info(`Routes List: ${JSON.stringify(routesList)}`, { styled: false })
|
|
58
|
+
log.info(`URL: ${JSON.stringify(url)}`, { styled: false })
|
|
60
59
|
|
|
61
60
|
if (req.method === 'OPTIONS') {
|
|
62
61
|
return handleOptions(req)
|
|
@@ -70,9 +69,10 @@ export async function serverResponse(req: Request, body: string) {
|
|
|
70
69
|
})
|
|
71
70
|
.find((route: Route) => route.method === req.method)
|
|
72
71
|
|
|
73
|
-
log.info(`Found Route: ${JSON.stringify(foundRoute)}
|
|
72
|
+
log.info(`Found Route: ${JSON.stringify(foundRoute)}`, { styled: false })
|
|
74
73
|
|
|
75
74
|
if (!foundRoute) {
|
|
75
|
+
// TODO: create a pretty 404 page
|
|
76
76
|
return new Response('Pretty 404 page coming soon', {
|
|
77
77
|
status: 404,
|
|
78
78
|
headers: {
|
|
@@ -80,7 +80,7 @@ export async function serverResponse(req: Request, body: string) {
|
|
|
80
80
|
'Access-Control-Allow-Headers': '*',
|
|
81
81
|
'Content-Type': 'json',
|
|
82
82
|
},
|
|
83
|
-
})
|
|
83
|
+
})
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const routeParams = extractDynamicSegments(foundRoute.uri, url.pathname)
|
|
@@ -144,7 +144,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
144
144
|
|
|
145
145
|
const { status, ...payloadWithoutStatus } = middlewarePayload
|
|
146
146
|
|
|
147
|
-
return
|
|
147
|
+
return new Response(JSON.stringify(payloadWithoutStatus), {
|
|
148
148
|
headers: {
|
|
149
149
|
'Content-Type': 'json',
|
|
150
150
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -160,7 +160,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
160
160
|
const callback = String(foundCallback)
|
|
161
161
|
const response = Response.redirect(callback, statusCode)
|
|
162
162
|
|
|
163
|
-
return
|
|
163
|
+
return noCache(response)
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
if (foundRoute?.method !== req.method) {
|
|
@@ -178,7 +178,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
178
178
|
try {
|
|
179
179
|
const fileContent = Bun.file(foundCallback)
|
|
180
180
|
|
|
181
|
-
return
|
|
181
|
+
return new Response(fileContent, {
|
|
182
182
|
headers: {
|
|
183
183
|
'Content-Type': 'text/html',
|
|
184
184
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -186,7 +186,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
186
186
|
},
|
|
187
187
|
})
|
|
188
188
|
} catch (error) {
|
|
189
|
-
return
|
|
189
|
+
return new Response('Error reading the HTML file', {
|
|
190
190
|
status: 500,
|
|
191
191
|
headers: {
|
|
192
192
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -197,7 +197,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
if (isString(foundCallback))
|
|
200
|
-
return
|
|
200
|
+
return new Response(foundCallback, {
|
|
201
201
|
headers: {
|
|
202
202
|
'Content-Type': 'json',
|
|
203
203
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -209,7 +209,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
209
209
|
if (isFunction(foundCallback)) {
|
|
210
210
|
const result = foundCallback()
|
|
211
211
|
|
|
212
|
-
return
|
|
212
|
+
return new Response(JSON.stringify(result), {
|
|
213
213
|
status: 200,
|
|
214
214
|
headers: {
|
|
215
215
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -222,7 +222,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
222
222
|
if (foundCallback.status === 401) {
|
|
223
223
|
const { status, ...rest } = foundCallback
|
|
224
224
|
|
|
225
|
-
return
|
|
225
|
+
return new Response(JSON.stringify(rest), {
|
|
226
226
|
headers: {
|
|
227
227
|
'Content-Type': 'json',
|
|
228
228
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -235,7 +235,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
235
235
|
if (foundCallback.status === 403) {
|
|
236
236
|
const { status, ...rest } = foundCallback
|
|
237
237
|
|
|
238
|
-
return
|
|
238
|
+
return new Response(JSON.stringify(rest), {
|
|
239
239
|
headers: {
|
|
240
240
|
'Content-Type': 'json',
|
|
241
241
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -248,7 +248,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
248
248
|
if (foundCallback.status === 422) {
|
|
249
249
|
const { status, ...rest } = foundCallback
|
|
250
250
|
|
|
251
|
-
return
|
|
251
|
+
return new Response(JSON.stringify(rest), {
|
|
252
252
|
headers: {
|
|
253
253
|
'Content-Type': 'json',
|
|
254
254
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -261,7 +261,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
261
261
|
if (foundCallback.status === 500) {
|
|
262
262
|
const { status, ...rest } = foundCallback
|
|
263
263
|
|
|
264
|
-
return
|
|
264
|
+
return new Response(JSON.stringify(rest), {
|
|
265
265
|
headers: { 'Content-Type': 'json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*' },
|
|
266
266
|
status: 500,
|
|
267
267
|
})
|
|
@@ -269,7 +269,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
if (isObject(foundCallback)) {
|
|
272
|
-
return
|
|
272
|
+
return new Response(JSON.stringify(foundCallback), {
|
|
273
273
|
headers: {
|
|
274
274
|
'Content-Type': 'json',
|
|
275
275
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -280,7 +280,7 @@ async function execute(foundRoute: Route, req: Request, { statusCode }: Options)
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// If no known type matched, return a generic error.
|
|
283
|
-
return
|
|
283
|
+
return new Response('Unknown callback type.', {
|
|
284
284
|
headers: {
|
|
285
285
|
'Content-Type': 'json',
|
|
286
286
|
'Access-Control-Allow-Origin': '*',
|
package/src/utils.ts
CHANGED
|
@@ -17,7 +17,6 @@ export function extractModelFromAction(action: string): string {
|
|
|
17
17
|
|
|
18
18
|
if (action.includes('IndexOrmAction')) {
|
|
19
19
|
const match = action.match(/\/([A-Z][a-z]+)IndexOrmAction/)
|
|
20
|
-
|
|
21
20
|
const modelString = match ? match[1] : ''
|
|
22
21
|
|
|
23
22
|
model = modelString as string
|
|
@@ -25,7 +24,6 @@ export function extractModelFromAction(action: string): string {
|
|
|
25
24
|
|
|
26
25
|
if (action.includes('StoreOrmAction')) {
|
|
27
26
|
const match = action.match(/\/([A-Z][a-z]+)StoreOrmAction/)
|
|
28
|
-
|
|
29
27
|
const modelString = match ? match[1] : ''
|
|
30
28
|
|
|
31
29
|
model = modelString as string
|
|
@@ -57,7 +55,6 @@ export function extractModelFromAction(action: string): string {
|
|
|
57
55
|
|
|
58
56
|
export function extractDynamicAction(action: string): string | undefined {
|
|
59
57
|
const regex = /Actions\/(.*?)Action/
|
|
60
|
-
|
|
61
58
|
const match = action.match(regex)
|
|
62
59
|
|
|
63
60
|
return match ? match[1] : ''
|
|
@@ -65,23 +62,17 @@ export function extractDynamicAction(action: string): string | undefined {
|
|
|
65
62
|
|
|
66
63
|
export async function extractModelRequest(action: string) {
|
|
67
64
|
const extractedModel = extractModelFromAction(action)
|
|
68
|
-
|
|
69
65
|
const lowerCaseModel = camelCase(extractedModel)
|
|
70
|
-
|
|
71
66
|
const requestPath = path.frameworkPath(`requests/${extractedModel}Request.ts`)
|
|
72
|
-
|
|
73
67
|
const requestInstance = await import(requestPath)
|
|
74
|
-
|
|
75
68
|
const requestIndex = `${lowerCaseModel}Request`
|
|
76
69
|
|
|
77
70
|
return requestInstance[requestIndex]
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
export async function findRequestInstance(requestInstance: string) {
|
|
81
|
-
const frameworkDirectory = path.
|
|
82
|
-
|
|
74
|
+
const frameworkDirectory = path.storagePath('framework/requests')
|
|
83
75
|
const filePath = path.join(frameworkDirectory, `${requestInstance}.ts`)
|
|
84
|
-
|
|
85
76
|
const pathExists = await existsSync(filePath)
|
|
86
77
|
|
|
87
78
|
// Check if the directory exists
|
|
@@ -91,8 +82,7 @@ export async function findRequestInstance(requestInstance: string) {
|
|
|
91
82
|
return requestInstance.request
|
|
92
83
|
}
|
|
93
84
|
|
|
94
|
-
const defaultRequestPath = path.
|
|
95
|
-
|
|
85
|
+
const defaultRequestPath = path.storagePath('framework/core/router/src/request.ts')
|
|
96
86
|
const fileExists = await existsSync(defaultRequestPath)
|
|
97
87
|
|
|
98
88
|
if (fileExists) {
|