@stacksjs/router 0.59.11 → 0.61.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/router",
3
3
  "type": "module",
4
- "version": "0.59.11",
4
+ "version": "0.61.0",
5
5
  "description": "The Stacks framework router.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -49,14 +49,14 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@stacksjs/config": "latest",
52
- "unplugin-vue-router": "^0.8.4",
53
- "vue-router": "^4.3.0"
52
+ "unplugin-vue-router": "^0.8.7",
53
+ "vue-router": "^4.3.2"
54
54
  },
55
55
  "dependencies": {
56
56
  "@stacksjs/config": "latest",
57
57
  "@stacksjs/logging": "latest",
58
- "unplugin-vue-router": "^0.8.4",
59
- "vue-router": "^4.3.0"
58
+ "unplugin-vue-router": "^0.8.7",
59
+ "vue-router": "^4.3.2"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@stacksjs/development": "latest"
package/src/middleware.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { appPath } from '@stacksjs/path'
1
+ import { userMiddlewarePath } from '@stacksjs/path'
2
2
  import type { MiddlewareOptions } from '@stacksjs/types'
3
3
 
4
4
  export class Middleware implements MiddlewareOptions {
@@ -30,4 +30,4 @@ async function importMiddlewares(directory: string) {
30
30
  return [directory] // fix this: return array of middlewares
31
31
  }
32
32
 
33
- export const middlewares = await importMiddlewares(appPath('middleware'))
33
+ export const middlewares = await importMiddlewares(userMiddlewarePath())
package/src/request.ts CHANGED
@@ -5,9 +5,18 @@ interface RequestData {
5
5
  type RouteParams = { [key: string]: string } | null
6
6
 
7
7
  export class Request {
8
+ private static instance: Request
8
9
  private query: RequestData = {}
9
10
  private params: RouteParams = null
10
11
 
12
+ // An attempt to singleston instance, might be needed at some point
13
+ public static getInstance(): Request {
14
+ if (!Request.instance) {
15
+ Request.instance = new Request()
16
+ }
17
+ return Request.instance
18
+ }
19
+
11
20
  public addQuery(url: URL): void {
12
21
  this.query = Object.fromEntries(url.searchParams)
13
22
  }
@@ -32,12 +41,16 @@ export class Request {
32
41
  const pattern = new RegExp(`^${routePattern.replace(/:(\w+)/g, (match, paramName) => `(?<${paramName}>\\w+)`)}$`)
33
42
  const match = pattern.exec(pathname)
34
43
 
35
- if (match?.groups)
36
- this.params = match?.groups
44
+ if (match?.groups) this.params = match.groups
37
45
  }
38
46
 
39
47
  public getParams(key: string): number | string | null {
40
- return this.params ? (this.params[key] || null) : null
48
+ return this.params ? this.params[key] || null : null
49
+ }
50
+
51
+ public getParamAsInt(key: string): number | null {
52
+ const value = this.params ? this.params[key] || null : null
53
+ return value ? Number.parseInt(value) : null
41
54
  }
42
55
  }
43
56
 
package/src/router.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { RedirectCode, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
2
- import { path as p, routesPath } from '@stacksjs/path'
3
1
  import { log } from '@stacksjs/logging'
2
+ import { path as p, projectStoragePath, routesPath } from '@stacksjs/path'
4
3
  import { pascalCase } from '@stacksjs/strings'
4
+ import type { RedirectCode, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
5
5
 
6
6
  export class Router implements RouterInterface {
7
7
  private routes: Route[] = []
@@ -9,19 +9,25 @@ export class Router implements RouterInterface {
9
9
  private groupPrefix = ''
10
10
  private path = ''
11
11
 
12
- private addRoute(method: Route['method'], uri: string, callback: Route['callback'] | string | object, statusCode: StatusCode): this {
12
+ private addRoute(
13
+ method: Route['method'],
14
+ uri: string,
15
+ callback: Route['callback'] | string | object,
16
+ statusCode: StatusCode,
17
+ ): this {
13
18
  const name = uri.replace(/\//g, '.').replace(/:/g, '') // we can improve this
14
- const pattern = new RegExp(`^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
15
- return '([a-zA-Z0-9-]+)'
16
- })}$`)
19
+ const pattern = new RegExp(
20
+ `^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
21
+ return '([a-zA-Z0-9-]+)'
22
+ })}$`,
23
+ )
17
24
 
18
25
  let routeCallback: Route['callback']
19
26
 
20
27
  if (typeof callback === 'string' || typeof callback === 'object') {
21
28
  // Convert string or object to RouteCallback
22
29
  routeCallback = () => callback
23
- }
24
- else {
30
+ } else {
25
31
  routeCallback = callback
26
32
  }
27
33
 
@@ -92,9 +98,20 @@ export class Router implements RouterInterface {
92
98
  public async action(path: Route['url']): Promise<this> {
93
99
  path = pascalCase(path) // actions are PascalCase
94
100
 
95
- // removes the potential `ActionAction` suffix in case the user does not choose to use the Job suffix in their file name
96
- const actionModule = await import(p.userActionsPath(`${path}Action.ts`.replace(/ActionAction/, 'Action')))
97
- const callback = actionModule.default.handle
101
+ let callback: Route['callback']
102
+ try {
103
+ // removes the potential `ActionAction` suffix in case the user does not choose to use the Job suffix in their file name
104
+ const actionModule = await import(p.userActionsPath(`${path}Action.ts`.replace(/ActionAction/, 'Action')))
105
+ callback = actionModule.default.handle
106
+ } catch (error) {
107
+ try {
108
+ const actionModule = await import(p.userActionsPath(`${path}.ts`.replace(/ActionAction/, 'Action')))
109
+ callback = actionModule.default.handle
110
+ } catch (error) {
111
+ log.error(`Could not find action module for path: ${path}`)
112
+ return this
113
+ }
114
+ }
98
115
 
99
116
  path = this.prepareUri(path)
100
117
  this.addRoute('GET', path, callback, 200)
@@ -137,8 +154,7 @@ export class Router implements RouterInterface {
137
154
  }
138
155
 
139
156
  public group(options: string | RouteGroupOptions, callback?: () => void): this {
140
- if (typeof options === 'string')
141
- options = options.startsWith('/') ? options.slice(1) : options
157
+ if (typeof options === 'string') options = options.startsWith('/') ? options.slice(1) : options
142
158
 
143
159
  let cb: () => void
144
160
 
@@ -149,8 +165,7 @@ export class Router implements RouterInterface {
149
165
  options = {}
150
166
  }
151
167
 
152
- if (!callback)
153
- throw new Error('Missing callback function for your route group.')
168
+ if (!callback) throw new Error('Missing callback function for your route group.')
154
169
 
155
170
  cb = callback
156
171
 
@@ -169,8 +184,7 @@ export class Router implements RouterInterface {
169
184
  this.routes.forEach((r) => {
170
185
  r.uri = `${prefix}${r.uri}`
171
186
 
172
- if (middleware.length)
173
- r.middleware = middleware
187
+ if (middleware.length) r.middleware = middleware
174
188
 
175
189
  originalRoutes.push(r)
176
190
  return this
@@ -205,6 +219,7 @@ export class Router implements RouterInterface {
205
219
 
206
220
  public async getRoutes(): Promise<Route[]> {
207
221
  await import(routesPath('api.ts'))
222
+ await import(projectStoragePath('framework/orm/routes.ts'))
208
223
 
209
224
  return this.routes
210
225
  }
@@ -223,13 +238,18 @@ export class Router implements RouterInterface {
223
238
  }
224
239
 
225
240
  private prepareGroupPrefix(options: string | RouteGroupOptions): void {
226
- if (this.groupPrefix !== '' && typeof options !== 'string')
227
- return this.setGroupPrefix(this.groupPrefix, options)
241
+ if (this.groupPrefix !== '' && typeof options !== 'string') {
242
+ this.setGroupPrefix(this.groupPrefix, options)
243
+ return
244
+ }
228
245
 
229
- if (typeof options === 'string')
230
- return this.setGroupPrefix(options)
246
+ if (typeof options === 'string') {
247
+ this.setGroupPrefix(options)
248
+ return
249
+ }
231
250
 
232
- return this.setGroupPrefix('', options)
251
+ this.setGroupPrefix('', options)
252
+ return
233
253
  }
234
254
 
235
255
  private async resolveCallback(callback: Route['callback']): Promise<Route['callback']> {
@@ -238,29 +258,36 @@ export class Router implements RouterInterface {
238
258
  return actionModule.default
239
259
  }
240
260
 
241
- if (typeof callback === 'string')
242
- return this.importCallbackFromPath(callback, this.path)
261
+ if (typeof callback === 'string') return await this.importCallbackFromPath(callback, this.path)
243
262
 
244
263
  // in this case, the callback ends up being a function
245
- return callback
264
+ return await callback
246
265
  }
247
266
 
248
267
  private async importCallbackFromPath(callbackPath: string, originalPath: string): Promise<Route['callback']> {
249
268
  let modulePath = callbackPath
250
269
  let importPathFunction = p.appPath // Default import path function
251
270
 
252
- if (callbackPath.startsWith('../'))
253
- importPathFunction = p.routesPath
271
+ if (callbackPath.startsWith('../')) importPathFunction = p.routesPath
272
+
273
+ if (modulePath.includes('OrmAction')) importPathFunction = p.projectStoragePath
254
274
 
255
275
  // Remove trailing .ts if present
256
276
  modulePath = modulePath.endsWith('.ts') ? modulePath.slice(0, -3) : modulePath
257
- const actionModule = await import(importPathFunction(`${modulePath}.ts`))
277
+
278
+ let actionModule = null
279
+
280
+ if (modulePath.includes('OrmAction')) {
281
+ actionModule = await import(importPathFunction(`/framework/orm/${modulePath}.ts`))
282
+ } else {
283
+ actionModule = await import(importPathFunction(`${modulePath}.ts`))
284
+ }
258
285
 
259
286
  // Use custom path from action module if available
260
287
  const newPath = actionModule.default.path ?? originalPath
261
288
  this.updatePathIfNeeded(newPath, originalPath)
262
289
 
263
- return actionModule.default.handle
290
+ return await actionModule.default.handle()
264
291
  }
265
292
 
266
293
  private normalizePath(path: string): string {
@@ -269,11 +296,12 @@ export class Router implements RouterInterface {
269
296
 
270
297
  public prepareUri(path: string) {
271
298
  // if string starts with / then remove it because we are adding it back in the next line
272
- if (path.startsWith('/'))
273
- path = path.slice(1)
299
+ if (path.startsWith('/')) path = path.slice(1)
274
300
 
275
301
  path = `${this.apiPrefix}${this.groupPrefix}/${path}`
276
302
 
303
+ console.log(path)
304
+
277
305
  // if path ends in "/", then remove it
278
306
  // e.g. triggered when route is "/"
279
307
  return path.endsWith('/') ? path.slice(0, -1) : path
@@ -281,7 +309,7 @@ export class Router implements RouterInterface {
281
309
 
282
310
  private updatePathIfNeeded(newPath: string, originalPath: string): void {
283
311
  if (newPath !== originalPath) {
284
- // Logic to update the path if needed, based on the action module's custom path
312
+ // Logic to update the path if needed, based on the action module's custom path
285
313
  this.path = newPath
286
314
  }
287
315
  }
package/src/server.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { URL } from 'node:url'
2
1
  import process from 'node:process'
3
2
  import { log } from '@stacksjs/logging'
4
- import type { MiddlewareOptions, Route, StatusCode } from '@stacksjs/types'
5
- import { middlewares } from './middleware'
6
- import { request } from './request'
3
+ import { extname } from '@stacksjs/path'
4
+ import type { Route, StatusCode } from '@stacksjs/types'
7
5
  import { route } from '.'
6
+ import { middlewares } from './middleware'
7
+ import { request as RequestParam } from './request'
8
8
 
9
9
  interface ServeOptions {
10
10
  host?: string
@@ -18,16 +18,15 @@ export async function serve(options: ServeOptions = {}) {
18
18
  const port = options.port || 3000
19
19
  const development = options.debug ? true : process.env.APP_ENV !== 'production' && process.env.APP_ENV !== 'prod'
20
20
 
21
- if (options.timezone)
22
- process.env.TZ = options.timezone
21
+ if (options.timezone) process.env.TZ = options.timezone
23
22
 
24
23
  Bun.serve({
25
24
  hostname,
26
25
  port,
27
26
  development,
28
27
 
29
- fetch(req: Request) {
30
- return serverResponse(req)
28
+ async fetch(req: Request) {
29
+ return await serverResponse(req)
31
30
  },
32
31
  })
33
32
  }
@@ -45,10 +44,12 @@ export async function serverResponse(req: Request) {
45
44
  // '/about' and '/about/' to be treated as the same
46
45
  const trimmedUrl = req.url.endsWith('/') && req.url.length > 1 ? req.url.slice(0, -1) : req.url
47
46
 
47
+ const url = new URL(trimmedUrl)
48
+ addRouteParamsAndQuery(url)
49
+
48
50
  const routesList: Route[] = await route.getRoutes()
49
51
  log.info(`Routes List: ${JSON.stringify(routesList)}`)
50
52
 
51
- const url = new URL(trimmedUrl)
52
53
  log.info(`URL: ${JSON.stringify(url)}`)
53
54
 
54
55
  const foundRoute: Route | undefined = routesList.find((route: Route) => {
@@ -62,42 +63,40 @@ export async function serverResponse(req: Request) {
62
63
  // if (url.pathname === '/favicon.ico')
63
64
  // return new Response('')
64
65
 
65
- if (!foundRoute)
66
- return new Response('Pretty 404 page coming soon', { status: 404 }) // TODO: create a pretty 404 page
66
+ if (!foundRoute) return new Response('Pretty 404 page coming soon', { status: 404 }) // TODO: create a pretty 404 page
67
67
 
68
- addRouteParamsAndQuery(url, foundRoute)
69
- executeMiddleware(foundRoute)
68
+ await executeMiddleware(foundRoute)
70
69
 
71
- return execute(foundRoute, req, { statusCode: foundRoute?.statusCode })
70
+ return await execute(foundRoute, req, { statusCode: foundRoute?.statusCode })
72
71
  }
73
72
 
74
- function addRouteParamsAndQuery(url: URL, route: Route): void {
75
- if (!isObjectNotEmpty(url.searchParams))
76
- request.addQuery(url)
73
+ function addRouteParamsAndQuery(url: URL): void {
74
+ if (!isObjectNotEmpty(url.searchParams)) RequestParam.addQuery(url)
77
75
 
78
- request.extractParamsFromRoute(route.uri, url.pathname)
76
+ // requestInstance.extractParamsFromRoute(route.uri, url.pathname)
79
77
  }
80
78
 
81
79
  function executeMiddleware(route: Route): void {
82
80
  const { middleware = null } = route
83
81
 
84
82
  if (middleware && middlewares && isObjectNotEmpty(middlewares)) {
83
+ // let middlewareItem: MiddlewareOptions
85
84
  if (isString(middleware)) {
86
- const middlewareItem: MiddlewareOptions = middlewares.find((middlewareItem: MiddlewareOptions) => {
87
- return middlewareItem.name === middleware
88
- })
89
-
90
- if (middlewareItem)
91
- middlewareItem.handle() // Invoke only if it exists and is not undefined.
92
- }
93
- else {
94
- middleware.forEach((m) => {
95
- const middlewareItem: MiddlewareOptions = middlewares.find((middlewareItem: MiddlewareOptions) => {
96
- return middlewareItem.name === m
97
- })
98
-
99
- if (middlewareItem)
100
- middlewareItem.handle() // Again, invoke only if it exists.
85
+ // TODO: fix and uncomment this
86
+ // middlewareItem = middlewares.find((m) => {
87
+ // return m.name === middleware
88
+ // })
89
+ // if (middlewareItem)
90
+ // middlewareItem.handle() // Invoke only if it exists and is not undefined.
91
+ } else {
92
+ // middleware.forEach((m) => {
93
+ middleware.forEach(() => {
94
+ // TODO: fix and uncomment this
95
+ // middlewareItem = middlewares.find((middlewareItem: MiddlewareOptions) => {
96
+ // return middlewareItem.name === m
97
+ // })
98
+ // if (middlewareItem)
99
+ // middlewareItem.handle() // Again, invoke only if it exists.
101
100
  })
102
101
  }
103
102
  }
@@ -107,45 +106,42 @@ interface Options {
107
106
  statusCode?: StatusCode
108
107
  }
109
108
 
110
- function execute(route: Route, request: Request, { statusCode }: Options) {
111
- if (!statusCode)
112
- statusCode = 200
109
+ async function execute(route: Route, req: Request, { statusCode }: Options) {
110
+ if (!statusCode) statusCode = 200
113
111
 
114
112
  if (route?.method === 'GET' && (statusCode === 301 || statusCode === 302)) {
115
113
  const callback = String(route.callback)
116
114
  const response = Response.redirect(callback, statusCode)
117
115
 
118
- return noCache(response)
116
+ return await noCache(response)
119
117
  }
120
118
 
121
- if (route?.method !== request.method)
122
- return new Response('Method not allowed', { status: 405 })
119
+ if (route?.method !== req.method) return new Response('Method not allowed', { status: 405 })
123
120
 
124
- // Check if it's a path to an HTM L file
121
+ // Check if it's a path to an HTML file
125
122
  if (isString(route.callback) && extname(route.callback) === '.html') {
126
123
  try {
127
124
  const fileContent = Bun.file(route.callback)
128
125
 
129
- return new Response(fileContent, { headers: { 'Content-Type': 'text/html' } })
130
- }
131
- catch (error) {
132
- return new Response('Error reading the HTML file', { status: 500 })
126
+ return await new Response(fileContent, {
127
+ headers: { 'Content-Type': 'text/html' },
128
+ })
129
+ } catch (error) {
130
+ return await new Response('Error reading the HTML file', { status: 500 })
133
131
  }
134
132
  }
135
133
 
136
- if (isString(route.callback))
137
- return new Response(route.callback)
134
+ if (isString(route.callback)) return await new Response(route.callback)
138
135
 
139
136
  if (isFunction(route.callback)) {
140
- const result = (route.callback)()
141
- return new Response(JSON.stringify(result))
137
+ const result = route.callback()
138
+ return await new Response(JSON.stringify(result))
142
139
  }
143
140
 
144
- if (isObject(route.callback))
145
- return new Response(JSON.stringify(route.callback))
141
+ if (isObject(route.callback)) return await new Response(JSON.stringify(route.callback))
146
142
 
147
143
  // If no known type matched, return a generic error.
148
- return new Response('Unknown callback type.', { status: 500 })
144
+ return await new Response('Unknown callback type.', { status: 500 })
149
145
  }
150
146
 
151
147
  function noCache(response: Response) {
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './middleware';
2
- export * from './request';
3
- export * from './server';
4
- export * from './router';
@@ -1,8 +0,0 @@
1
- import type { MiddlewareOptions } from '@stacksjs/types';
2
- export declare class Middleware implements MiddlewareOptions {
3
- name: string;
4
- priority: number;
5
- handle: Function;
6
- constructor(data: MiddlewareOptions);
7
- }
8
- export declare const middlewares: string[];
package/dist/request.d.ts DELETED
@@ -1,16 +0,0 @@
1
- interface RequestData {
2
- [key: string]: string;
3
- }
4
- export declare class Request {
5
- private query;
6
- private params;
7
- addQuery(url: URL): void;
8
- get(element: string): string | number | undefined;
9
- all(): RequestData;
10
- has(element: string): boolean;
11
- isEmpty(): boolean;
12
- extractParamsFromRoute(routePattern: string, pathname: string): void;
13
- getParams(key: string): number | string | null;
14
- }
15
- export declare const request: Request;
16
- export {};
package/dist/router.d.ts DELETED
@@ -1,32 +0,0 @@
1
- import type { RedirectCode, Route, RouteGroupOptions, RouterInterface } from '@stacksjs/types';
2
- export declare class Router implements RouterInterface {
3
- private routes;
4
- private apiPrefix;
5
- private groupPrefix;
6
- private path;
7
- private addRoute;
8
- get(path: Route['url'], callback: Route['callback']): Promise<this>;
9
- email(path: Route['url']): Promise<this>;
10
- health(): Promise<this>;
11
- job(path: Route['url']): Promise<this>;
12
- action(path: Route['url']): Promise<this>;
13
- post(path: Route['url'], callback: Route['callback']): this;
14
- view(path: Route['url'], callback: Route['callback']): this;
15
- redirect(path: Route['url'], callback: Route['callback'], _status?: RedirectCode): this;
16
- delete(path: Route['url'], callback: Route['callback']): this;
17
- patch(path: Route['url'], callback: Route['callback']): this;
18
- put(path: Route['url'], callback: Route['callback']): this;
19
- group(options: string | RouteGroupOptions, callback?: () => void): this;
20
- name(name: string): this;
21
- middleware(middleware: Route['middleware']): this;
22
- prefix(prefix: string): this;
23
- getRoutes(): Promise<Route[]>;
24
- private setGroupPrefix;
25
- private prepareGroupPrefix;
26
- private resolveCallback;
27
- private importCallbackFromPath;
28
- private normalizePath;
29
- prepareUri(path: string): string;
30
- private updatePathIfNeeded;
31
- }
32
- export declare const route: Router;
package/dist/server.d.ts DELETED
@@ -1,9 +0,0 @@
1
- interface ServeOptions {
2
- host?: string;
3
- port?: number;
4
- debug?: boolean;
5
- timezone?: string;
6
- }
7
- export declare function serve(options?: ServeOptions): Promise<void>;
8
- export declare function serverResponse(req: Request): Promise<Response>;
9
- export {};