@stacksjs/router 0.61.17 → 0.61.19

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 CHANGED
@@ -30,6 +30,9 @@ class Request {
30
30
  addQuery(url) {
31
31
  this.query = Object.fromEntries(url.searchParams);
32
32
  }
33
+ addParam(param) {
34
+ this.params = param;
35
+ }
33
36
  get(element) {
34
37
  return this.query[element];
35
38
  }
@@ -48,15 +51,18 @@ class Request {
48
51
  if (match?.groups)
49
52
  this.params = match.groups;
50
53
  }
51
- getParams(key) {
54
+ getParam(key) {
52
55
  return this.params ? this.params[key] || null : null;
53
56
  }
57
+ getParams() {
58
+ return this.params;
59
+ }
54
60
  getParamAsInt(key) {
55
61
  const value = this.params ? this.params[key] || null : null;
56
62
  return value ? Number.parseInt(value) : null;
57
63
  }
58
64
  }
59
- var request = new Request;
65
+ var request2 = new Request;
60
66
  // src/server.ts
61
67
  import process from "process";
62
68
  import {log} from "@stacksjs/logging";
@@ -82,47 +88,50 @@ async function serverResponse(req) {
82
88
  log.info(`Body: ${JSON.stringify(req.body)}`);
83
89
  const trimmedUrl = req.url.endsWith("/") && req.url.length > 1 ? req.url.slice(0, -1) : req.url;
84
90
  const url = new URL(trimmedUrl);
85
- addRouteParamsAndQuery(url);
86
91
  const routesList = await route.getRoutes();
87
92
  log.info(`Routes List: ${JSON.stringify(routesList)}`);
88
93
  log.info(`URL: ${JSON.stringify(url)}`);
89
- const foundRoute = routesList.find((route2) => {
90
- const pattern = new RegExp(`^${route2.uri.replace(/:\w+/g, "\\w+")}\$`);
94
+ const foundRoute = routesList.filter((route2) => {
95
+ const pattern = new RegExp(`^${route2.uri.replace(/\{(\w+)\}/g, "(\\w+)")}\$`);
91
96
  return pattern.test(url.pathname);
92
- });
97
+ }).find((route2) => route2.method === req.method);
93
98
  log.info(`Found Route: ${JSON.stringify(foundRoute)}`);
94
99
  if (!foundRoute)
95
100
  return new Response("Pretty 404 page coming soon", { status: 404 });
101
+ const routeParams = extractDynamicSegments(foundRoute.uri, url.pathname);
102
+ addRouteQuery(url);
103
+ addRouteParam(routeParams);
96
104
  await executeMiddleware(foundRoute);
97
105
  return await execute(foundRoute, req, { statusCode: foundRoute?.statusCode });
98
106
  }
99
- var addRouteParamsAndQuery = function(url) {
100
- if (!isObjectNotEmpty(url.searchParams))
101
- request.addQuery(url);
102
- };
103
- var executeMiddleware = function(route2) {
104
- const { middleware: middleware2 = null } = route2;
105
- if (middleware2 && middlewares && isObjectNotEmpty(middlewares)) {
106
- if (isString(middleware2)) {
107
- } else {
108
- middleware2.forEach(() => {
109
- });
110
- }
111
- }
107
+ var extractDynamicSegments = function(routePattern, path3) {
108
+ const regexPattern = new RegExp(`^${routePattern.replace(/\{(\w+)\}/g, "(\\w+)")}\$`);
109
+ const match = path3.match(regexPattern);
110
+ if (!match) {
111
+ return null;
112
+ }
113
+ const dynamicSegmentNames = [...routePattern.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
114
+ const dynamicSegmentValues = match.slice(1);
115
+ const dynamicSegments = {};
116
+ dynamicSegmentNames.forEach((name, index) => {
117
+ dynamicSegments[name] = dynamicSegmentValues[index];
118
+ });
119
+ return dynamicSegments;
112
120
  };
113
- async function execute(route2, req, { statusCode }) {
121
+ async function execute(foundRoute, req, { statusCode }) {
122
+ const foundCallback = await route.resolveCallback(foundRoute.callback);
114
123
  if (!statusCode)
115
124
  statusCode = 200;
116
- if (route2?.method === "GET" && (statusCode === 301 || statusCode === 302)) {
117
- const callback = String(route2.callback);
125
+ if (foundRoute?.method === "GET" && (statusCode === 301 || statusCode === 302)) {
126
+ const callback = String(foundCallback);
118
127
  const response = Response.redirect(callback, statusCode);
119
128
  return await noCache(response);
120
129
  }
121
- if (route2?.method !== req.method)
130
+ if (foundRoute?.method !== req.method)
122
131
  return new Response("Method not allowed", { status: 405 });
123
- if (isString(route2.callback) && extname(route2.callback) === ".html") {
132
+ if (isString(foundCallback) && extname(foundCallback) === ".html") {
124
133
  try {
125
- const fileContent = Bun.file(route2.callback);
134
+ const fileContent = Bun.file(foundCallback);
126
135
  return await new Response(fileContent, {
127
136
  headers: { "Content-Type": "text/html" }
128
137
  });
@@ -130,14 +139,14 @@ async function execute(route2, req, { statusCode }) {
130
139
  return await new Response("Error reading the HTML file", { status: 500 });
131
140
  }
132
141
  }
133
- if (isString(route2.callback))
134
- return await new Response(route2.callback);
135
- if (isFunction(route2.callback)) {
136
- const result = route2.callback();
142
+ if (isString(foundCallback))
143
+ return await new Response(foundCallback);
144
+ if (isFunction(foundCallback)) {
145
+ const result = foundCallback();
137
146
  return await new Response(JSON.stringify(result));
138
147
  }
139
- if (isObject(route2.callback))
140
- return await new Response(JSON.stringify(route2.callback));
148
+ if (isObject(foundCallback))
149
+ return await new Response(JSON.stringify(foundCallback));
141
150
  return await new Response("Unknown callback type.", { status: 500 });
142
151
  }
143
152
  var noCache = function(response) {
@@ -146,6 +155,23 @@ var noCache = function(response) {
146
155
  response.headers.set("Expires", "0");
147
156
  return response;
148
157
  };
158
+ var addRouteQuery = function(url) {
159
+ if (!isObjectNotEmpty(url.searchParams))
160
+ request2.addQuery(url);
161
+ };
162
+ var addRouteParam = function(param) {
163
+ request2.addParam(param);
164
+ };
165
+ var executeMiddleware = function(route2) {
166
+ const { middleware: middleware2 = null } = route2;
167
+ if (middleware2 && middlewares && isObjectNotEmpty(middlewares)) {
168
+ if (isString(middleware2)) {
169
+ } else {
170
+ middleware2.forEach(() => {
171
+ });
172
+ }
173
+ }
174
+ };
149
175
  var isString = function(val) {
150
176
  return typeof val === "string";
151
177
  };
@@ -156,12 +182,12 @@ var isFunction = function(val) {
156
182
  return typeof val === "function";
157
183
  };
158
184
  var isObject = function(val) {
159
- return val !== null && typeof val === "object" && !Array.isArray(val);
185
+ return typeof val === "object";
160
186
  };
161
187
  // src/router.ts
162
188
  import {log as log2} from "@stacksjs/logging";
163
189
  import {path as p, projectStoragePath, routesPath} from "@stacksjs/path";
164
- import {pascalCase} from "@stacksjs/strings";
190
+ import {kebabCase, pascalCase} from "@stacksjs/strings";
165
191
 
166
192
  class Router {
167
193
  routes = [];
@@ -173,99 +199,94 @@ class Router {
173
199
  const pattern = new RegExp(`^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
174
200
  return "([a-zA-Z0-9-]+)";
175
201
  })}\$`);
176
- let routeCallback;
177
- if (typeof callback === "string" || typeof callback === "object") {
178
- routeCallback = () => callback;
179
- } else {
180
- routeCallback = callback;
181
- }
182
202
  log2.debug(`Adding route: ${method} ${uri} with name ${name}`);
183
203
  this.routes.push({
184
204
  name,
185
205
  method,
186
206
  url: uri,
187
207
  uri,
188
- callback: routeCallback,
208
+ callback,
189
209
  pattern,
190
210
  statusCode,
191
211
  paramNames: []
192
212
  });
193
213
  return this;
194
214
  }
195
- async get(path4, callback) {
215
+ get(path4, callback) {
196
216
  this.path = this.normalizePath(path4);
197
217
  log2.debug(`Normalized Path: ${this.path}`);
198
- callback = await this.resolveCallback(callback);
199
218
  const uri = this.prepareUri(this.path);
200
219
  log2.debug(`Prepared URI: ${uri}`);
201
220
  return this.addRoute("GET", uri, callback, 200);
202
221
  }
203
222
  async email(path4) {
204
223
  path4 = pascalCase(path4);
205
- const emailModule = await import(p.userNotificationsPath(`${path4}.ts`));
206
- const callback = emailModule.default.handle;
224
+ const emailModule = (await import(p.userNotificationsPath(`${path4}.ts`))).default;
225
+ const callback = emailModule.handle;
207
226
  const uri = this.prepareUri(path4);
208
227
  this.addRoute("GET", uri, callback, 200);
209
228
  return this;
210
229
  }
211
230
  async health() {
212
- const healthModule = await import(p.userActionsPath("HealthAction.ts"));
213
- const callback = healthModule.default.handle;
214
- const path4 = healthModule.default.path ?? `${this.apiPrefix}/health`;
231
+ const healthModule = (await import(p.userActionsPath("HealthAction.ts"))).default;
232
+ const callback = healthModule.handle;
233
+ const path4 = healthModule.path ?? `${this.apiPrefix}/health`;
215
234
  this.addRoute("GET", path4, callback, 200);
216
235
  return this;
217
236
  }
218
237
  async job(path4) {
219
238
  path4 = pascalCase(path4);
220
- const jobModule = await import(p.userJobsPath(`${path4}Job.ts`.replace(/JobJob/, "Job")));
221
- const callback = jobModule.default.handle;
222
- path4 = this.prepareUri(path4);
223
- this.addRoute("GET", path4, callback, 200);
224
- return this;
239
+ const job = (await import(p.userJobsPath(`${path4}.ts`))).default;
240
+ return this.addRoute("GET", this.prepareUri(path4), job.handle, 200);
225
241
  }
226
242
  async action(path4) {
243
+ if (path4?.endsWith(".ts")) {
244
+ const action = (await import(p.userActionsPath(path4))).default;
245
+ path4 = action.path ?? kebabCase(path4);
246
+ return this.addRoute(action.method ?? "GET", path4, action.handle, 200);
247
+ }
227
248
  path4 = pascalCase(path4);
228
- let callback;
249
+ const userActionsPath = p.userActionsPath(`${path4}.ts`);
229
250
  try {
230
- const actionModule = await import(p.userActionsPath(`${path4}Action.ts`.replace(/ActionAction/, "Action")));
231
- callback = actionModule.default.handle;
251
+ const action = (await import(userActionsPath)).default;
252
+ return this.addRoute(action.method ?? "GET", this.prepareUri(path4), action.handle, 200);
232
253
  } catch (error) {
233
254
  try {
234
- const actionModule = await import(p.userActionsPath(`${path4}.ts`.replace(/ActionAction/, "Action")));
235
- callback = actionModule.default.handle;
255
+ const action = (await import(p.userActionsPath(`${path4}.ts`))).default;
256
+ return this.addRoute(action.method ?? "GET", this.prepareUri(path4), action.handle, 200);
236
257
  } catch (error2) {
237
- log2.error(`Could not find action module for path: ${path4}`);
258
+ log2.error(`Could not find Action for path: ${path4}`);
238
259
  return this;
239
260
  }
240
261
  }
241
- path4 = this.prepareUri(path4);
242
- this.addRoute("GET", path4, callback, 200);
243
- return this;
244
262
  }
245
263
  post(path4, callback) {
246
- path4 = this.prepareUri(path4);
247
- this.addRoute("POST", path4, callback, 201);
248
- return this;
264
+ this.path = this.normalizePath(path4);
265
+ const uri = this.prepareUri(this.path);
266
+ return this.addRoute("POST", uri, callback, 201);
249
267
  }
250
268
  view(path4, callback) {
251
- this.addRoute("GET", path4, callback, 200);
252
- return this;
269
+ this.path = this.normalizePath(path4);
270
+ const uri = this.prepareUri(this.path);
271
+ return this.addRoute("GET", uri, callback, 200);
253
272
  }
254
273
  redirect(path4, callback, _status) {
255
- this.addRoute("GET", path4, callback, 302);
256
- return this;
274
+ return this.addRoute("GET", path4, callback, 302);
257
275
  }
258
276
  delete(path4, callback) {
259
- this.addRoute("DELETE", path4, callback, 204);
260
- return this;
277
+ return this.addRoute("DELETE", this.prepareUri(path4), callback, 204);
261
278
  }
262
279
  patch(path4, callback) {
263
- this.addRoute("PATCH", path4, callback, 202);
264
- return this;
280
+ this.path = this.normalizePath(path4);
281
+ log2.debug(`Normalized Path: ${this.path}`);
282
+ const uri = this.prepareUri(this.path);
283
+ log2.debug(`Prepared URI: ${uri}`);
284
+ return this.addRoute("PATCH", uri, callback, 202);
265
285
  }
266
286
  put(path4, callback) {
267
- this.addRoute("PUT", path4, callback, 202);
268
- return this;
287
+ this.path = this.normalizePath(path4);
288
+ const uri = this.prepareUri(this.path);
289
+ return this.addRoute("PUT", uri, callback, 202);
269
290
  }
270
291
  group(options, callback) {
271
292
  if (typeof options === "string")
@@ -338,7 +359,7 @@ class Router {
338
359
  }
339
360
  if (typeof callback === "string")
340
361
  return await this.importCallbackFromPath(callback, this.path);
341
- return await callback;
362
+ return callback;
342
363
  }
343
364
  async importCallbackFromPath(callbackPath, originalPath) {
344
365
  let modulePath = callbackPath;
@@ -356,7 +377,9 @@ class Router {
356
377
  }
357
378
  const newPath = actionModule.default.path ?? originalPath;
358
379
  this.updatePathIfNeeded(newPath, originalPath);
359
- return await actionModule.default.handle();
380
+ if (condition)
381
+ return await actionModule.default.handle();
382
+ return await actionModule.default.handle(request);
360
383
  }
361
384
  normalizePath(path4) {
362
385
  return path4.endsWith("/") ? path4.slice(0, -1) : path4;
@@ -365,7 +388,6 @@ class Router {
365
388
  if (path4.startsWith("/"))
366
389
  path4 = path4.slice(1);
367
390
  path4 = `${this.apiPrefix}${this.groupPrefix}/${path4}`;
368
- console.log(path4);
369
391
  return path4.endsWith("/") ? path4.slice(0, -1) : path4;
370
392
  }
371
393
  updatePathIfNeeded(newPath, originalPath) {
@@ -375,12 +397,19 @@ class Router {
375
397
  }
376
398
  }
377
399
  var route = new Router;
400
+ // src/utils.ts
401
+ async function listRoutes() {
402
+ const routeLists = await route.getRoutes();
403
+ console.table(routeLists);
404
+ return ok("Successfully listed routes!");
405
+ }
378
406
  export {
379
407
  serverResponse,
380
408
  serve,
381
409
  route,
382
- request,
410
+ request2 as request,
383
411
  middlewares,
412
+ listRoutes,
384
413
  Router,
385
414
  Request,
386
415
  Middleware
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/router",
3
3
  "type": "module",
4
- "version": "0.61.17",
4
+ "version": "0.61.19",
5
5
  "description": "The Stacks framework router.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -49,13 +49,13 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@stacksjs/config": "latest",
52
- "unplugin-vue-router": "^0.8.7",
52
+ "unplugin-vue-router": "^0.8.8",
53
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.7",
58
+ "unplugin-vue-router": "^0.8.8",
59
59
  "vue-router": "^4.3.2"
60
60
  },
61
61
  "devDependencies": {
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './middleware'
2
2
  export * from './request'
3
3
  export * from './server'
4
4
  export * from './router'
5
+ export * from './utils'
package/src/request.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { RouteParam } from '@stacksjs/types'
2
+
1
3
  interface RequestData {
2
4
  [key: string]: string
3
5
  }
@@ -21,6 +23,10 @@ export class Request {
21
23
  this.query = Object.fromEntries(url.searchParams)
22
24
  }
23
25
 
26
+ public addParam(param: RouteParam): void {
27
+ this.params = param
28
+ }
29
+
24
30
  public get(element: string): string | number | undefined {
25
31
  return this.query[element]
26
32
  }
@@ -44,10 +50,14 @@ export class Request {
44
50
  if (match?.groups) this.params = match.groups
45
51
  }
46
52
 
47
- public getParams(key: string): number | string | null {
53
+ public getParam(key: string): number | string | null {
48
54
  return this.params ? this.params[key] || null : null
49
55
  }
50
56
 
57
+ public getParams(): RouteParams {
58
+ return this.params
59
+ }
60
+
51
61
  public getParamAsInt(key: string): number | null {
52
62
  const value = this.params ? this.params[key] || null : null
53
63
  return value ? Number.parseInt(value) : null
package/src/router.ts CHANGED
@@ -1,8 +1,12 @@
1
+ import type { Action } from '@stacksjs/actions'
1
2
  import { log } from '@stacksjs/logging'
2
3
  import { path as p, projectStoragePath, routesPath } from '@stacksjs/path'
3
- import { pascalCase } from '@stacksjs/strings'
4
+ import { kebabCase, pascalCase } from '@stacksjs/strings'
5
+ import type { Job } from '@stacksjs/types'
4
6
  import type { RedirectCode, Route, RouteGroupOptions, RouterInterface, StatusCode } from '@stacksjs/types'
5
7
 
8
+ type ActionPath = string // TODO: narrow this by automating its generation
9
+
6
10
  export class Router implements RouterInterface {
7
11
  private routes: Route[] = []
8
12
  private apiPrefix = '/api'
@@ -22,14 +26,14 @@ export class Router implements RouterInterface {
22
26
  })}$`,
23
27
  )
24
28
 
25
- let routeCallback: Route['callback']
29
+ // let routeCallback: Route['callback']
26
30
 
27
- if (typeof callback === 'string' || typeof callback === 'object') {
28
- // Convert string or object to RouteCallback
29
- routeCallback = () => callback
30
- } else {
31
- routeCallback = callback
32
- }
31
+ // if (typeof callback === 'string' || typeof callback === 'object') {
32
+ // // Convert string or object to RouteCallback
33
+ // routeCallback = () => callback
34
+ // } else {
35
+ // routeCallback = callback
36
+ // }
33
37
 
34
38
  log.debug(`Adding route: ${method} ${uri} with name ${name}`)
35
39
 
@@ -38,21 +42,20 @@ export class Router implements RouterInterface {
38
42
  method,
39
43
  url: uri,
40
44
  uri,
41
- callback: routeCallback,
45
+ callback,
42
46
  pattern,
43
47
  statusCode,
44
48
  paramNames: [],
49
+ // middleware: [],
45
50
  })
46
51
 
47
52
  return this
48
53
  }
49
54
 
50
- public async get(path: Route['url'], callback: Route['callback']): Promise<this> {
55
+ public get(path: Route['url'], callback: Route['callback']): this {
51
56
  this.path = this.normalizePath(path)
52
57
  log.debug(`Normalized Path: ${this.path}`)
53
58
 
54
- callback = await this.resolveCallback(callback)
55
-
56
59
  const uri = this.prepareUri(this.path)
57
60
  log.debug(`Prepared URI: ${uri}`)
58
61
 
@@ -62,9 +65,8 @@ export class Router implements RouterInterface {
62
65
  public async email(path: Route['url']): Promise<this> {
63
66
  path = pascalCase(path)
64
67
 
65
- const emailModule = await import(p.userNotificationsPath(`${path}.ts`))
66
- const callback = emailModule.default.handle
67
-
68
+ const emailModule = (await import(p.userNotificationsPath(`${path}.ts`))).default as Action
69
+ const callback = emailModule.handle
68
70
  const uri = this.prepareUri(path)
69
71
  this.addRoute('GET', uri, callback, 200)
70
72
 
@@ -72,10 +74,9 @@ export class Router implements RouterInterface {
72
74
  }
73
75
 
74
76
  public async health(): Promise<this> {
75
- const healthModule = await import(p.userActionsPath('HealthAction.ts'))
76
- const callback = healthModule.default.handle
77
-
78
- const path = healthModule.default.path ?? `${this.apiPrefix}/health`
77
+ const healthModule = (await import(p.userActionsPath('HealthAction.ts'))).default as Action
78
+ const callback = healthModule.handle
79
+ const path = healthModule.path ?? `${this.apiPrefix}/health`
79
80
 
80
81
  this.addRoute('GET', path, callback, 200)
81
82
 
@@ -86,71 +87,80 @@ export class Router implements RouterInterface {
86
87
  path = pascalCase(path)
87
88
 
88
89
  // removes the potential `JobJob` suffix in case the user does not choose to use the Job suffix in their file name
89
- const jobModule = await import(p.userJobsPath(`${path}Job.ts`.replace(/JobJob/, 'Job')))
90
- const callback = jobModule.default.handle
90
+ const job = (await import(p.userJobsPath(`${path}.ts`))).default as Job
91
91
 
92
- path = this.prepareUri(path)
93
- this.addRoute('GET', path, callback, 200)
94
-
95
- return this
92
+ return this.addRoute('GET', this.prepareUri(path), job.handle, 200)
96
93
  }
97
94
 
98
- public async action(path: Route['url']): Promise<this> {
95
+ public async action(path: ActionPath | Route['path']): Promise<this> {
96
+ // check if action is a file anywhere in ./app/Actions/**/*.ts
97
+ if (path?.endsWith('.ts')) {
98
+ // given it ends with .ts, we treat it as an Actions path
99
+ const action = (await import(p.userActionsPath(path))).default as Action
100
+ path = action.path ?? kebabCase(path as string)
101
+ return this.addRoute(action.method ?? 'GET', path, action.handle, 200)
102
+ }
103
+
99
104
  path = pascalCase(path) // actions are PascalCase
105
+ const userActionsPath = p.userActionsPath(`${path}.ts`)
100
106
 
101
- let callback: Route['callback']
102
107
  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
108
+ const action = (await import(userActionsPath)).default as Action
109
+
110
+ return this.addRoute(action.method ?? 'GET', this.prepareUri(path), action.handle, 200)
106
111
  } catch (error) {
107
112
  try {
108
- const actionModule = await import(p.userActionsPath(`${path}.ts`.replace(/ActionAction/, 'Action')))
109
- callback = actionModule.default.handle
113
+ const action = (await import(p.userActionsPath(`${path}.ts`))).default as Action
114
+
115
+ return this.addRoute(action.method ?? 'GET', this.prepareUri(path), action.handle, 200)
110
116
  } catch (error) {
111
- log.error(`Could not find action module for path: ${path}`)
117
+ log.error(`Could not find Action for path: ${path}`)
118
+
112
119
  return this
113
120
  }
114
121
  }
115
-
116
- path = this.prepareUri(path)
117
- this.addRoute('GET', path, callback, 200)
118
-
119
- return this
120
122
  }
121
123
 
122
124
  public post(path: Route['url'], callback: Route['callback']): this {
123
- path = this.prepareUri(path)
124
- this.addRoute('POST', path, callback, 201)
125
+ this.path = this.normalizePath(path)
125
126
 
126
- return this
127
+ const uri = this.prepareUri(this.path)
128
+
129
+ return this.addRoute('POST', uri, callback, 201)
127
130
  }
128
131
 
129
132
  public view(path: Route['url'], callback: Route['callback']): this {
130
- this.addRoute('GET', path, callback, 200)
133
+ this.path = this.normalizePath(path)
131
134
 
132
- return this
135
+ const uri = this.prepareUri(this.path)
136
+
137
+ return this.addRoute('GET', uri, callback, 200)
133
138
  }
134
139
 
135
140
  public redirect(path: Route['url'], callback: Route['callback'], _status?: RedirectCode): this {
136
- this.addRoute('GET', path, callback, 302)
137
-
138
- return this
141
+ return this.addRoute('GET', path, callback, 302)
139
142
  }
140
143
 
141
144
  public delete(path: Route['url'], callback: Route['callback']): this {
142
- this.addRoute('DELETE', path, callback, 204)
143
- return this
145
+ return this.addRoute('DELETE', this.prepareUri(path), callback, 204)
144
146
  }
145
147
 
146
148
  public patch(path: Route['url'], callback: Route['callback']): this {
147
- this.addRoute('PATCH', path, callback, 202)
148
- return this
149
+ this.path = this.normalizePath(path)
150
+ log.debug(`Normalized Path: ${this.path}`)
151
+
152
+ const uri = this.prepareUri(this.path)
153
+ log.debug(`Prepared URI: ${uri}`)
154
+
155
+ return this.addRoute('PATCH', uri, callback, 202)
149
156
  }
150
157
 
151
158
  public put(path: Route['url'], callback: Route['callback']): this {
152
- this.addRoute('PUT', path, callback, 202)
153
- return this
159
+ this.path = this.normalizePath(path)
160
+
161
+ const uri = this.prepareUri(this.path)
162
+
163
+ return this.addRoute('PUT', uri, callback, 202)
154
164
  }
155
165
 
156
166
  public group(options: string | RouteGroupOptions, callback?: () => void): this {
@@ -211,7 +221,6 @@ export class Router implements RouterInterface {
211
221
  }
212
222
 
213
223
  public prefix(prefix: string): this {
214
- // @ts-expect-error - this is fine for now
215
224
  this.routes[this.routes.length - 1].prefix = prefix
216
225
 
217
226
  return this
@@ -252,7 +261,7 @@ export class Router implements RouterInterface {
252
261
  return
253
262
  }
254
263
 
255
- private async resolveCallback(callback: Route['callback']): Promise<Route['callback']> {
264
+ public async resolveCallback(callback: Route['callback']): Promise<Route['callback']> {
256
265
  if (callback instanceof Promise) {
257
266
  const actionModule = await callback
258
267
  return actionModule.default
@@ -261,10 +270,10 @@ export class Router implements RouterInterface {
261
270
  if (typeof callback === 'string') return await this.importCallbackFromPath(callback, this.path)
262
271
 
263
272
  // in this case, the callback ends up being a function
264
- return await callback
273
+ return callback
265
274
  }
266
275
 
267
- private async importCallbackFromPath(callbackPath: string, originalPath: string): Promise<Route['callback']> {
276
+ public async importCallbackFromPath(callbackPath: string, originalPath: string): Promise<Route['callback']> {
268
277
  let modulePath = callbackPath
269
278
  let importPathFunction = p.appPath // Default import path function
270
279
 
@@ -287,7 +296,17 @@ export class Router implements RouterInterface {
287
296
  const newPath = actionModule.default.path ?? originalPath
288
297
  this.updatePathIfNeeded(newPath, originalPath)
289
298
 
290
- return await actionModule.default.handle()
299
+ // we need to make sure the validation happens here
300
+ // to do so, we need to:
301
+ // find the ./app/Models/* file
302
+ // then check via a regex which model attributes validations to utilize by checking what's in between t
303
+ // then validate
304
+ // if succeeds, run the handle
305
+ // if fails, return validation error
306
+
307
+ if (condition) return await actionModule.default.handle()
308
+
309
+ return await actionModule.default.handle(request)
291
310
  }
292
311
 
293
312
  private normalizePath(path: string): string {
@@ -300,8 +319,6 @@ export class Router implements RouterInterface {
300
319
 
301
320
  path = `${this.apiPrefix}${this.groupPrefix}/${path}`
302
321
 
303
- console.log(path)
304
-
305
322
  // if path ends in "/", then remove it
306
323
  // e.g. triggered when route is "/"
307
324
  return path.endsWith('/') ? path.slice(0, -1) : path
package/src/server.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import process from 'node:process'
2
2
  import { log } from '@stacksjs/logging'
3
3
  import { extname } from '@stacksjs/path'
4
- import type { Route, StatusCode } from '@stacksjs/types'
4
+ import type { Route, RouteParam, StatusCode } from '@stacksjs/types'
5
5
  import { route } from '.'
6
6
  import { middlewares } from './middleware'
7
7
  import { request as RequestParam } from './request'
@@ -13,6 +13,10 @@ interface ServeOptions {
13
13
  timezone?: string
14
14
  }
15
15
 
16
+ interface Options {
17
+ statusCode?: StatusCode
18
+ }
19
+
16
20
  export async function serve(options: ServeOptions = {}) {
17
21
  const hostname = options.host || 'localhost'
18
22
  const port = options.port || 3000
@@ -45,83 +49,72 @@ export async function serverResponse(req: Request) {
45
49
  const trimmedUrl = req.url.endsWith('/') && req.url.length > 1 ? req.url.slice(0, -1) : req.url
46
50
 
47
51
  const url = new URL(trimmedUrl)
48
- addRouteParamsAndQuery(url)
49
52
 
50
53
  const routesList: Route[] = await route.getRoutes()
51
54
  log.info(`Routes List: ${JSON.stringify(routesList)}`)
52
55
 
53
56
  log.info(`URL: ${JSON.stringify(url)}`)
54
57
 
55
- const foundRoute: Route | undefined = routesList.find((route: Route) => {
56
- const pattern = new RegExp(`^${route.uri.replace(/:\w+/g, '\\w+')}$`)
58
+ const foundRoute: Route | undefined = routesList
59
+ .filter((route: Route) => {
60
+ const pattern = new RegExp(`^${route.uri.replace(/\{(\w+)\}/g, '(\\w+)')}$`)
57
61
 
58
- return pattern.test(url.pathname)
59
- })
62
+ return pattern.test(url.pathname)
63
+ })
64
+ .find((route: Route) => route.method === req.method)
60
65
 
61
66
  log.info(`Found Route: ${JSON.stringify(foundRoute)}`)
62
-
63
67
  // if (url.pathname === '/favicon.ico')
64
68
  // return new Response('')
65
69
 
66
70
  if (!foundRoute) return new Response('Pretty 404 page coming soon', { status: 404 }) // TODO: create a pretty 404 page
67
71
 
72
+ const routeParams = extractDynamicSegments(foundRoute.uri, url.pathname)
73
+
74
+ addRouteQuery(url)
75
+ addRouteParam(routeParams)
76
+
68
77
  await executeMiddleware(foundRoute)
69
78
 
70
79
  return await execute(foundRoute, req, { statusCode: foundRoute?.statusCode })
71
80
  }
72
81
 
73
- function addRouteParamsAndQuery(url: URL): void {
74
- if (!isObjectNotEmpty(url.searchParams)) RequestParam.addQuery(url)
82
+ function extractDynamicSegments(routePattern: string, path: string): RouteParam {
83
+ const regexPattern = new RegExp(`^${routePattern.replace(/\{(\w+)\}/g, '(\\w+)')}$`)
84
+ const match = path.match(regexPattern)
75
85
 
76
- // requestInstance.extractParamsFromRoute(route.uri, url.pathname)
77
- }
86
+ if (!match) {
87
+ return null
88
+ }
89
+ const dynamicSegmentNames = [...routePattern.matchAll(/\{(\w+)\}/g)].map((m) => m[1])
90
+ const dynamicSegmentValues = match.slice(1) // First match is the whole string, so we slice it off
78
91
 
79
- function executeMiddleware(route: Route): void {
80
- const { middleware = null } = route
92
+ const dynamicSegments: { [key: string]: string } = {}
93
+ dynamicSegmentNames.forEach((name, index) => {
94
+ dynamicSegments[name] = dynamicSegmentValues[index]
95
+ })
81
96
 
82
- if (middleware && middlewares && isObjectNotEmpty(middlewares)) {
83
- // let middlewareItem: MiddlewareOptions
84
- if (isString(middleware)) {
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.
100
- })
101
- }
102
- }
97
+ return dynamicSegments
103
98
  }
104
99
 
105
- interface Options {
106
- statusCode?: StatusCode
107
- }
100
+ async function execute(foundRoute: Route, req: Request, { statusCode }: Options) {
101
+ const foundCallback = await route.resolveCallback(foundRoute.callback)
108
102
 
109
- async function execute(route: Route, req: Request, { statusCode }: Options) {
110
103
  if (!statusCode) statusCode = 200
111
104
 
112
- if (route?.method === 'GET' && (statusCode === 301 || statusCode === 302)) {
113
- const callback = String(route.callback)
105
+ if (foundRoute?.method === 'GET' && (statusCode === 301 || statusCode === 302)) {
106
+ const callback = String(foundCallback)
114
107
  const response = Response.redirect(callback, statusCode)
115
108
 
116
109
  return await noCache(response)
117
110
  }
118
111
 
119
- if (route?.method !== req.method) return new Response('Method not allowed', { status: 405 })
112
+ if (foundRoute?.method !== req.method) return new Response('Method not allowed', { status: 405 })
120
113
 
121
114
  // Check if it's a path to an HTML file
122
- if (isString(route.callback) && extname(route.callback) === '.html') {
115
+ if (isString(foundCallback) && extname(foundCallback) === '.html') {
123
116
  try {
124
- const fileContent = Bun.file(route.callback)
117
+ const fileContent = Bun.file(foundCallback)
125
118
 
126
119
  return await new Response(fileContent, {
127
120
  headers: { 'Content-Type': 'text/html' },
@@ -131,14 +124,15 @@ async function execute(route: Route, req: Request, { statusCode }: Options) {
131
124
  }
132
125
  }
133
126
 
134
- if (isString(route.callback)) return await new Response(route.callback)
127
+ if (isString(foundCallback)) return await new Response(foundCallback)
128
+
129
+ if (isFunction(foundCallback)) {
130
+ const result = foundCallback()
135
131
 
136
- if (isFunction(route.callback)) {
137
- const result = route.callback()
138
132
  return await new Response(JSON.stringify(result))
139
133
  }
140
134
 
141
- if (isObject(route.callback)) return await new Response(JSON.stringify(route.callback))
135
+ if (isObject(foundCallback)) return await new Response(JSON.stringify(foundCallback))
142
136
 
143
137
  // If no known type matched, return a generic error.
144
138
  return await new Response('Unknown callback type.', { status: 500 })
@@ -152,6 +146,41 @@ function noCache(response: Response) {
152
146
  return response
153
147
  }
154
148
 
149
+ function addRouteQuery(url: URL): void {
150
+ if (!isObjectNotEmpty(url.searchParams)) RequestParam.addQuery(url)
151
+
152
+ // requestInstance.extractParamsFromRoute(route.uri, url.pathname)
153
+ }
154
+
155
+ function addRouteParam(param: RouteParam): void {
156
+ RequestParam.addParam(param)
157
+ }
158
+
159
+ function executeMiddleware(route: Route): void {
160
+ const { middleware = null } = route
161
+
162
+ if (middleware && middlewares && isObjectNotEmpty(middlewares)) {
163
+ // let middlewareItem: MiddlewareOptions
164
+ if (isString(middleware)) {
165
+ // TODO: fix and uncomment this
166
+ // middlewareItem = middlewares.find((m) => {
167
+ // return m.name === middleware
168
+ // })
169
+ // if (middlewareItem)
170
+ // middlewareItem.handle() // Invoke only if it exists and is not undefined.
171
+ } else {
172
+ // middleware.forEach((m) => {
173
+ middleware.forEach(() => {
174
+ // TODO: fix and uncomment this
175
+ // middlewareItem = middlewares.find((middlewareItem: MiddlewareOptions) => {
176
+ // return middlewareItem.name === m
177
+ // })
178
+ // if (middlewareItem)
179
+ // middlewareItem.handle() // Again, invoke only if it exists.
180
+ })
181
+ }
182
+ }
183
+ }
155
184
  function isString(val: unknown): val is string {
156
185
  return typeof val === 'string'
157
186
  }
@@ -165,5 +194,5 @@ function isFunction(val: unknown): val is Function {
165
194
  }
166
195
 
167
196
  function isObject(val: unknown): val is object {
168
- return val !== null && typeof val === 'object' && !Array.isArray(val)
197
+ return typeof val === 'object'
169
198
  }
package/src/utils.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { route } from './router'
2
+
3
+ export async function listRoutes() {
4
+ const routeLists = await route.getRoutes()
5
+
6
+ console.table(routeLists)
7
+
8
+ return ok('Successfully listed routes!')
9
+ }