bun-router 0.7.4-experimental.5 → 0.7.4-experimental.7
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/examples/ssr/index.ts +1 -1
- package/examples/static.ts +2 -1
- package/lib/fs/filetree.ts +6 -7
- package/lib/fs/fsys.ts +10 -11
- package/lib/logger/color.ts +25 -25
- package/lib/logger/logger.d.ts +1 -1
- package/lib/logger/logger.ts +1 -1
- package/lib/router/context.ts +5 -6
- package/lib/router/{tree.ts → routeTree.ts} +5 -6
- package/lib/router/router.ts +29 -26
- package/package.json +1 -1
- package/tsconfig.json +0 -1
- package/lib/resolver/resolver.test.ts +0 -5
- package/lib/resolver/resolver.ts +0 -43
- package/lib/resolver/testdir/foo.tsx +0 -5
- package/lib/util/strings.ts +0 -3
package/examples/ssr/index.ts
CHANGED
package/examples/static.ts
CHANGED
package/lib/fs/filetree.ts
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import { splitFilePath } from '../fs/fsys';
|
2
1
|
import path from 'node:path';
|
3
2
|
|
4
3
|
type File = {
|
@@ -22,8 +21,8 @@ function createFile(name: string): File {
|
|
22
21
|
const FileTree = (dir: string) => {
|
23
22
|
const root = createFile(dir);
|
24
23
|
|
25
|
-
const addFile = (
|
26
|
-
const pathParts =
|
24
|
+
const addFile = (filepath: string) => {
|
25
|
+
const pathParts = filepath.split(path.sep);
|
27
26
|
let current = root;
|
28
27
|
|
29
28
|
for (let i = 0; i < pathParts.length; i++) {
|
@@ -35,8 +34,8 @@ const FileTree = (dir: string) => {
|
|
35
34
|
}
|
36
35
|
|
37
36
|
current.isLast = true;
|
38
|
-
current.path =
|
39
|
-
current.extension = path.extname(
|
37
|
+
current.path = filepath;
|
38
|
+
current.extension = path.extname(filepath);
|
40
39
|
};
|
41
40
|
|
42
41
|
const getFilesByExtension = (extension: string): string[] => {
|
@@ -53,9 +52,9 @@ const FileTree = (dir: string) => {
|
|
53
52
|
return files;
|
54
53
|
};
|
55
54
|
|
56
|
-
const getFileByName = (
|
55
|
+
const getFileByName = (filepath: string): string | undefined => {
|
57
56
|
let current = root;
|
58
|
-
const pathParts =
|
57
|
+
const pathParts = filepath.split(path.sep);
|
59
58
|
for (let i = 0; i < pathParts.length; i++) {
|
60
59
|
const part = pathParts[i];
|
61
60
|
if (current.children.has(part)) {
|
package/lib/fs/fsys.ts
CHANGED
@@ -22,19 +22,18 @@ async function readDir(dirpath: string, handler: (filepath: string, entry: BunFi
|
|
22
22
|
}
|
23
23
|
}
|
24
24
|
|
25
|
-
//
|
26
|
-
function ext(p: string): string {
|
27
|
-
return path.extname(p);
|
28
|
-
}
|
29
|
-
|
30
|
-
// split a file path into an array of strings (unnecessary)
|
31
|
-
function splitFilePath(p: string): string[] {
|
32
|
-
return p.split(path.sep);
|
33
|
-
}
|
34
|
-
|
25
|
+
// resolve module paths relative to the current working directory
|
35
26
|
function resolveModulePath(module: string) {
|
36
27
|
return path.join(process.cwd(), module);
|
37
28
|
}
|
38
29
|
|
30
|
+
function exists(filepath: string): boolean {
|
31
|
+
try {
|
32
|
+
fs.access(filepath);
|
33
|
+
return true;
|
34
|
+
} catch (err) {
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
}
|
39
38
|
|
40
|
-
export { readDir,
|
39
|
+
export { readDir, resolveModulePath, exists };
|
package/lib/logger/color.ts
CHANGED
@@ -1,36 +1,36 @@
|
|
1
1
|
const Colors: Record<string,string> = {
|
2
|
-
|
2
|
+
reset: '\x1b[0m',
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
// foreground
|
5
|
+
black: '\x1b[30m',
|
6
|
+
red: '\x1b[31m',
|
7
|
+
green: '\x1b[32m',
|
8
|
+
yellow: '\x1b[33m',
|
9
|
+
blue: '\x1b[34m',
|
10
|
+
magenta: '\x1b[35m',
|
11
|
+
cyan: '\x1b[36m',
|
12
|
+
white: '\x1b[37m',
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
// background
|
15
|
+
bgBlack: '\x1b[40m',
|
16
|
+
bgRed: '\x1b[41m',
|
17
|
+
bgGreen: '\x1b[42m',
|
18
|
+
bgYellow: '\x1b[43m',
|
19
|
+
bgBlue: '\x1b[44m',
|
20
|
+
bgMagenta: '\x1b[45m',
|
21
|
+
bgCyan: '\x1b[46m',
|
22
|
+
bgWhite: '\x1b[47m',
|
23
|
+
} as const;
|
24
24
|
|
25
25
|
|
26
26
|
|
27
27
|
function color(foreground: string, background: string, message: string) {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
const _foreground = Colors[foreground];
|
29
|
+
const _background = Colors[background];
|
30
|
+
const reset = Colors.reset;
|
31
|
+
return `${_foreground}${_background}${message}${reset}`;
|
32
32
|
}
|
33
33
|
|
34
34
|
|
35
35
|
|
36
|
-
export { color }
|
36
|
+
export { color };
|
package/lib/logger/logger.d.ts
CHANGED
package/lib/logger/logger.ts
CHANGED
@@ -9,7 +9,7 @@ _ _
|
|
9
9
|
|___|___|_|_| |_| |___|___|_| |___|_|
|
10
10
|
|
11
11
|
`;
|
12
|
-
const VERSION = '0.7.4-experimental.
|
12
|
+
const VERSION = '0.7.4-experimental.7';
|
13
13
|
const Logger = (): BunLogger => {
|
14
14
|
return {
|
15
15
|
info: async (statusCode: number, routePath: string, method: string, message?: string) => {
|
package/lib/router/context.ts
CHANGED
@@ -6,8 +6,8 @@ import { http } from './router';
|
|
6
6
|
import { ReactNode } from 'react';
|
7
7
|
|
8
8
|
async function createContext(path: string, route: Route, request: Request): Promise<Context> {
|
9
|
-
const params = extractParams(path, route);
|
10
9
|
const query = new URLSearchParams(path);
|
10
|
+
const params = extractParams(path, route);
|
11
11
|
const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
|
12
12
|
|
13
13
|
return Promise.resolve({
|
@@ -21,16 +21,16 @@ async function createContext(path: string, route: Route, request: Request): Prom
|
|
21
21
|
});
|
22
22
|
}
|
23
23
|
|
24
|
-
function extractParams(
|
24
|
+
function extractParams(pattern: string, route: Route): Map<string, string> {
|
25
25
|
const params: Map<string, string> = new Map();
|
26
|
-
const pathSegments =
|
26
|
+
const pathSegments = pattern.split('/');
|
27
27
|
const routeSegments = route.path.split('/');
|
28
28
|
|
29
29
|
if (pathSegments.length !== routeSegments.length) return params;
|
30
30
|
|
31
31
|
for (let i = 0; i < pathSegments.length; i++) {
|
32
32
|
if (routeSegments[i][0] === ':') {
|
33
|
-
const key = routeSegments[i].
|
33
|
+
const key = routeSegments[i].slice(1);
|
34
34
|
const value = pathSegments[i];
|
35
35
|
params.set(key, value);
|
36
36
|
}
|
@@ -41,8 +41,7 @@ function extractParams(path: string, route: Route): Map<string, string> {
|
|
41
41
|
|
42
42
|
function getContentType(headers: Headers): string {
|
43
43
|
const contentType = headers.get('Content-Type');
|
44
|
-
|
45
|
-
return contentType;
|
44
|
+
return contentType ?? '';
|
46
45
|
}
|
47
46
|
|
48
47
|
function isMultiPartForm(headers: Headers): boolean {
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { HttpHandler, Route } from './router.d';
|
2
2
|
import { http } from '../http/http';
|
3
3
|
import { createContext } from './context';
|
4
|
-
import { splitPath } from '../util/strings';
|
5
4
|
|
6
5
|
const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
|
7
6
|
const route: Route = {
|
@@ -19,8 +18,8 @@ const createRoute = (path: string, method: string, handler: HttpHandler): Route
|
|
19
18
|
const RouteTree = () => {
|
20
19
|
const root = createRoute('', 'GET', () => http.notFound());
|
21
20
|
|
22
|
-
const addRoute = (
|
23
|
-
const pathParts =
|
21
|
+
const addRoute = (pattern: string, method: string, handler: HttpHandler) => {
|
22
|
+
const pathParts = pattern.split('/');
|
24
23
|
let current = root;
|
25
24
|
|
26
25
|
for (let i = 0; i < pathParts.length; i++) {
|
@@ -36,11 +35,11 @@ const RouteTree = () => {
|
|
36
35
|
|
37
36
|
current.handler = handler;
|
38
37
|
current.isLast = true;
|
39
|
-
current.path =
|
38
|
+
current.path = pattern;
|
40
39
|
};
|
41
40
|
|
42
|
-
function findRoute(
|
43
|
-
const pathParts =
|
41
|
+
function findRoute(pathname: string): Route | undefined {
|
42
|
+
const pathParts = pathname.split('/');
|
44
43
|
let current = root;
|
45
44
|
for (let i = 0; i < pathParts.length; i++) {
|
46
45
|
const part = pathParts[i];
|
package/lib/router/router.ts
CHANGED
@@ -2,21 +2,34 @@ import path from 'path';
|
|
2
2
|
import { Database } from 'bun:sqlite';
|
3
3
|
import { Route, BunRouter, RouterOptions, Options, HttpHandler } from './router.d';
|
4
4
|
import { httpStatusCodes } from '../http/status';
|
5
|
-
import { readDir,
|
5
|
+
import { readDir, exists } from '../fs/fsys';
|
6
6
|
import { Logger, startMessage } from '../logger/logger';
|
7
7
|
import { http } from '../http/http';
|
8
|
-
import { RouteTree } from './
|
8
|
+
import { RouteTree } from './routeTree';
|
9
9
|
import { createContext } from './context';
|
10
10
|
|
11
11
|
const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
|
12
12
|
const { addRoute, findRoute } = RouteTree();
|
13
13
|
const logger = Logger();
|
14
14
|
|
15
|
-
async function loadComponent(name: string) {
|
16
|
-
const module = await import(name);
|
15
|
+
async function loadComponent(root: string, name: string) {
|
16
|
+
const module = await import(path.join(process.cwd(), root, name));
|
17
17
|
return module.default;
|
18
18
|
}
|
19
19
|
|
20
|
+
function normalizePath(pattern: string, pathname: string) {
|
21
|
+
const extension = path.extname(pathname);
|
22
|
+
let base = path.basename(pathname);
|
23
|
+
|
24
|
+
if (extension === '.html' || extension === '.tsx') base = base.replace(extension, '');
|
25
|
+
|
26
|
+
let patternPath = [pattern, base].join('/');
|
27
|
+
|
28
|
+
if (base === 'index') patternPath = pattern;
|
29
|
+
|
30
|
+
return { patternPath, extension, base }
|
31
|
+
}
|
32
|
+
|
20
33
|
return {
|
21
34
|
// add a route to the router tree
|
22
35
|
add: (pattern, method, callback) => { addRoute(pattern, method, callback); },
|
@@ -26,35 +39,23 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
|
|
26
39
|
delete: (pattern, callback) => { addRoute(pattern, 'DELETE', callback); },
|
27
40
|
|
28
41
|
// add static routes to the router tree
|
29
|
-
// .tsx and .html are rendered as components
|
42
|
+
// .tsx and .html are rendered as components, or pages
|
30
43
|
// all other file extensions are served as files
|
31
44
|
// the root directory is traversed recursively
|
32
45
|
static: async (pattern: string, root: string) => {
|
46
|
+
if (!exists(root)) console.log(`Cannot find directory ${root}`);
|
33
47
|
await readDir(root, async (fp) => {
|
34
|
-
const
|
35
|
-
|
36
|
-
let base = path.basename(fp);
|
37
|
-
|
38
|
-
//FIXME: this can be improved
|
39
|
-
if (ext === '.html' || ext === '.tsx') base = base.replace(ext, '');
|
40
|
-
|
41
|
-
if (pattern[0] !== '/') pattern = '/' + pattern;
|
42
|
-
|
43
|
-
let patternPath = pattern + '/' + base;
|
44
|
-
|
45
|
-
if (base === 'index') patternPath = pattern;
|
46
|
-
|
47
|
-
const purePath = path.join(root, patternPath);
|
48
|
+
const { patternPath, extension, base } = normalizePath(pattern, fp);
|
48
49
|
|
49
50
|
const route: Route = {
|
50
51
|
children: new Map(),
|
51
52
|
dynamicPath: '',
|
52
53
|
isLast: true,
|
53
|
-
path: patternPath,
|
54
|
+
path: patternPath.slice(1),
|
54
55
|
method: 'GET',
|
55
56
|
handler: async () => {
|
56
|
-
if (
|
57
|
-
const component = await loadComponent(
|
57
|
+
if (extension === '.tsx') {
|
58
|
+
const component = await loadComponent(root, base);
|
58
59
|
return await http.render(component());
|
59
60
|
} else {
|
60
61
|
return await http.file(200, fp);
|
@@ -62,6 +63,8 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
|
|
62
63
|
},
|
63
64
|
};
|
64
65
|
|
66
|
+
console.log(route.path);
|
67
|
+
|
65
68
|
addRoute(route.path, 'GET', route.handler);
|
66
69
|
});
|
67
70
|
},
|
@@ -75,7 +78,7 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
|
|
75
78
|
...options,
|
76
79
|
async fetch(req) {
|
77
80
|
const url = new URL(req.url);
|
78
|
-
const
|
81
|
+
const pathname = url.pathname;
|
79
82
|
|
80
83
|
// set the database
|
81
84
|
if (options) {
|
@@ -83,16 +86,16 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
|
|
83
86
|
opts.db = o.db;
|
84
87
|
}
|
85
88
|
|
86
|
-
const route = findRoute(
|
89
|
+
const route = findRoute(pathname);
|
87
90
|
|
88
|
-
// if the route exists,
|
91
|
+
// if the route exists, call the handler
|
89
92
|
if (route) {
|
90
93
|
if (route.method !== req.method) {
|
91
94
|
logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
|
92
95
|
return Promise.resolve(http.methodNotAllowed());
|
93
96
|
}
|
94
97
|
|
95
|
-
const context = await createContext(
|
98
|
+
const context = await createContext(pathname, route, req);
|
96
99
|
context.db = new Database(opts.db);
|
97
100
|
|
98
101
|
const response = await route.handler(context);
|
package/package.json
CHANGED
package/tsconfig.json
CHANGED
package/lib/resolver/resolver.ts
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
import { FileTree } from '../fs/filetree';
|
2
|
-
import { readDir } from '../fs/fsys';
|
3
|
-
import { ComponentType } from 'react';
|
4
|
-
|
5
|
-
async function createFileTree(root: string) {
|
6
|
-
const tree = FileTree(root);
|
7
|
-
|
8
|
-
await readDir(root, (fp) => {
|
9
|
-
tree.addFile(fp);
|
10
|
-
});
|
11
|
-
|
12
|
-
return tree;
|
13
|
-
}
|
14
|
-
|
15
|
-
async function resolveModules(root: string) {
|
16
|
-
|
17
|
-
if (!await doesExist(root)) {
|
18
|
-
throw new Error(`Directory ${root} does not exist`);
|
19
|
-
}
|
20
|
-
|
21
|
-
const tree = await createFileTree(root);
|
22
|
-
const files = tree.getFilesByExtension('.tsx');
|
23
|
-
const modules: ComponentType[] = [];
|
24
|
-
|
25
|
-
for (const file of files) {
|
26
|
-
const module = await import(file);
|
27
|
-
modules.push(module);
|
28
|
-
}
|
29
|
-
return modules;
|
30
|
-
}
|
31
|
-
|
32
|
-
async function doesExist(root: string): Promise<boolean> {
|
33
|
-
const file = Bun.file(root);
|
34
|
-
return await file.exists();
|
35
|
-
}
|
36
|
-
|
37
|
-
async function isDir(root: string): Promise<boolean> {
|
38
|
-
const file = Bun.file(root);
|
39
|
-
return await file.isDir();
|
40
|
-
}
|
41
|
-
|
42
|
-
|
43
|
-
export { resolveModules };
|
package/lib/util/strings.ts
DELETED