hanni 1.0.6 → 1.0.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/package.json +1 -1
- package/src/hanni.js +44 -40
- package/src/middleware.js +41 -10
package/package.json
CHANGED
package/src/hanni.js
CHANGED
|
@@ -1,83 +1,87 @@
|
|
|
1
|
-
import { Router as _Router } from './router.js'
|
|
2
|
-
import { createContext } from './context.js'
|
|
3
|
-
import { buildSpec, swaggerHTML } from './swagger.js'
|
|
4
|
-
import { compose } from './middleware.js'
|
|
5
|
-
import { parseBody, corsHeaders } from './utils.js'
|
|
1
|
+
import { Router as _Router } from './router.js';
|
|
2
|
+
import { createContext } from './context.js';
|
|
3
|
+
import { buildSpec, swaggerHTML } from './swagger.js';
|
|
4
|
+
import { compose, Static as _Static } from './middleware.js';
|
|
5
|
+
import { parseBody, corsHeaders } from './utils.js';
|
|
6
6
|
|
|
7
|
-
export { _Router as Router }
|
|
7
|
+
export { _Router as Router, _Static as Static };
|
|
8
8
|
|
|
9
9
|
export function Hanni(config = {}) {
|
|
10
|
-
const router = new _Router()
|
|
11
|
-
const middlewares = []
|
|
12
|
-
const mounts = []
|
|
13
|
-
const swaggerCfg = config.swagger
|
|
10
|
+
const router = new _Router();
|
|
11
|
+
const middlewares = [];
|
|
12
|
+
const mounts = [];
|
|
13
|
+
const swaggerCfg = config.swagger;
|
|
14
14
|
|
|
15
|
-
const app = {}
|
|
15
|
+
const app = {};
|
|
16
16
|
|
|
17
|
-
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'ALL']
|
|
17
|
+
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'ALL'];
|
|
18
18
|
for (const m of methods) {
|
|
19
19
|
app[m.toLowerCase()] = (path, handler, meta) => {
|
|
20
|
-
router.add(m, path, handler, meta)
|
|
21
|
-
return app
|
|
22
|
-
}
|
|
20
|
+
router.add(m, path, handler, meta);
|
|
21
|
+
return app;
|
|
22
|
+
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
app.use = (arg1, arg2) => {
|
|
26
26
|
if (typeof arg1 === 'string' && arg2 && arg2.list) {
|
|
27
|
-
mounts.push({ path: arg1, router: arg2 })
|
|
27
|
+
mounts.push({ path: arg1, router: arg2 });
|
|
28
28
|
} else if (typeof arg1 === 'function') {
|
|
29
|
-
middlewares.push(arg1)
|
|
29
|
+
middlewares.push(arg1);
|
|
30
30
|
}
|
|
31
|
-
return app
|
|
32
|
-
}
|
|
31
|
+
return app;
|
|
32
|
+
};
|
|
33
33
|
|
|
34
|
-
app.listen = port => {
|
|
34
|
+
app.listen = (port) => {
|
|
35
35
|
Bun.serve({
|
|
36
36
|
port,
|
|
37
37
|
async fetch(req) {
|
|
38
|
-
const url = new URL(req.url)
|
|
38
|
+
const url = new URL(req.url);
|
|
39
39
|
|
|
40
40
|
if (swaggerCfg) {
|
|
41
|
-
const base = swaggerCfg.path
|
|
41
|
+
const base = swaggerCfg.path;
|
|
42
42
|
if (url.pathname === base || url.pathname === base + '/') {
|
|
43
|
-
return new Response(swaggerHTML(base + '/json', swaggerCfg), { headers: { 'Content-Type': 'text/html' } })
|
|
43
|
+
return new Response(swaggerHTML(base + '/json', swaggerCfg), { headers: { 'Content-Type': 'text/html' } });
|
|
44
44
|
}
|
|
45
45
|
if (url.pathname === base + '/json') {
|
|
46
46
|
const allRoutes = [
|
|
47
47
|
...router.list(),
|
|
48
48
|
...mounts.flatMap(mount => mount.router.list().map(r => ({ ...r, path: mount.path + r.path })))
|
|
49
|
-
]
|
|
50
|
-
return Response.json(buildSpec(allRoutes, swaggerCfg))
|
|
49
|
+
];
|
|
50
|
+
return Response.json(buildSpec(allRoutes, swaggerCfg));
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
if (config.cors && req.method === 'OPTIONS')
|
|
54
|
+
if (config.cors && req.method === 'OPTIONS') {
|
|
55
|
+
return new Response(null, { headers: corsHeaders() });
|
|
56
|
+
}
|
|
55
57
|
|
|
56
|
-
let match = router.match(req.method, url.pathname)
|
|
58
|
+
let match = router.match(req.method, url.pathname);
|
|
57
59
|
if (!match) {
|
|
58
60
|
for (const mount of mounts) {
|
|
59
61
|
if (url.pathname.startsWith(mount.path)) {
|
|
60
|
-
const subPath = url.pathname.slice(mount.path.length) || '/'
|
|
61
|
-
match = mount.router.match(req.method, subPath)
|
|
62
|
-
if (match) break
|
|
62
|
+
const subPath = url.pathname.slice(mount.path.length) || '/';
|
|
63
|
+
match = mount.router.match(req.method, subPath);
|
|
64
|
+
if (match) break;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
if (!match) return new Response('Not Found', { status: 404 })
|
|
69
|
+
if (!match) return new Response('Not Found', { status: 404 });
|
|
68
70
|
|
|
69
|
-
const ctx = createContext(req, match.params)
|
|
70
|
-
ctx.body = await parseBody(req)
|
|
71
|
+
const ctx = createContext(req, match.params);
|
|
72
|
+
ctx.body = await parseBody(req);
|
|
71
73
|
|
|
72
|
-
const handler = compose(middlewares, match.handler)
|
|
73
|
-
const res = await handler(ctx)
|
|
74
|
+
const handler = compose(middlewares, match.handler);
|
|
75
|
+
const res = await handler(ctx);
|
|
74
76
|
|
|
75
|
-
if (config.cors)
|
|
77
|
+
if (config.cors) {
|
|
78
|
+
Object.entries(corsHeaders()).forEach(([k, v]) => res.headers.set(k, v));
|
|
79
|
+
}
|
|
76
80
|
|
|
77
|
-
return res
|
|
81
|
+
return res;
|
|
78
82
|
}
|
|
79
|
-
})
|
|
80
|
-
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
81
85
|
|
|
82
|
-
return app
|
|
86
|
+
return app;
|
|
83
87
|
}
|
package/src/middleware.js
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
export function Static({ path: urlPath, folder }) {
|
|
5
|
+
return async function (ctx, next) {
|
|
6
|
+
const reqPath = ctx.req.url;
|
|
7
|
+
if (!reqPath.startsWith(urlPath)) return next();
|
|
8
|
+
|
|
9
|
+
const filePath = join(folder, reqPath.slice(urlPath.length));
|
|
10
|
+
if (!existsSync(filePath)) {
|
|
11
|
+
ctx.res.status = 404;
|
|
12
|
+
ctx.res.body = "File not found";
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const file = Bun.file(filePath);
|
|
17
|
+
ctx.res.body = file;
|
|
18
|
+
ctx.res.headers.set("Content-Type", getContentType(filePath));
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getContentType(filePath) {
|
|
23
|
+
if (filePath.endsWith(".html")) return "text/html";
|
|
24
|
+
if (filePath.endsWith(".css")) return "text/css";
|
|
25
|
+
if (filePath.endsWith(".js")) return "application/javascript";
|
|
26
|
+
if (filePath.endsWith(".json")) return "application/json";
|
|
27
|
+
if (filePath.endsWith(".png")) return "image/png";
|
|
28
|
+
if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg";
|
|
29
|
+
return "text/plain";
|
|
30
|
+
}
|
|
31
|
+
|
|
1
32
|
export function compose(middlewares, handler) {
|
|
2
33
|
return function (ctx) {
|
|
3
|
-
let i = -1
|
|
4
|
-
const dispatch = idx => {
|
|
5
|
-
if (idx <= i) return Promise.reject()
|
|
6
|
-
i = idx
|
|
7
|
-
const fn = idx === middlewares.length ? handler : middlewares[idx]
|
|
8
|
-
if (!fn) return Promise.resolve()
|
|
9
|
-
return Promise.resolve(fn(ctx, () => dispatch(idx + 1)))
|
|
10
|
-
}
|
|
11
|
-
return dispatch(0)
|
|
12
|
-
}
|
|
34
|
+
let i = -1;
|
|
35
|
+
const dispatch = (idx) => {
|
|
36
|
+
if (idx <= i) return Promise.reject();
|
|
37
|
+
i = idx;
|
|
38
|
+
const fn = idx === middlewares.length ? handler : middlewares[idx];
|
|
39
|
+
if (!fn) return Promise.resolve();
|
|
40
|
+
return Promise.resolve(fn(ctx, () => dispatch(idx + 1)));
|
|
41
|
+
};
|
|
42
|
+
return dispatch(0);
|
|
43
|
+
};
|
|
13
44
|
}
|