@senrijs/senri 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +377 -2
- package/dist/adapters/node/index.d.ts +14 -0
- package/dist/adapters/node/index.js +152 -0
- package/dist/adapters/serve/index.d.ts +28 -0
- package/dist/adapters/serve/index.js +137 -0
- package/dist/adapters/static/index.d.ts +9 -0
- package/dist/adapters/static/index.js +120 -0
- package/dist/src/app.d.ts +86 -0
- package/dist/src/app.js +325 -0
- package/dist/src/body.d.ts +3 -0
- package/dist/src/body.js +45 -0
- package/dist/src/compose.d.ts +22 -0
- package/dist/src/compose.js +80 -0
- package/dist/src/connection.d.ts +3 -0
- package/dist/src/connection.js +49 -0
- package/dist/src/context.d.ts +58 -0
- package/dist/src/context.js +235 -0
- package/dist/src/cookie.d.ts +18 -0
- package/dist/src/cookie.js +73 -0
- package/dist/src/dev/dashboard/access.d.ts +6 -0
- package/dist/src/dev/dashboard/access.js +46 -0
- package/dist/src/dev/dashboard/index.d.ts +27 -0
- package/dist/src/dev/dashboard/index.js +51 -0
- package/dist/src/dev/dashboard/links.d.ts +3 -0
- package/dist/src/dev/dashboard/links.js +8 -0
- package/dist/src/dev/dashboard/log.d.ts +20 -0
- package/dist/src/dev/dashboard/log.js +53 -0
- package/dist/src/dev/dashboard/mask.d.ts +4 -0
- package/dist/src/dev/dashboard/mask.js +43 -0
- package/dist/src/dev/dashboard/record.d.ts +37 -0
- package/dist/src/dev/dashboard/record.js +50 -0
- package/dist/src/dev/dashboard/routes.d.ts +7 -0
- package/dist/src/dev/dashboard/routes.js +121 -0
- package/dist/src/dev/dashboard/sse.d.ts +7 -0
- package/dist/src/dev/dashboard/sse.js +73 -0
- package/dist/src/dev/editor-url.d.ts +14 -0
- package/dist/src/dev/editor-url.js +61 -0
- package/dist/src/dev/editor.d.ts +6 -0
- package/dist/src/dev/editor.js +14 -0
- package/dist/src/dev/error-page.d.ts +3 -0
- package/dist/src/dev/error-page.js +69 -0
- package/dist/src/dev/explain.d.ts +2 -0
- package/dist/src/dev/explain.js +37 -0
- package/dist/src/dev/fallback.d.ts +3 -0
- package/dist/src/dev/fallback.js +40 -0
- package/dist/src/dev/hints.d.ts +6 -0
- package/dist/src/dev/hints.js +85 -0
- package/dist/src/dev/html.d.ts +1 -0
- package/dist/src/dev/html.js +14 -0
- package/dist/src/dev/index.d.ts +3 -0
- package/dist/src/dev/index.js +185 -0
- package/dist/src/dev/messages.d.ts +2 -0
- package/dist/src/dev/messages.js +6 -0
- package/dist/src/dev/not-found-page.d.ts +7 -0
- package/dist/src/dev/not-found-page.js +33 -0
- package/dist/src/dev/not-found.d.ts +3 -0
- package/dist/src/dev/not-found.js +15 -0
- package/dist/src/dev/page-data.d.ts +95 -0
- package/dist/src/dev/page-data.js +0 -0
- package/dist/src/dev/page.generated.d.ts +1 -0
- package/dist/src/dev/page.generated.js +3 -0
- package/dist/src/dev/pages.d.ts +4 -0
- package/dist/src/dev/pages.js +59 -0
- package/dist/src/dev/registry.d.ts +3 -0
- package/dist/src/dev/registry.js +7 -0
- package/dist/src/dev/render.d.ts +5 -0
- package/dist/src/dev/render.js +42 -0
- package/dist/src/dev/server-timing.d.ts +6 -0
- package/dist/src/dev/server-timing.js +45 -0
- package/dist/src/dev/source.d.ts +20 -0
- package/dist/src/dev/source.js +106 -0
- package/dist/src/dev/stack.d.ts +12 -0
- package/dist/src/dev/stack.js +93 -0
- package/dist/src/dev/suggest.d.ts +12 -0
- package/dist/src/dev/suggest.js +95 -0
- package/dist/src/dev/validation.d.ts +3 -0
- package/dist/src/dev/validation.js +47 -0
- package/dist/src/errors.d.ts +25 -0
- package/dist/src/errors.js +97 -0
- package/dist/src/group.d.ts +37 -0
- package/dist/src/group.js +75 -0
- package/dist/src/health.d.ts +9 -0
- package/dist/src/health.js +40 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +12 -0
- package/dist/src/ip.d.ts +2 -0
- package/dist/src/ip.js +12 -0
- package/dist/src/logger.d.ts +18 -0
- package/dist/src/logger.js +110 -0
- package/dist/src/matcher.d.ts +16 -0
- package/dist/src/matcher.js +86 -0
- package/dist/src/middleware/body-limit.d.ts +6 -0
- package/dist/src/middleware/body-limit.js +47 -0
- package/dist/src/middleware/cors.d.ts +10 -0
- package/dist/src/middleware/cors.js +79 -0
- package/dist/src/middleware/response.d.ts +2 -0
- package/dist/src/middleware/response.js +28 -0
- package/dist/src/middleware/secure-headers.d.ts +19 -0
- package/dist/src/middleware/secure-headers.js +46 -0
- package/dist/src/router.d.ts +30 -0
- package/dist/src/router.js +177 -0
- package/dist/src/runtime.d.ts +5 -0
- package/dist/src/runtime.js +55 -0
- package/dist/src/validate.d.ts +46 -0
- package/dist/src/validate.js +55 -0
- package/package.json +90 -4
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { showRoutes } from "../../src/index.js";
|
|
2
|
+
import { setDraining } from "../../src/health.js";
|
|
3
|
+
import * as runtime from "../../src/runtime.js";
|
|
4
|
+
// よく使われる 3000 や 8080 とぶつかりにくい番号にしている
|
|
5
|
+
export const DEFAULT_PORT = 3927;
|
|
6
|
+
function isPort(n) {
|
|
7
|
+
return Number.isInteger(n) && n >= 0 && n <= 65535;
|
|
8
|
+
}
|
|
9
|
+
// 使うポートを決める: 明示した port → 環境変数 PORT → DEFAULT_PORT
|
|
10
|
+
// 環境変数は runtime.env で読むので、Deno で --allow-env がなければ聞き返さずにデフォルトになる
|
|
11
|
+
export function resolvePort(explicit, env = runtime.env) {
|
|
12
|
+
if (explicit !== undefined) {
|
|
13
|
+
if (!isPort(explicit))
|
|
14
|
+
throw new RangeError(`port が正しいポート番号(0〜65535)ではありません: ${explicit}`);
|
|
15
|
+
return { port: explicit, source: "option" };
|
|
16
|
+
}
|
|
17
|
+
const raw = env("PORT")?.trim();
|
|
18
|
+
if (raw) {
|
|
19
|
+
// 黙ってデフォルトで待ち受けると、デプロイ先で「つながらない」原因が分かりにくいので、エラーにする
|
|
20
|
+
const port = /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
21
|
+
if (!isPort(port))
|
|
22
|
+
throw new RangeError(`環境変数 PORT が正しいポート番号(0〜65535)ではありません: ${raw}`);
|
|
23
|
+
return { port, source: "env" };
|
|
24
|
+
}
|
|
25
|
+
return { port: DEFAULT_PORT, source: "default" };
|
|
26
|
+
}
|
|
27
|
+
const SOURCE_NOTE = {
|
|
28
|
+
option: "port オプション",
|
|
29
|
+
env: "環境変数 PORT",
|
|
30
|
+
default: "デフォルト。環境変数 PORT で変えられます",
|
|
31
|
+
};
|
|
32
|
+
const SIGNALS = ["SIGTERM", "SIGINT"];
|
|
33
|
+
// 上限つきで待つ。時間内に終われば true
|
|
34
|
+
function within(promise, ms) {
|
|
35
|
+
let timer;
|
|
36
|
+
const timeout = new Promise((resolve) => (timer = setTimeout(() => resolve(false), ms)));
|
|
37
|
+
return Promise.race([promise.then(() => true), timeout]).finally(() => clearTimeout(timer));
|
|
38
|
+
}
|
|
39
|
+
export async function serve(app, options = {}) {
|
|
40
|
+
const { port, source } = resolvePort(options.port);
|
|
41
|
+
const hostname = options.hostname;
|
|
42
|
+
const g = globalThis;
|
|
43
|
+
const timeout = options.shutdownTimeout ?? 10_000;
|
|
44
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
45
|
+
throw new RangeError(`shutdownTimeout には 0 以上のミリ秒を指定してください: ${timeout}`);
|
|
46
|
+
}
|
|
47
|
+
// 実際に待ち受けたポート(port: 0 なら、ランタイムが選んだ番号)と、止め方
|
|
48
|
+
// - drain: 新しい接続を受けるのをやめ、処理中のものが終わったら resolve
|
|
49
|
+
// - force: 残っている接続を切る(drain が時間内に終わらなかったとき)
|
|
50
|
+
let actualPort;
|
|
51
|
+
let drain;
|
|
52
|
+
let force;
|
|
53
|
+
if (g.Bun) {
|
|
54
|
+
const s = g.Bun.serve({ port, hostname, fetch: app.fetch });
|
|
55
|
+
actualPort = s.port;
|
|
56
|
+
drain = async () => s.stop(false);
|
|
57
|
+
force = () => void s.stop(true);
|
|
58
|
+
}
|
|
59
|
+
else if (g.Deno) {
|
|
60
|
+
// onListen を空にして、Deno が勝手に出す "Listening on ..." を消す(バナーと二重になるので)
|
|
61
|
+
const s = g.Deno.serve({ port, hostname, onListen() { } }, app.fetch);
|
|
62
|
+
actualPort = s.addr.port;
|
|
63
|
+
drain = () => s.shutdown();
|
|
64
|
+
// Deno には、shutdown() のあとに残りの接続を切る方法がない(signal で abort すると Deno の中で例外になる)
|
|
65
|
+
// なので待つのをやめるだけ。シグナルで止めたときは、このあとプロセスごと終わるので接続も切れる
|
|
66
|
+
force = () => { };
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Node のときだけ node:http を読み込む
|
|
70
|
+
const node = await import("../node/index.js");
|
|
71
|
+
const s = node.serve(app, { port });
|
|
72
|
+
// Node は待ち受けが非同期。使用中のポートなどは error イベントで来るので、ここで reject にする
|
|
73
|
+
// (受け取らないとプロセスごと落ちる)
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
s.once("listening", resolve);
|
|
76
|
+
s.once("error", reject);
|
|
77
|
+
});
|
|
78
|
+
const address = s.address();
|
|
79
|
+
actualPort = typeof address === "object" && address ? address.port : port;
|
|
80
|
+
drain = () => new Promise((resolve) => {
|
|
81
|
+
s.close(() => resolve());
|
|
82
|
+
// close() は、使っていない keep-alive の接続が切れるまで待ってしまうので、それは先に切る
|
|
83
|
+
s.closeIdleConnections();
|
|
84
|
+
});
|
|
85
|
+
force = () => s.closeAllConnections();
|
|
86
|
+
}
|
|
87
|
+
let stopping = null;
|
|
88
|
+
// シグナルのリスナーを外すもの(止めたあとまで残さない)
|
|
89
|
+
const detach = [];
|
|
90
|
+
const stop = () => {
|
|
91
|
+
stopping ??= (async () => {
|
|
92
|
+
for (const off of detach)
|
|
93
|
+
off();
|
|
94
|
+
// まず /readyz を 503 にする(ロードバランサーが、新しいリクエストを送ってこないように)
|
|
95
|
+
setDraining(true);
|
|
96
|
+
if (!(await within(drain(), timeout)))
|
|
97
|
+
force();
|
|
98
|
+
})();
|
|
99
|
+
return stopping;
|
|
100
|
+
};
|
|
101
|
+
// シグナルを受けたら、止めてから終わる。2回目を受けたら待たずに終わる
|
|
102
|
+
if (options.gracefulShutdown ?? true) {
|
|
103
|
+
const proc = globalThis.process;
|
|
104
|
+
const exit = (code) => (g.Deno ? g.Deno.exit(code) : proc?.exit(code));
|
|
105
|
+
const onSignal = () => {
|
|
106
|
+
if (stopping)
|
|
107
|
+
return exit(1);
|
|
108
|
+
console.log("\n止めています(処理中のリクエストが終わるのを待ちます)…");
|
|
109
|
+
stop()
|
|
110
|
+
.then(() => options.onShutdown?.())
|
|
111
|
+
.then(() => exit(0), (err) => {
|
|
112
|
+
console.error(err);
|
|
113
|
+
exit(1);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
for (const signal of SIGNALS) {
|
|
117
|
+
try {
|
|
118
|
+
if (g.Deno) {
|
|
119
|
+
g.Deno.addSignalListener(signal, onSignal);
|
|
120
|
+
detach.push(() => g.Deno.removeSignalListener(signal, onSignal));
|
|
121
|
+
}
|
|
122
|
+
else if (proc) {
|
|
123
|
+
proc.on(signal, onSignal);
|
|
124
|
+
detach.push(() => proc.off(signal, onSignal));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Windows の Deno は SIGTERM を受けられない(SIGINT だけ使う)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const url = `http://${hostname ?? "localhost"}:${actualPort}`;
|
|
133
|
+
if (options.banner ?? true) {
|
|
134
|
+
showRoutes(app, url, { note: SOURCE_NOTE[source] });
|
|
135
|
+
}
|
|
136
|
+
return { port: actualPort, url, stop };
|
|
137
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Middleware } from "../../src/index.ts";
|
|
2
|
+
export type ServeStaticOptions = {
|
|
3
|
+
root: string;
|
|
4
|
+
prefix?: string;
|
|
5
|
+
index?: string | false;
|
|
6
|
+
dotfiles?: boolean;
|
|
7
|
+
cacheControl?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function serveStatic(options: ServeStaticOptions): Middleware;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// 静的ファイル配信(senri/static)。node:fs を使うので本体とは別の入口にしている
|
|
2
|
+
// Bun / Deno / Node で動く。Cloudflare Workers ではファイルを読めないので、wrangler の Static Assets を使う
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
import { realpath, stat } from "node:fs/promises";
|
|
5
|
+
import { extname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
const MIME = {
|
|
8
|
+
".html": "text/html; charset=utf-8",
|
|
9
|
+
".htm": "text/html; charset=utf-8",
|
|
10
|
+
".css": "text/css; charset=utf-8",
|
|
11
|
+
".js": "text/javascript; charset=utf-8",
|
|
12
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
13
|
+
".json": "application/json",
|
|
14
|
+
".map": "application/json",
|
|
15
|
+
".txt": "text/plain; charset=utf-8",
|
|
16
|
+
".md": "text/markdown; charset=utf-8",
|
|
17
|
+
".csv": "text/csv; charset=utf-8",
|
|
18
|
+
".xml": "application/xml",
|
|
19
|
+
".svg": "image/svg+xml",
|
|
20
|
+
".png": "image/png",
|
|
21
|
+
".jpg": "image/jpeg",
|
|
22
|
+
".jpeg": "image/jpeg",
|
|
23
|
+
".gif": "image/gif",
|
|
24
|
+
".webp": "image/webp",
|
|
25
|
+
".avif": "image/avif",
|
|
26
|
+
".ico": "image/x-icon",
|
|
27
|
+
".woff": "font/woff",
|
|
28
|
+
".woff2": "font/woff2",
|
|
29
|
+
".ttf": "font/ttf",
|
|
30
|
+
".otf": "font/otf",
|
|
31
|
+
".wasm": "application/wasm",
|
|
32
|
+
".pdf": "application/pdf",
|
|
33
|
+
".mp4": "video/mp4",
|
|
34
|
+
".webm": "video/webm",
|
|
35
|
+
".mp3": "audio/mpeg",
|
|
36
|
+
".wav": "audio/wav",
|
|
37
|
+
".ogg": "audio/ogg",
|
|
38
|
+
".zip": "application/zip",
|
|
39
|
+
".webmanifest": "application/manifest+json",
|
|
40
|
+
};
|
|
41
|
+
function isWorkers() {
|
|
42
|
+
return globalThis.navigator?.userAgent === "Cloudflare-Workers";
|
|
43
|
+
}
|
|
44
|
+
// If-None-Match に、この ETag が入っているか(W/ は比べない = 弱い比較)
|
|
45
|
+
function matchesEtag(header, etag) {
|
|
46
|
+
const bare = etag.replace(/^W\//, "");
|
|
47
|
+
return header.split(",").some((t) => {
|
|
48
|
+
const tag = t.trim();
|
|
49
|
+
return tag === "*" || tag.replace(/^W\//, "") === bare;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// ファイルの場所として使ってはいけないセグメント
|
|
53
|
+
// (ルーターが "." / ".." / %2F は 400 にしている。ここでは Windows の区切りやドライブ、NUL を防ぐ)
|
|
54
|
+
function unsafeSegment(segment) {
|
|
55
|
+
return segment.includes("\\") || segment.includes(":") || segment.includes("\0");
|
|
56
|
+
}
|
|
57
|
+
export function serveStatic(options) {
|
|
58
|
+
if (isWorkers()) {
|
|
59
|
+
throw new Error("serveStatic() は Cloudflare Workers では使えません(ファイルを読めないため)。\n" +
|
|
60
|
+
"wrangler.jsonc の assets(Static Assets)で配ってください: https://developers.cloudflare.com/workers/static-assets/");
|
|
61
|
+
}
|
|
62
|
+
const root = resolve(options.root);
|
|
63
|
+
const prefix = (options.prefix ?? "/").split("/").filter(Boolean);
|
|
64
|
+
const index = options.index ?? "index.html";
|
|
65
|
+
// シンボリックリンクで外に出ていないかを、実体のパスで確かめる(root 自体がリンクでもよいように、root も実体にする)
|
|
66
|
+
const realRoot = realpath(root).catch(() => root);
|
|
67
|
+
return async function serveStatic(c, next) {
|
|
68
|
+
const method = c.req.method;
|
|
69
|
+
if (method !== "GET" && method !== "HEAD")
|
|
70
|
+
return next();
|
|
71
|
+
const segments = c.segments;
|
|
72
|
+
if (segments.length < prefix.length || prefix.some((p, i) => p !== segments[i]))
|
|
73
|
+
return next();
|
|
74
|
+
const rest = segments.slice(prefix.length);
|
|
75
|
+
if (rest.some(unsafeSegment))
|
|
76
|
+
return next();
|
|
77
|
+
if (!options.dotfiles && rest.some((s) => s.startsWith(".")))
|
|
78
|
+
return next();
|
|
79
|
+
let file = join(root, ...rest);
|
|
80
|
+
// 念のため、つないだ結果が root の外になっていないか
|
|
81
|
+
const rel = relative(root, file);
|
|
82
|
+
if (rel.startsWith("..") || resolve(root, rel) !== file)
|
|
83
|
+
return next();
|
|
84
|
+
let info = await stat(file).catch(() => null);
|
|
85
|
+
if (info?.isDirectory()) {
|
|
86
|
+
if (index === false)
|
|
87
|
+
return next();
|
|
88
|
+
file = join(file, index);
|
|
89
|
+
info = await stat(file).catch(() => null);
|
|
90
|
+
}
|
|
91
|
+
if (!info?.isFile())
|
|
92
|
+
return next();
|
|
93
|
+
const real = await realpath(file).catch(() => null);
|
|
94
|
+
const base = await realRoot;
|
|
95
|
+
if (real === null || (real !== base && !real.startsWith(base.endsWith(sep) ? base : base + sep)))
|
|
96
|
+
return next();
|
|
97
|
+
// root の中のシンボリックリンクが .env などを指していることもあるので、実体のパスでもドットファイルを確かめる
|
|
98
|
+
if (!options.dotfiles && relative(base, real).split(sep).some((s) => s.startsWith(".")))
|
|
99
|
+
return next();
|
|
100
|
+
const etag = `W/"${info.size.toString(16)}-${Math.floor(info.mtimeMs).toString(16)}"`;
|
|
101
|
+
const lastModified = info.mtime.toUTCString();
|
|
102
|
+
const headers = new Headers({ etag, "last-modified": lastModified });
|
|
103
|
+
if (options.cacheControl)
|
|
104
|
+
headers.set("cache-control", options.cacheControl);
|
|
105
|
+
// 変わっていなければ 304(If-None-Match があれば、If-Modified-Since より優先する)
|
|
106
|
+
const ifNoneMatch = c.req.headers.get("if-none-match");
|
|
107
|
+
const ifModifiedSince = c.req.headers.get("if-modified-since");
|
|
108
|
+
const notModified = ifNoneMatch !== null
|
|
109
|
+
? matchesEtag(ifNoneMatch, etag)
|
|
110
|
+
: ifModifiedSince !== null && Math.floor(info.mtimeMs / 1000) <= Date.parse(ifModifiedSince) / 1000;
|
|
111
|
+
if (notModified)
|
|
112
|
+
return new Response(null, { status: 304, headers });
|
|
113
|
+
headers.set("content-type", MIME[extname(file).toLowerCase()] ?? "application/octet-stream");
|
|
114
|
+
headers.set("content-length", String(info.size));
|
|
115
|
+
if (method === "HEAD")
|
|
116
|
+
return new Response(null, { status: 200, headers });
|
|
117
|
+
const body = Readable.toWeb(createReadStream(file));
|
|
118
|
+
return new Response(body, { status: 200, headers });
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Context, type ExecutionContext, type Handler, type Middleware } from "./context.ts";
|
|
2
|
+
import { type ExtractParams, type RouteTable, type Route, type Simplify } from "./router.ts";
|
|
3
|
+
import { type Layer } from "./compose.ts";
|
|
4
|
+
import { type RouteSchemas, type Validated, type ValidatedParams } from "./validate.ts";
|
|
5
|
+
import { type Router } from "./group.ts";
|
|
6
|
+
import { type ErrorHandler, type NotFoundHandler } from "./errors.ts";
|
|
7
|
+
export type Resolved = {
|
|
8
|
+
kind: "route";
|
|
9
|
+
route: Route;
|
|
10
|
+
params: Record<string, string>;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "options";
|
|
13
|
+
allow: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "methodNotAllowed";
|
|
16
|
+
allow: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "notFound";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "notImplemented";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "badRequest";
|
|
23
|
+
error: unknown;
|
|
24
|
+
};
|
|
25
|
+
export type RouteMethod<V extends object> = {
|
|
26
|
+
<Path extends string>(path: Path, handler: Handler<Simplify<ExtractParams<Path>>, V>): void;
|
|
27
|
+
<Path extends string, S extends RouteSchemas>(path: Path, schemas: S, handler: Handler<ValidatedParams<S, Simplify<ExtractParams<Path>>>, V, Validated<S>>): void;
|
|
28
|
+
};
|
|
29
|
+
export type OnMethod<V extends object> = {
|
|
30
|
+
<Path extends string>(method: string | readonly string[], path: Path, handler: Handler<Simplify<ExtractParams<Path>>, V>): void;
|
|
31
|
+
<Path extends string, S extends RouteSchemas>(method: string | readonly string[], path: Path, schemas: S, handler: Handler<ValidatedParams<S, Simplify<ExtractParams<Path>>>, V, Validated<S>>): void;
|
|
32
|
+
};
|
|
33
|
+
export type MountableApp = {
|
|
34
|
+
routeList(): {
|
|
35
|
+
method: string;
|
|
36
|
+
path: string;
|
|
37
|
+
}[];
|
|
38
|
+
};
|
|
39
|
+
export type App<V extends object = Record<string, unknown>> = {
|
|
40
|
+
use(pathOrMw: string | Middleware<V>, mw?: Middleware<V>): void;
|
|
41
|
+
onError(handler: ErrorHandler<V>): void;
|
|
42
|
+
notFound(handler: NotFoundHandler<V>): void;
|
|
43
|
+
get: RouteMethod<V>;
|
|
44
|
+
post: RouteMethod<V>;
|
|
45
|
+
put: RouteMethod<V>;
|
|
46
|
+
patch: RouteMethod<V>;
|
|
47
|
+
delete: RouteMethod<V>;
|
|
48
|
+
head: RouteMethod<V>;
|
|
49
|
+
options: RouteMethod<V>;
|
|
50
|
+
all: RouteMethod<V>;
|
|
51
|
+
on: OnMethod<V>;
|
|
52
|
+
routes<K extends string>(table: RouteTable<K, V>): void;
|
|
53
|
+
route(routerOrPrefix: Router<string, V> | string, child?: MountableApp): void;
|
|
54
|
+
middlewareList(): {
|
|
55
|
+
name: string;
|
|
56
|
+
path: string | null;
|
|
57
|
+
anonymous: boolean;
|
|
58
|
+
}[];
|
|
59
|
+
routeList(): {
|
|
60
|
+
method: string;
|
|
61
|
+
path: string;
|
|
62
|
+
}[];
|
|
63
|
+
fetch(req: Request, info?: unknown, ctx?: ExecutionContext): Promise<Response>;
|
|
64
|
+
};
|
|
65
|
+
export type AppOptions = {
|
|
66
|
+
dev?: DevHooks | undefined;
|
|
67
|
+
};
|
|
68
|
+
export type DevHooks = {
|
|
69
|
+
install(core: AppCore): DevInstalled;
|
|
70
|
+
};
|
|
71
|
+
export type AppCore = {
|
|
72
|
+
app: App<any>;
|
|
73
|
+
resolve(c: Context): Resolved;
|
|
74
|
+
layers(c: Context): Layer[];
|
|
75
|
+
inner(c: Context, resolved: Resolved): Response | Promise<Response>;
|
|
76
|
+
fail(err: unknown, c: Context): Promise<Response>;
|
|
77
|
+
routes(): readonly Route[];
|
|
78
|
+
};
|
|
79
|
+
export type DevInstalled = {
|
|
80
|
+
fetch(req: Request, info?: unknown, ctx?: ExecutionContext): Promise<Response>;
|
|
81
|
+
onError: ErrorHandler;
|
|
82
|
+
notFound: NotFoundHandler;
|
|
83
|
+
checkRoute(method: string, path: string, schemas: RouteSchemas | undefined): void;
|
|
84
|
+
explain(err: unknown): unknown;
|
|
85
|
+
};
|
|
86
|
+
export declare function createApp<V extends object = Record<string, unknown>>(options?: AppOptions): App<V>;
|