@stacksjs/router 0.58.70 → 0.58.72
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 +107 -33
- package/package.json +2 -1
- package/src/router.ts +135 -38
- package/src/server.ts +26 -19
package/dist/index.js
CHANGED
|
@@ -552,7 +552,7 @@ var toNamespacedPath = function(p) {
|
|
|
552
552
|
return normalizeWindowsPath(p);
|
|
553
553
|
};
|
|
554
554
|
var _EXTNAME_RE = /.(\.[^./]+)$/;
|
|
555
|
-
var
|
|
555
|
+
var extname2 = function(p) {
|
|
556
556
|
const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));
|
|
557
557
|
return match && match[1] || "";
|
|
558
558
|
};
|
|
@@ -590,7 +590,7 @@ var basename = function(p, extension) {
|
|
|
590
590
|
var parse = function(p) {
|
|
591
591
|
const root = normalizeWindowsPath(p).split("/").shift() || "/";
|
|
592
592
|
const base = basename(p);
|
|
593
|
-
const extension =
|
|
593
|
+
const extension = extname2(base);
|
|
594
594
|
return {
|
|
595
595
|
root,
|
|
596
596
|
dir: dirname(p),
|
|
@@ -847,7 +847,10 @@ function replPath(path2) {
|
|
|
847
847
|
function routerPath(path2) {
|
|
848
848
|
return corePath(`router/${path2 || ""}`);
|
|
849
849
|
}
|
|
850
|
-
function routesPath(path2) {
|
|
850
|
+
function routesPath(path2, options) {
|
|
851
|
+
const absolutePath = resourcesPath(`routes/${path2 || ""}`);
|
|
852
|
+
if (options?.relative)
|
|
853
|
+
return relative(process2.cwd(), absolutePath);
|
|
851
854
|
return projectPath(`routes/${path2 || ""}`);
|
|
852
855
|
}
|
|
853
856
|
function searchEnginePath(path2) {
|
|
@@ -1024,7 +1027,7 @@ var path2 = {
|
|
|
1024
1027
|
basename,
|
|
1025
1028
|
delimiter,
|
|
1026
1029
|
dirname,
|
|
1027
|
-
extname,
|
|
1030
|
+
extname: extname2,
|
|
1028
1031
|
format,
|
|
1029
1032
|
isAbsolute,
|
|
1030
1033
|
join,
|
|
@@ -1084,33 +1087,38 @@ class Request {
|
|
|
1084
1087
|
}
|
|
1085
1088
|
var request = new Request;
|
|
1086
1089
|
// src/server.ts
|
|
1087
|
-
import {extname as extname2} from "path";
|
|
1088
1090
|
import {URL} from "url";
|
|
1091
|
+
import process3 from "process";
|
|
1092
|
+
import {log} from "@stacksjs/logging";
|
|
1089
1093
|
async function serve(options = {}) {
|
|
1090
1094
|
const hostname = options.host || "localhost";
|
|
1091
1095
|
const port = options.port || 3000;
|
|
1096
|
+
const development = options.debug ? true : process3.env.APP_ENV !== "production" && process3.env.APP_ENV !== "prod";
|
|
1097
|
+
if (options.timezone)
|
|
1098
|
+
process3.env.TZ = options.timezone;
|
|
1092
1099
|
Bun.serve({
|
|
1093
1100
|
hostname,
|
|
1094
1101
|
port,
|
|
1102
|
+
development,
|
|
1095
1103
|
fetch(req) {
|
|
1096
1104
|
return serverResponse(req);
|
|
1097
1105
|
}
|
|
1098
1106
|
});
|
|
1099
1107
|
}
|
|
1100
1108
|
async function serverResponse(req) {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
console.log(`${timestamp} - Query: ${JSON.stringify(req.query)}`);
|
|
1106
|
-
console.log(`${timestamp} - Params: ${JSON.stringify(req.params)}`);
|
|
1107
|
-
console.log(`${timestamp} - Cookies: ${JSON.stringify(req.cookies)}`);
|
|
1109
|
+
log.info(`Incoming Request: ${req.method} ${req.url}`);
|
|
1110
|
+
log.info(`Headers: ${JSON.stringify(req.headers)}`);
|
|
1111
|
+
log.info(`Body: ${JSON.stringify(req.body)}`);
|
|
1112
|
+
const trimmedUrl = req.url.endsWith("/") && req.url.length > 1 ? req.url.slice(0, -1) : req.url;
|
|
1108
1113
|
const routesList = await route.getRoutes();
|
|
1109
|
-
|
|
1114
|
+
log.info(`Routes List: ${JSON.stringify(routesList)}`);
|
|
1115
|
+
const url = new URL(trimmedUrl);
|
|
1116
|
+
log.info(`URL: ${JSON.stringify(url)}`);
|
|
1110
1117
|
const foundRoute = routesList.find((route2) => {
|
|
1111
1118
|
const pattern = new RegExp(`^${route2.uri.replace(/:\w+/g, "\\w+")}\$`);
|
|
1112
1119
|
return pattern.test(url.pathname);
|
|
1113
1120
|
});
|
|
1121
|
+
log.info(`Found Route: ${JSON.stringify(foundRoute)}`);
|
|
1114
1122
|
if (!foundRoute)
|
|
1115
1123
|
return new Response("Pretty 404 page coming soon", { status: 404 });
|
|
1116
1124
|
addRouteParamsAndQuery(url, foundRoute);
|
|
@@ -1152,7 +1160,7 @@ var execute = function(route2, request3, { statusCode }) {
|
|
|
1152
1160
|
}
|
|
1153
1161
|
if (route2?.method !== request3.method)
|
|
1154
1162
|
return new Response("Method not allowed", { status: 405 });
|
|
1155
|
-
if (isString(route2.callback) &&
|
|
1163
|
+
if (isString(route2.callback) && extname(route2.callback) === ".html") {
|
|
1156
1164
|
try {
|
|
1157
1165
|
const fileContent = Bun.file(route2.callback);
|
|
1158
1166
|
return new Response(fileContent, { headers: { "Content-Type": "text/html" } });
|
|
@@ -1188,6 +1196,9 @@ var isFunction = function(val) {
|
|
|
1188
1196
|
var isObject = function(val) {
|
|
1189
1197
|
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
1190
1198
|
};
|
|
1199
|
+
// src/router.ts
|
|
1200
|
+
import {log as log2} from "@stacksjs/logging";
|
|
1201
|
+
|
|
1191
1202
|
// /home/runner/work/stacks/stacks/storage/framework/core/strings/src/utils.ts
|
|
1192
1203
|
var import_slugify = __toESM(require_slugify(), 1);
|
|
1193
1204
|
// /home/runner/work/stacks/stacks/node_modules/change-case/dist/index.js
|
|
@@ -1324,6 +1335,9 @@ var pluralize = __toESM(require_pluralize(), 1);
|
|
|
1324
1335
|
// src/router.ts
|
|
1325
1336
|
class Router {
|
|
1326
1337
|
routes = [];
|
|
1338
|
+
apiPrefix = "/api";
|
|
1339
|
+
groupPrefix = "";
|
|
1340
|
+
path = "";
|
|
1327
1341
|
addRoute(method, uri, callback, statusCode) {
|
|
1328
1342
|
const name = uri.replace(/\//g, ".").replace(/:/g, "");
|
|
1329
1343
|
const pattern = new RegExp(`^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
|
|
@@ -1335,6 +1349,7 @@ class Router {
|
|
|
1335
1349
|
} else {
|
|
1336
1350
|
routeCallback = callback;
|
|
1337
1351
|
}
|
|
1352
|
+
log2.debug(`Adding route: ${method} ${uri} with name ${name}`);
|
|
1338
1353
|
this.routes.push({
|
|
1339
1354
|
name,
|
|
1340
1355
|
method,
|
|
@@ -1345,34 +1360,41 @@ class Router {
|
|
|
1345
1360
|
statusCode,
|
|
1346
1361
|
paramNames: []
|
|
1347
1362
|
});
|
|
1363
|
+
return this;
|
|
1348
1364
|
}
|
|
1349
1365
|
async get(path5, callback) {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
callback = actionModule.default.handle;
|
|
1357
|
-
}
|
|
1358
|
-
this.addRoute("GET", path5, callback, 200);
|
|
1359
|
-
return this;
|
|
1366
|
+
this.path = this.normalizePath(path5);
|
|
1367
|
+
log2.debug(`Normalized Path: ${this.path}`);
|
|
1368
|
+
callback = await this.resolveCallback(callback);
|
|
1369
|
+
const uri = this.prepareUri(this.path);
|
|
1370
|
+
log2.debug(`Prepared URI: ${uri}`);
|
|
1371
|
+
return this.addRoute("GET", uri, callback, 200);
|
|
1360
1372
|
}
|
|
1361
1373
|
async health() {
|
|
1362
1374
|
const healthModule = await import(path2.userActionsPath("HealthAction.ts"));
|
|
1363
1375
|
const callback = healthModule.default.handle;
|
|
1364
|
-
this.
|
|
1376
|
+
const path5 = healthModule.default.path ?? `${this.apiPrefix}/health`;
|
|
1377
|
+
this.addRoute("GET", path5, callback, 200);
|
|
1365
1378
|
return this;
|
|
1366
1379
|
}
|
|
1367
1380
|
async job(path5) {
|
|
1368
|
-
|
|
1369
|
-
path5 = pascalCase(path5.replace(/^api\/?|^\//, ""));
|
|
1381
|
+
path5 = pascalCase(path5);
|
|
1370
1382
|
const jobModule = await import(path2.userJobsPath(`${path5}Job.ts`.replace(/JobJob/, "Job")));
|
|
1371
1383
|
const callback = jobModule.default.handle;
|
|
1384
|
+
path5 = this.prepareUri(path5);
|
|
1385
|
+
this.addRoute("GET", path5, callback, 200);
|
|
1386
|
+
return this;
|
|
1387
|
+
}
|
|
1388
|
+
async action(path5) {
|
|
1389
|
+
path5 = pascalCase(path5);
|
|
1390
|
+
const actionModule = await import(path2.userActionsPath(`${path5}Action.ts`.replace(/ActionAction/, "Action")));
|
|
1391
|
+
const callback = actionModule.default.handle;
|
|
1392
|
+
path5 = this.prepareUri(path5);
|
|
1372
1393
|
this.addRoute("GET", path5, callback, 200);
|
|
1373
1394
|
return this;
|
|
1374
1395
|
}
|
|
1375
1396
|
post(path5, callback) {
|
|
1397
|
+
path5 = this.prepareUri(path5);
|
|
1376
1398
|
this.addRoute("POST", path5, callback, 201);
|
|
1377
1399
|
return this;
|
|
1378
1400
|
}
|
|
@@ -1397,16 +1419,18 @@ class Router {
|
|
|
1397
1419
|
return this;
|
|
1398
1420
|
}
|
|
1399
1421
|
group(options, callback) {
|
|
1422
|
+
if (typeof options === "string")
|
|
1423
|
+
options = options.startsWith("/") ? options.slice(1) : options;
|
|
1400
1424
|
let cb;
|
|
1425
|
+
this.prepareGroupPrefix(options);
|
|
1401
1426
|
if (typeof options === "function") {
|
|
1402
1427
|
cb = options;
|
|
1403
1428
|
options = {};
|
|
1404
|
-
} else {
|
|
1405
|
-
if (!callback)
|
|
1406
|
-
throw new Error("Missing callback function for route group.");
|
|
1407
|
-
cb = callback;
|
|
1408
1429
|
}
|
|
1409
|
-
|
|
1430
|
+
if (!callback)
|
|
1431
|
+
throw new Error("Missing callback function for your route group.");
|
|
1432
|
+
cb = callback;
|
|
1433
|
+
const { prefix, middleware: middleware2 = [] } = options;
|
|
1410
1434
|
const originalRoutes = this.routes;
|
|
1411
1435
|
this.routes = [];
|
|
1412
1436
|
cb();
|
|
@@ -1433,9 +1457,59 @@ class Router {
|
|
|
1433
1457
|
return this;
|
|
1434
1458
|
}
|
|
1435
1459
|
async getRoutes() {
|
|
1436
|
-
await import(
|
|
1460
|
+
await import(routesPath("api.ts"));
|
|
1437
1461
|
return this.routes;
|
|
1438
1462
|
}
|
|
1463
|
+
setGroupPrefix(prefix, options = {}) {
|
|
1464
|
+
if (prefix !== "") {
|
|
1465
|
+
prefix = `/${this.groupPrefix}/${prefix}`.replace(/\/\//g, "/");
|
|
1466
|
+
this.groupPrefix = prefix;
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const effectiveOptions = typeof options === "object" ? options : {};
|
|
1470
|
+
this.groupPrefix = effectiveOptions.prefix ?? prefix ?? "";
|
|
1471
|
+
}
|
|
1472
|
+
prepareGroupPrefix(options) {
|
|
1473
|
+
if (this.groupPrefix !== "" && typeof options !== "string")
|
|
1474
|
+
return this.setGroupPrefix(this.groupPrefix, options);
|
|
1475
|
+
if (typeof options === "string")
|
|
1476
|
+
return this.setGroupPrefix(options);
|
|
1477
|
+
return this.setGroupPrefix("", options);
|
|
1478
|
+
}
|
|
1479
|
+
async resolveCallback(callback) {
|
|
1480
|
+
if (callback instanceof Promise) {
|
|
1481
|
+
const actionModule = await callback;
|
|
1482
|
+
return actionModule.default;
|
|
1483
|
+
}
|
|
1484
|
+
if (typeof callback === "string")
|
|
1485
|
+
return this.importCallbackFromPath(callback, this.path);
|
|
1486
|
+
return callback;
|
|
1487
|
+
}
|
|
1488
|
+
async importCallbackFromPath(callbackPath, originalPath) {
|
|
1489
|
+
let modulePath = callbackPath;
|
|
1490
|
+
let importPathFunction = path2.appPath;
|
|
1491
|
+
if (callbackPath.startsWith("../"))
|
|
1492
|
+
importPathFunction = path2.routesPath;
|
|
1493
|
+
modulePath = modulePath.endsWith(".ts") ? modulePath.slice(0, -3) : modulePath;
|
|
1494
|
+
const actionModule = await import(importPathFunction(`${modulePath}.ts`));
|
|
1495
|
+
const newPath = actionModule.default.path ?? originalPath;
|
|
1496
|
+
this.updatePathIfNeeded(newPath, originalPath);
|
|
1497
|
+
return actionModule.default.handle;
|
|
1498
|
+
}
|
|
1499
|
+
normalizePath(path5) {
|
|
1500
|
+
return path5.endsWith("/") ? path5.slice(0, -1) : path5;
|
|
1501
|
+
}
|
|
1502
|
+
prepareUri(path5) {
|
|
1503
|
+
if (path5.startsWith("/"))
|
|
1504
|
+
path5 = path5.slice(1);
|
|
1505
|
+
path5 = `${this.apiPrefix}${this.groupPrefix}/${path5}`;
|
|
1506
|
+
return path5.endsWith("/") ? path5.slice(0, -1) : path5;
|
|
1507
|
+
}
|
|
1508
|
+
updatePathIfNeeded(newPath, originalPath) {
|
|
1509
|
+
if (newPath !== originalPath) {
|
|
1510
|
+
this.path = newPath;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1439
1513
|
}
|
|
1440
1514
|
var route = new Router;
|
|
1441
1515
|
export {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.58.
|
|
4
|
+
"version": "0.58.72",
|
|
5
5
|
"description": "The Stacks framework router.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"license": "MIT",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@stacksjs/config": "latest",
|
|
57
|
+
"@stacksjs/logging": "latest",
|
|
57
58
|
"unplugin-vue-router": "^0.7.0",
|
|
58
59
|
"vue-router": "^4.2.5"
|
|
59
60
|
},
|
package/src/router.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { RedirectCode, Route, RouteGroupOptions, StatusCode } from '@stacksjs/types'
|
|
2
|
-
import { path as p,
|
|
2
|
+
import { path as p, routesPath } from '@stacksjs/path'
|
|
3
|
+
import { log } from '@stacksjs/logging'
|
|
3
4
|
import { pascalCase } from '@stacksjs/strings'
|
|
4
5
|
|
|
6
|
+
type Prefix = string
|
|
7
|
+
|
|
5
8
|
export interface RouterInterface {
|
|
6
9
|
get: (url: Route['url'], callback: Route['callback']) => Promise<this>
|
|
7
10
|
post: (url: Route['url'], callback: Route['callback']) => this
|
|
@@ -10,7 +13,7 @@ export interface RouterInterface {
|
|
|
10
13
|
delete: (url: Route['url'], callback: Route['callback']) => this
|
|
11
14
|
patch: (url: Route['url'], callback: Route['callback']) => this
|
|
12
15
|
put: (url: Route['url'], callback: Route['callback']) => this
|
|
13
|
-
group: (options: RouteGroupOptions, callback: () => void) => this
|
|
16
|
+
group: (options: Prefix | RouteGroupOptions, callback: () => void) => this
|
|
14
17
|
name: (name: string) => this
|
|
15
18
|
middleware: (middleware: Route['middleware']) => this
|
|
16
19
|
getRoutes: () => Promise<Route[]>
|
|
@@ -18,8 +21,11 @@ export interface RouterInterface {
|
|
|
18
21
|
|
|
19
22
|
export class Router implements RouterInterface {
|
|
20
23
|
private routes: Route[] = []
|
|
24
|
+
private apiPrefix = '/api'
|
|
25
|
+
private groupPrefix = ''
|
|
26
|
+
private path = ''
|
|
21
27
|
|
|
22
|
-
private addRoute(method: Route['method'], uri: string, callback: Route['callback'] | string | object, statusCode: StatusCode):
|
|
28
|
+
private addRoute(method: Route['method'], uri: string, callback: Route['callback'] | string | object, statusCode: StatusCode): this {
|
|
23
29
|
const name = uri.replace(/\//g, '.').replace(/:/g, '') // we can improve this
|
|
24
30
|
const pattern = new RegExp(`^${uri.replace(/:[a-zA-Z]+/g, (_match) => {
|
|
25
31
|
return '([a-zA-Z0-9-]+)'
|
|
@@ -35,6 +41,8 @@ export class Router implements RouterInterface {
|
|
|
35
41
|
routeCallback = callback
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
log.debug(`Adding route: ${method} ${uri} with name ${name}`)
|
|
45
|
+
|
|
38
46
|
this.routes.push({
|
|
39
47
|
name,
|
|
40
48
|
method,
|
|
@@ -45,59 +53,75 @@ export class Router implements RouterInterface {
|
|
|
45
53
|
statusCode,
|
|
46
54
|
paramNames: [],
|
|
47
55
|
})
|
|
56
|
+
|
|
57
|
+
return this
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
public async get(path: Route['url'], callback: Route['callback']): Promise<this> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const actionModule = await callback
|
|
54
|
-
callback = actionModule.default
|
|
55
|
-
}
|
|
61
|
+
this.path = this.normalizePath(path)
|
|
62
|
+
log.debug(`Normalized Path: ${this.path}`)
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
// import the module and use the default.handle function as the callback
|
|
59
|
-
const actionModule = await import(p.userActionsPath(`${callback}.ts`))
|
|
64
|
+
callback = await this.resolveCallback(callback)
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
66
|
+
const uri = this.prepareUri(this.path)
|
|
67
|
+
log.debug(`Prepared URI: ${uri}`)
|
|
64
68
|
|
|
65
|
-
this.addRoute('GET',
|
|
66
|
-
return this
|
|
69
|
+
return this.addRoute('GET', uri, callback, 200)
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
public async health(): Promise<this> {
|
|
70
73
|
const healthModule = await import(p.userActionsPath('HealthAction.ts'))
|
|
71
74
|
const callback = healthModule.default.handle
|
|
72
75
|
|
|
73
|
-
this.
|
|
76
|
+
const path = healthModule.default.path ?? `${this.apiPrefix}/health`
|
|
77
|
+
|
|
78
|
+
this.addRoute('GET', path, callback, 200)
|
|
79
|
+
|
|
74
80
|
return this
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
public async job(path: Route['url']): Promise<this> {
|
|
78
|
-
|
|
79
|
-
path = pascalCase(path.replace(/^api\/?|^\//, ''))
|
|
84
|
+
path = pascalCase(path)
|
|
80
85
|
|
|
81
86
|
// removes the potential `JobJob` suffix in case the user does not choose to use the Job suffix in their file name
|
|
82
87
|
const jobModule = await import(p.userJobsPath(`${path}Job.ts`.replace(/JobJob/, 'Job')))
|
|
83
88
|
const callback = jobModule.default.handle
|
|
84
89
|
|
|
90
|
+
path = this.prepareUri(path)
|
|
91
|
+
this.addRoute('GET', path, callback, 200)
|
|
92
|
+
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public async action(path: Route['url']): Promise<this> {
|
|
97
|
+
path = pascalCase(path) // actions are PascalCase
|
|
98
|
+
|
|
99
|
+
// removes the potential `ActionAction` suffix in case the user does not choose to use the Job suffix in their file name
|
|
100
|
+
const actionModule = await import(p.userActionsPath(`${path}Action.ts`.replace(/ActionAction/, 'Action')))
|
|
101
|
+
const callback = actionModule.default.handle
|
|
102
|
+
|
|
103
|
+
path = this.prepareUri(path)
|
|
85
104
|
this.addRoute('GET', path, callback, 200)
|
|
105
|
+
|
|
86
106
|
return this
|
|
87
107
|
}
|
|
88
108
|
|
|
89
109
|
public post(path: Route['url'], callback: Route['callback']): this {
|
|
110
|
+
path = this.prepareUri(path)
|
|
90
111
|
this.addRoute('POST', path, callback, 201)
|
|
112
|
+
|
|
91
113
|
return this
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
public view(path: Route['url'], callback: Route['callback']): this {
|
|
95
117
|
this.addRoute('GET', path, callback, 200)
|
|
118
|
+
|
|
96
119
|
return this
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
public redirect(path: Route['url'], callback: Route['callback'], _status?: RedirectCode): this {
|
|
100
123
|
this.addRoute('GET', path, callback, 302)
|
|
124
|
+
|
|
101
125
|
return this
|
|
102
126
|
}
|
|
103
127
|
|
|
@@ -116,37 +140,41 @@ export class Router implements RouterInterface {
|
|
|
116
140
|
return this
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
public group(options:
|
|
143
|
+
public group(options: string | RouteGroupOptions, callback?: () => void): this {
|
|
144
|
+
if (typeof options === 'string')
|
|
145
|
+
options = options.startsWith('/') ? options.slice(1) : options
|
|
146
|
+
|
|
120
147
|
let cb: () => void
|
|
121
148
|
|
|
149
|
+
this.prepareGroupPrefix(options)
|
|
150
|
+
|
|
122
151
|
if (typeof options === 'function') {
|
|
123
152
|
cb = options
|
|
124
153
|
options = {}
|
|
125
154
|
}
|
|
126
|
-
else {
|
|
127
|
-
if (!callback)
|
|
128
|
-
throw new Error('Missing callback function for route group.')
|
|
129
|
-
cb = callback
|
|
130
|
-
}
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
if (!callback)
|
|
157
|
+
throw new Error('Missing callback function for your route group.')
|
|
158
|
+
|
|
159
|
+
cb = callback
|
|
160
|
+
|
|
161
|
+
const { prefix, middleware = [] } = options as RouteGroupOptions
|
|
133
162
|
|
|
134
|
-
// Save a reference to the original routes array
|
|
163
|
+
// Save a reference to the original routes array
|
|
135
164
|
const originalRoutes = this.routes
|
|
136
165
|
|
|
137
|
-
// Create a new routes array for the duration of the callback
|
|
166
|
+
// Create a new routes array for the duration of the callback
|
|
138
167
|
this.routes = []
|
|
139
168
|
|
|
140
|
-
// Execute the callback. This will add routes to the new this.routes array
|
|
169
|
+
// Execute the callback. This will add routes to the new this.routes array
|
|
141
170
|
cb()
|
|
142
171
|
|
|
143
|
-
// For each route added by the callback, adjust the URI and add to the original routes array
|
|
172
|
+
// For each route added by the callback, adjust the URI and add to the original routes array
|
|
144
173
|
this.routes.forEach((r) => {
|
|
145
174
|
r.uri = `${prefix}${r.uri}`
|
|
146
175
|
|
|
147
176
|
if (middleware.length)
|
|
148
177
|
r.middleware = middleware
|
|
149
|
-
// Assuming you have a middleware property for each route.
|
|
150
178
|
|
|
151
179
|
originalRoutes.push(r)
|
|
152
180
|
return this
|
|
@@ -180,17 +208,86 @@ export class Router implements RouterInterface {
|
|
|
180
208
|
}
|
|
181
209
|
|
|
182
210
|
public async getRoutes(): Promise<Route[]> {
|
|
183
|
-
|
|
211
|
+
await import(routesPath('api.ts'))
|
|
184
212
|
|
|
185
|
-
|
|
213
|
+
return this.routes
|
|
214
|
+
}
|
|
186
215
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
216
|
+
private setGroupPrefix(prefix: string, options: RouteGroupOptions = {}) {
|
|
217
|
+
if (prefix !== '') {
|
|
218
|
+
prefix = `/${this.groupPrefix}/${prefix}`.replace(/\/\//g, '/') // remove double slashes in case there are any
|
|
219
|
+
this.groupPrefix = prefix
|
|
220
|
+
return
|
|
221
|
+
}
|
|
190
222
|
|
|
191
|
-
//
|
|
223
|
+
// Ensure options is always treated as an object, even if it's undefined or a function
|
|
224
|
+
const effectiveOptions = typeof options === 'object' ? options : {}
|
|
192
225
|
|
|
193
|
-
|
|
226
|
+
this.groupPrefix = effectiveOptions.prefix ?? prefix ?? ''
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private prepareGroupPrefix(options: string | RouteGroupOptions): void {
|
|
230
|
+
if (this.groupPrefix !== '' && typeof options !== 'string')
|
|
231
|
+
return this.setGroupPrefix(this.groupPrefix, options)
|
|
232
|
+
|
|
233
|
+
if (typeof options === 'string')
|
|
234
|
+
return this.setGroupPrefix(options)
|
|
235
|
+
|
|
236
|
+
return this.setGroupPrefix('', options)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private async resolveCallback(callback: Route['callback']): Promise<Route['callback']> {
|
|
240
|
+
if (callback instanceof Promise) {
|
|
241
|
+
const actionModule = await callback
|
|
242
|
+
return actionModule.default
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (typeof callback === 'string')
|
|
246
|
+
return this.importCallbackFromPath(callback, this.path)
|
|
247
|
+
|
|
248
|
+
// in this case, the callback ends up being a function
|
|
249
|
+
return callback
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async importCallbackFromPath(callbackPath: string, originalPath: string): Promise<Route['callback']> {
|
|
253
|
+
let modulePath = callbackPath
|
|
254
|
+
let importPathFunction = p.appPath // Default import path function
|
|
255
|
+
|
|
256
|
+
if (callbackPath.startsWith('../'))
|
|
257
|
+
importPathFunction = p.routesPath
|
|
258
|
+
|
|
259
|
+
// Remove trailing .ts if present
|
|
260
|
+
modulePath = modulePath.endsWith('.ts') ? modulePath.slice(0, -3) : modulePath
|
|
261
|
+
const actionModule = await import(importPathFunction(`${modulePath}.ts`))
|
|
262
|
+
|
|
263
|
+
// Use custom path from action module if available
|
|
264
|
+
const newPath = actionModule.default.path ?? originalPath
|
|
265
|
+
this.updatePathIfNeeded(newPath, originalPath)
|
|
266
|
+
|
|
267
|
+
return actionModule.default.handle
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private normalizePath(path: string): string {
|
|
271
|
+
return path.endsWith('/') ? path.slice(0, -1) : path
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public prepareUri(path: string) {
|
|
275
|
+
// if string starts with / then remove it because we are adding it back in the next line
|
|
276
|
+
if (path.startsWith('/'))
|
|
277
|
+
path = path.slice(1)
|
|
278
|
+
|
|
279
|
+
path = `${this.apiPrefix}${this.groupPrefix}/${path}`
|
|
280
|
+
|
|
281
|
+
// if path ends in "/", then remove it
|
|
282
|
+
// e.g. triggered when route is "/"
|
|
283
|
+
return path.endsWith('/') ? path.slice(0, -1) : path
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private updatePathIfNeeded(newPath: string, originalPath: string): void {
|
|
287
|
+
if (newPath !== originalPath) {
|
|
288
|
+
// Logic to update the path if needed, based on the action module's custom path
|
|
289
|
+
this.path = newPath
|
|
290
|
+
}
|
|
194
291
|
}
|
|
195
292
|
}
|
|
196
293
|
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { extname } from 'node:path'
|
|
2
1
|
import { URL } from 'node:url'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { log } from '@stacksjs/logging'
|
|
3
4
|
import type { MiddlewareType, Route, StatusCode } from '@stacksjs/types'
|
|
4
5
|
import { middlewares } from './middleware'
|
|
5
6
|
import { request } from './request'
|
|
@@ -8,17 +9,22 @@ import { route } from '.'
|
|
|
8
9
|
interface ServeOptions {
|
|
9
10
|
host?: string
|
|
10
11
|
port?: number
|
|
11
|
-
|
|
12
|
+
debug?: boolean
|
|
13
|
+
timezone?: string
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export async function serve(options: ServeOptions = {}) {
|
|
15
|
-
// maybe make use of localUrl({ type: 'backend' }) here & options.tunnel
|
|
16
17
|
const hostname = options.host || 'localhost'
|
|
17
18
|
const port = options.port || 3000
|
|
19
|
+
const development = options.debug ? true : process.env.APP_ENV !== 'production' && process.env.APP_ENV !== 'prod'
|
|
20
|
+
|
|
21
|
+
if (options.timezone)
|
|
22
|
+
process.env.TZ = options.timezone
|
|
18
23
|
|
|
19
24
|
Bun.serve({
|
|
20
25
|
hostname,
|
|
21
26
|
port,
|
|
27
|
+
development,
|
|
22
28
|
|
|
23
29
|
fetch(req: Request) {
|
|
24
30
|
return serverResponse(req)
|
|
@@ -27,24 +33,23 @@ export async function serve(options: ServeOptions = {}) {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
export async function serverResponse(req: Request) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// eslint-disable-next-line no-console
|
|
42
|
-
console.log(`${timestamp} - Params: ${JSON.stringify(req.params)}`)
|
|
43
|
-
// eslint-disable-next-line no-console
|
|
44
|
-
console.log(`${timestamp} - Cookies: ${JSON.stringify(req.cookies)}`)
|
|
36
|
+
log.info(`Incoming Request: ${req.method} ${req.url}`)
|
|
37
|
+
log.info(`Headers: ${JSON.stringify(req.headers)}`)
|
|
38
|
+
log.info(`Body: ${JSON.stringify(req.body)}`)
|
|
39
|
+
// log.info(`Query: ${JSON.stringify(req.query)}`)
|
|
40
|
+
// log.info(`Params: ${JSON.stringify(req.params)}`)
|
|
41
|
+
// log.info(`Cookies: ${JSON.stringify(req.cookies)}`)
|
|
42
|
+
|
|
43
|
+
// Trim trailing slash from the URL if it's not the root '/'
|
|
44
|
+
// This automatically allows for route definitions, like
|
|
45
|
+
// '/about' and '/about/' to be treated as the same
|
|
46
|
+
const trimmedUrl = req.url.endsWith('/') && req.url.length > 1 ? req.url.slice(0, -1) : req.url
|
|
45
47
|
|
|
46
48
|
const routesList: Route[] = await route.getRoutes()
|
|
47
|
-
|
|
49
|
+
log.info(`Routes List: ${JSON.stringify(routesList)}`)
|
|
50
|
+
|
|
51
|
+
const url = new URL(trimmedUrl)
|
|
52
|
+
log.info(`URL: ${JSON.stringify(url)}`)
|
|
48
53
|
|
|
49
54
|
const foundRoute: Route | undefined = routesList.find((route: Route) => {
|
|
50
55
|
const pattern = new RegExp(`^${route.uri.replace(/:\w+/g, '\\w+')}$`)
|
|
@@ -52,6 +57,8 @@ export async function serverResponse(req: Request) {
|
|
|
52
57
|
return pattern.test(url.pathname)
|
|
53
58
|
})
|
|
54
59
|
|
|
60
|
+
log.info(`Found Route: ${JSON.stringify(foundRoute)}`)
|
|
61
|
+
|
|
55
62
|
// if (url.pathname === '/favicon.ico')
|
|
56
63
|
// return new Response('')
|
|
57
64
|
|