sevok 0.0.2
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 +342 -0
- package/README.zh-CN.md +342 -0
- package/bin/sevok.mjs +13 -0
- package/dist/_color-B42q-MpL.mjs +34 -0
- package/dist/_node_like-BRaAGeNM.mjs +112 -0
- package/dist/_shared-38k_JIsU.mjs +92 -0
- package/dist/bun.d.mts +31 -0
- package/dist/bun.mjs +58 -0
- package/dist/cli.d.mts +142 -0
- package/dist/cli.mjs +461 -0
- package/dist/core-BGp2ZR_k.d.mts +665 -0
- package/dist/core-CmUugTW7.mjs +732 -0
- package/dist/deno.d.mts +32 -0
- package/dist/deno.mjs +65 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/log.d.mts +27 -0
- package/dist/log.mjs +26 -0
- package/dist/node.d.mts +32 -0
- package/dist/node.mjs +139 -0
- package/dist/static.d.mts +35 -0
- package/dist/static.mjs +100 -0
- package/dist/stream.d.mts +56 -0
- package/dist/stream.mjs +68 -0
- package/package.json +68 -0
package/dist/deno.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { d as RuntimeAdapter, f as RuntimeCapabilities, p as Server } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/deno.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime adapter that backs `Server` with `Deno.serve()`.
|
|
6
|
+
*/
|
|
7
|
+
declare class DenoRuntimeAdapter implements RuntimeAdapter {
|
|
8
|
+
#private;
|
|
9
|
+
readonly graceful = true;
|
|
10
|
+
get capabilities(): RuntimeCapabilities;
|
|
11
|
+
/**
|
|
12
|
+
* Translate generic server options into Deno's `serve` options.
|
|
13
|
+
*/
|
|
14
|
+
setup(server: Server): void;
|
|
15
|
+
/**
|
|
16
|
+
* Start Deno's HTTP server and resolve only after the runtime reports the
|
|
17
|
+
* bound address via `onListen`.
|
|
18
|
+
*/
|
|
19
|
+
serve(server: Server): Promise<{
|
|
20
|
+
url: string | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Derive the public URL from the last `onListen` callback.
|
|
24
|
+
*/
|
|
25
|
+
get url(): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Gracefully shut down the Deno server, if one is active.
|
|
28
|
+
*/
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { DenoRuntimeAdapter };
|
package/dist/deno.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { n as resolveTLSOptions, r as runtimeCapabilities, t as printListening } from "./_node_like-BRaAGeNM.mjs";
|
|
2
|
+
import { a as fmtURL, s as resolvePortAndHost } from "./_shared-38k_JIsU.mjs";
|
|
3
|
+
//#region src/deno.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime adapter that backs `Server` with `Deno.serve()`.
|
|
6
|
+
*/
|
|
7
|
+
var DenoRuntimeAdapter = class {
|
|
8
|
+
#server;
|
|
9
|
+
#listeningPromise;
|
|
10
|
+
#listeningInfo;
|
|
11
|
+
#serveOptions;
|
|
12
|
+
graceful = true;
|
|
13
|
+
get capabilities() {
|
|
14
|
+
return runtimeCapabilities;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Translate generic server options into Deno's `serve` options.
|
|
18
|
+
*/
|
|
19
|
+
setup(server) {
|
|
20
|
+
const { options } = server;
|
|
21
|
+
const tls = resolveTLSOptions(options);
|
|
22
|
+
this.#serveOptions = {
|
|
23
|
+
...resolvePortAndHost(options),
|
|
24
|
+
reusePort: options.reusePort,
|
|
25
|
+
onError: options.error,
|
|
26
|
+
...tls,
|
|
27
|
+
...options.deno
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start Deno's HTTP server and resolve only after the runtime reports the
|
|
32
|
+
* bound address via `onListen`.
|
|
33
|
+
*/
|
|
34
|
+
async serve(server) {
|
|
35
|
+
if (this.#server) return Promise.resolve(this.#listeningPromise).then(() => ({ url: this.url }));
|
|
36
|
+
const onListenPromise = Promise.withResolvers();
|
|
37
|
+
this.#listeningPromise = onListenPromise.promise;
|
|
38
|
+
this.#server = Deno.serve({
|
|
39
|
+
...this.#serveOptions,
|
|
40
|
+
onListen: (info) => {
|
|
41
|
+
this.#listeningInfo = info;
|
|
42
|
+
if (this.#serveOptions?.onListen) this.#serveOptions.onListen(info);
|
|
43
|
+
printListening(server.options, this.url);
|
|
44
|
+
onListenPromise.resolve();
|
|
45
|
+
}
|
|
46
|
+
}, (request) => server.fetch(request));
|
|
47
|
+
return Promise.resolve(this.#listeningPromise).then(() => ({ url: this.url }));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Derive the public URL from the last `onListen` callback.
|
|
51
|
+
*/
|
|
52
|
+
get url() {
|
|
53
|
+
return this.#listeningInfo ? fmtURL(this.#listeningInfo.hostname, this.#listeningInfo.port, !!this.#serveOptions.cert) : void 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gracefully shut down the Deno server, if one is active.
|
|
57
|
+
*/
|
|
58
|
+
async close() {
|
|
59
|
+
return Promise.resolve(this.#server?.shutdown()).then(() => {
|
|
60
|
+
this.#server = void 0;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { DenoRuntimeAdapter };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as WaitUntil, B as unstable_buildRouteTree, C as ServerPlugin, D as UnstableConvertRoutesToHandlerOptions, E as TLSOptions, F as loadServerAdapter, H as unstable_match, I as raceRequestAbort, L as runMiddleware, M as createContextKey, N as createWaitUntil, O as UnstableRouteMatch, P as isServerHandlerObject, R as serve, S as ServerOptions, T as ServerServeEvent, U as wrapFetch, V as unstable_convertRoutesToHandler, _ as ServerEventMapCustom, a as InvocationContext, b as ServerInit, c as InvocationContextValue, d as RuntimeAdapter, f as RuntimeCapabilities, g as ServerEventMap, h as ServerErrorEvent, i as HTTPMethod, j as WaitUntilFunction, k as UnstableRouteMatchResult, l as MaybePromise, m as ServerCloseEvent, n as DenoServerOptions, o as InvocationContextInit, p as Server, r as ErrorHandler, s as InvocationContextKey, t as BunServerOptions, u as NodeServerOptions, v as ServerHandler, w as ServerRoutes, x as ServerMiddleware, y as ServerHandlerObject, z as toServerHandlerObject } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
export { BunServerOptions, DenoServerOptions, ErrorHandler, HTTPMethod, InvocationContext, InvocationContextInit, InvocationContextKey, InvocationContextValue, MaybePromise, NodeServerOptions, RuntimeAdapter, RuntimeCapabilities, Server, ServerCloseEvent, ServerErrorEvent, ServerEventMap, ServerEventMapCustom, ServerHandler, ServerHandlerObject, ServerInit, ServerMiddleware, ServerOptions, ServerPlugin, ServerRoutes, ServerServeEvent, TLSOptions, UnstableConvertRoutesToHandlerOptions, UnstableRouteMatch, UnstableRouteMatchResult, WaitUntil, WaitUntilFunction, createContextKey, createWaitUntil, isServerHandlerObject, loadServerAdapter, raceRequestAbort, runMiddleware, serve, toServerHandlerObject, unstable_buildRouteTree, unstable_convertRoutesToHandler, unstable_match, wrapFetch };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as unstable_match, a as ServerServeEvent, c as createWaitUntil, d as raceRequestAbort, f as runMiddleware, g as unstable_convertRoutesToHandler, h as unstable_buildRouteTree, i as ServerErrorEvent, l as isServerHandlerObject, m as toServerHandlerObject, n as Server, p as serve, r as ServerCloseEvent, s as createContextKey, t as InvocationContext, u as loadServerAdapter, v as wrapFetch } from "./core-CmUugTW7.mjs";
|
|
2
|
+
export { InvocationContext, Server, ServerCloseEvent, ServerErrorEvent, ServerServeEvent, createContextKey, createWaitUntil, isServerHandlerObject, loadServerAdapter, raceRequestAbort, runMiddleware, serve, toServerHandlerObject, unstable_buildRouteTree, unstable_convertRoutesToHandler, unstable_match, wrapFetch };
|
package/dist/log.d.mts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { x as ServerMiddleware } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/log.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Logging middleware options.
|
|
6
|
+
*
|
|
7
|
+
* Reserved for future customization of log format and destination.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* declare module "sevok/log" {
|
|
12
|
+
* interface LogOptions {
|
|
13
|
+
* requestId?: boolean;
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
interface LogOptions {}
|
|
19
|
+
/**
|
|
20
|
+
* Create a request logger middleware.
|
|
21
|
+
*
|
|
22
|
+
* Each completed request prints timestamp, method, url, status code, and total
|
|
23
|
+
* response time using simple terminal colors.
|
|
24
|
+
*/
|
|
25
|
+
declare const log: (_options?: LogOptions) => ServerMiddleware;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { LogOptions, log };
|
package/dist/log.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { t as _color_default } from "./_color-B42q-MpL.mjs";
|
|
2
|
+
//#region src/log.ts
|
|
3
|
+
const statusColors = {
|
|
4
|
+
1: _color_default.blue,
|
|
5
|
+
2: _color_default.green,
|
|
6
|
+
3: _color_default.yellow
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Create a request logger middleware.
|
|
10
|
+
*
|
|
11
|
+
* Each completed request prints timestamp, method, url, status code, and total
|
|
12
|
+
* response time using simple terminal colors.
|
|
13
|
+
*/
|
|
14
|
+
const log = (_options = {}) => {
|
|
15
|
+
return async (ctx, next) => {
|
|
16
|
+
const start = performance.now();
|
|
17
|
+
const req = ctx.request;
|
|
18
|
+
const res = await next(ctx);
|
|
19
|
+
const duration = performance.now() - start;
|
|
20
|
+
const statusColor = statusColors[Math.floor(res.status / 100)] || _color_default.red;
|
|
21
|
+
console.log(`${_color_default.gray(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`)} ${_color_default.bold(req.method)} ${_color_default.blue(req.url)} [${statusColor(res.status + "")}] ${_color_default.gray(`(${duration.toFixed(2)}ms)`)}`);
|
|
22
|
+
return res;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { log };
|
package/dist/node.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { d as RuntimeAdapter, f as RuntimeCapabilities, p as Server } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/node.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime adapter that backs `Server` with Node's HTTP, HTTPS, or HTTP/2
|
|
6
|
+
* servers depending on the effective TLS configuration.
|
|
7
|
+
*/
|
|
8
|
+
declare class NodeRuntimeAdapter implements RuntimeAdapter {
|
|
9
|
+
#private;
|
|
10
|
+
readonly graceful = true;
|
|
11
|
+
get capabilities(): RuntimeCapabilities;
|
|
12
|
+
/**
|
|
13
|
+
* Create the underlying Node server instance but do not start listening yet.
|
|
14
|
+
*/
|
|
15
|
+
setup(server: Server): void;
|
|
16
|
+
/**
|
|
17
|
+
* Start the Node server once and reuse the same ready promise afterwards.
|
|
18
|
+
*/
|
|
19
|
+
serve(server: Server): Promise<{
|
|
20
|
+
url: string | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Return either a socket path or a formatted origin for TCP listeners.
|
|
24
|
+
*/
|
|
25
|
+
get url(): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Stop accepting new work and optionally close active HTTP connections.
|
|
28
|
+
*/
|
|
29
|
+
close(closeActiveConnections: boolean): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { NodeRuntimeAdapter };
|
package/dist/node.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { n as resolveTLSOptions, r as runtimeCapabilities, t as printListening } from "./_node_like-BRaAGeNM.mjs";
|
|
2
|
+
import { a as fmtURL, s as resolvePortAndHost } from "./_shared-38k_JIsU.mjs";
|
|
3
|
+
import nodeHTTP from "node:http";
|
|
4
|
+
import nodeHTTPS from "node:https";
|
|
5
|
+
import nodeHTTP2 from "node:http2";
|
|
6
|
+
//#region src/node.ts
|
|
7
|
+
/**
|
|
8
|
+
* Runtime adapter that backs `Server` with Node's HTTP, HTTPS, or HTTP/2
|
|
9
|
+
* servers depending on the effective TLS configuration.
|
|
10
|
+
*/
|
|
11
|
+
var NodeRuntimeAdapter = class {
|
|
12
|
+
#server;
|
|
13
|
+
#serveOptions;
|
|
14
|
+
#isSecure;
|
|
15
|
+
#listeningPromise;
|
|
16
|
+
graceful = true;
|
|
17
|
+
get capabilities() {
|
|
18
|
+
return runtimeCapabilities;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create the underlying Node server instance but do not start listening yet.
|
|
22
|
+
*/
|
|
23
|
+
setup(server) {
|
|
24
|
+
const { options } = server;
|
|
25
|
+
const { hostname, port } = resolvePortAndHost(options);
|
|
26
|
+
const tls = resolveTLSOptions(options);
|
|
27
|
+
const isSecure = !!tls?.cert && options.protocol !== "http";
|
|
28
|
+
const isHttp2 = options.node?.http2 ?? isSecure;
|
|
29
|
+
this.#isSecure = isSecure;
|
|
30
|
+
this.#serveOptions = {
|
|
31
|
+
port,
|
|
32
|
+
host: hostname,
|
|
33
|
+
exclusive: !server.options.reusePort,
|
|
34
|
+
...tls ? {
|
|
35
|
+
cert: tls.cert,
|
|
36
|
+
key: tls.key,
|
|
37
|
+
passphrase: tls.passphrase
|
|
38
|
+
} : {},
|
|
39
|
+
...options.node
|
|
40
|
+
};
|
|
41
|
+
const handler = async (request, response) => {
|
|
42
|
+
await handleNodeRequest(server, request, response);
|
|
43
|
+
};
|
|
44
|
+
if (isHttp2) if (isSecure) this.#server = nodeHTTP2.createSecureServer({
|
|
45
|
+
allowHTTP1: true,
|
|
46
|
+
...this.#serveOptions
|
|
47
|
+
}, handler);
|
|
48
|
+
else throw new Error("node.http2 option requires tls certificate!");
|
|
49
|
+
else if (isSecure) this.#server = nodeHTTPS.createServer(this.#serveOptions, handler);
|
|
50
|
+
else this.#server = nodeHTTP.createServer(this.#serveOptions, handler);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start the Node server once and reuse the same ready promise afterwards.
|
|
54
|
+
*/
|
|
55
|
+
async serve(server) {
|
|
56
|
+
if (this.#listeningPromise) return Promise.resolve(this.#listeningPromise).then(() => ({ url: this.url }));
|
|
57
|
+
this.#listeningPromise = new Promise((resolve) => {
|
|
58
|
+
this.#server.listen(this.#serveOptions, () => {
|
|
59
|
+
printListening(server.options, this.url);
|
|
60
|
+
resolve({ url: this.url });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return this.#listeningPromise;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Return either a socket path or a formatted origin for TCP listeners.
|
|
67
|
+
*/
|
|
68
|
+
get url() {
|
|
69
|
+
const addr = this.#server?.address();
|
|
70
|
+
if (!addr) return;
|
|
71
|
+
return typeof addr === "string" ? addr : fmtURL(addr.address, addr.port, this.#isSecure);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Stop accepting new work and optionally close active HTTP connections.
|
|
75
|
+
*/
|
|
76
|
+
close(closeActiveConnections) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const server = this.#server;
|
|
79
|
+
if (server && closeActiveConnections && "closeAllConnections" in server) server.closeAllConnections();
|
|
80
|
+
if (!server || !server.listening) return resolve();
|
|
81
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Convert a Node request/response pair into fetch primitives and forward
|
|
87
|
+
* framework-level errors through the configured error handler.
|
|
88
|
+
*/
|
|
89
|
+
async function handleNodeRequest(server, request, response) {
|
|
90
|
+
try {
|
|
91
|
+
const nextRequest = await toRequest(request);
|
|
92
|
+
await writeResponse(response, await server.fetch(nextRequest));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
await writeResponse(response, server.options.error ? await server.options.error(error) : new Response("Internal Server Error", { status: 500 }));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convert Node's `IncomingMessage` into a standard `Request`.
|
|
99
|
+
*/
|
|
100
|
+
async function toRequest(request) {
|
|
101
|
+
const protocol = request.socket.encrypted ? "https" : "http";
|
|
102
|
+
const host = request.headers.host ?? "127.0.0.1";
|
|
103
|
+
const url = new URL(request.url ?? "/", `${protocol}://${host}`);
|
|
104
|
+
const headers = new Headers();
|
|
105
|
+
for (const [key, value] of Object.entries(request.headers)) {
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
for (const entry of value) headers.append(key, entry);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "string") headers.set(key, value);
|
|
111
|
+
}
|
|
112
|
+
const init = {
|
|
113
|
+
method: request.method,
|
|
114
|
+
headers
|
|
115
|
+
};
|
|
116
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
117
|
+
const { Readable } = await import("node:stream");
|
|
118
|
+
init.body = Readable.toWeb(request);
|
|
119
|
+
init.duplex = "half";
|
|
120
|
+
}
|
|
121
|
+
return new Request(url.toString(), init);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Write a fetch `Response` back to Node's `ServerResponse`.
|
|
125
|
+
*/
|
|
126
|
+
async function writeResponse(response, nextResponse) {
|
|
127
|
+
response.statusCode = nextResponse.status;
|
|
128
|
+
response.statusMessage = nextResponse.statusText;
|
|
129
|
+
nextResponse.headers.forEach((value, key) => {
|
|
130
|
+
response.setHeader(key, value);
|
|
131
|
+
});
|
|
132
|
+
if (!nextResponse.body) {
|
|
133
|
+
response.end();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
response.end(Buffer.from(await nextResponse.arrayBuffer()));
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { NodeRuntimeAdapter };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { l as MaybePromise, x as ServerMiddleware } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/static.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for `serveStatic()`.
|
|
6
|
+
*/
|
|
7
|
+
interface ServeStaticOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The directory to serve static files from.
|
|
10
|
+
*/
|
|
11
|
+
dir: string;
|
|
12
|
+
/**
|
|
13
|
+
* The HTTP methods to allow for serving static files.
|
|
14
|
+
*/
|
|
15
|
+
methods?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* A function to modify the HTML content before serving it.
|
|
18
|
+
*/
|
|
19
|
+
renderHTML?: (ctx: {
|
|
20
|
+
request: Request;
|
|
21
|
+
html: string;
|
|
22
|
+
filename: string;
|
|
23
|
+
}) => MaybePromise<Response>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create middleware that serves files from a runtime-provided filesystem.
|
|
27
|
+
*
|
|
28
|
+
* Resolution follows common static-site conventions:
|
|
29
|
+
* - `/` maps to `index.html`
|
|
30
|
+
* - extensionless paths try `name.html` and `name/index.html`
|
|
31
|
+
* - explicit extensions are used as-is
|
|
32
|
+
*/
|
|
33
|
+
declare function serveStatic(options: ServeStaticOptions): ServerMiddleware;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { ServeStaticOptions, serveStatic };
|
package/dist/static.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//#region src/static.ts
|
|
2
|
+
const COMMON_MIME_TYPES = {
|
|
3
|
+
".html": "text/html",
|
|
4
|
+
".htm": "text/html",
|
|
5
|
+
".css": "text/css",
|
|
6
|
+
".js": "text/javascript",
|
|
7
|
+
".mjs": "text/javascript",
|
|
8
|
+
".json": "application/json",
|
|
9
|
+
".txt": "text/plain",
|
|
10
|
+
".xml": "application/xml",
|
|
11
|
+
".gif": "image/gif",
|
|
12
|
+
".ico": "image/vnd.microsoft.icon",
|
|
13
|
+
".jpeg": "image/jpeg",
|
|
14
|
+
".jpg": "image/jpeg",
|
|
15
|
+
".png": "image/png",
|
|
16
|
+
".svg": "image/svg+xml",
|
|
17
|
+
".webp": "image/webp",
|
|
18
|
+
".woff": "font/woff",
|
|
19
|
+
".woff2": "font/woff2",
|
|
20
|
+
".mp4": "video/mp4",
|
|
21
|
+
".webm": "video/webm",
|
|
22
|
+
".zip": "application/zip",
|
|
23
|
+
".pdf": "application/pdf"
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Create middleware that serves files from a runtime-provided filesystem.
|
|
27
|
+
*
|
|
28
|
+
* Resolution follows common static-site conventions:
|
|
29
|
+
* - `/` maps to `index.html`
|
|
30
|
+
* - extensionless paths try `name.html` and `name/index.html`
|
|
31
|
+
* - explicit extensions are used as-is
|
|
32
|
+
*/
|
|
33
|
+
function serveStatic(options) {
|
|
34
|
+
const methods = new Set((options.methods || ["GET", "HEAD"]).map((m) => m.toUpperCase()));
|
|
35
|
+
return async (ctx, next) => {
|
|
36
|
+
const req = ctx.request;
|
|
37
|
+
if (!methods.has(req.method)) return next(ctx);
|
|
38
|
+
const path = (ctx.url ??= new URL(req.url)).pathname.slice(1).replace(/\/$/, "");
|
|
39
|
+
let paths;
|
|
40
|
+
if (path === "") paths = ["index.html"];
|
|
41
|
+
else if (extname(path) === "") paths = [`${path}.html`, `${path}/index.html`];
|
|
42
|
+
else paths = [path];
|
|
43
|
+
const fs = ctx.capabilities;
|
|
44
|
+
for (const path of paths) {
|
|
45
|
+
const filePath = await fs.resolve(options.dir, path);
|
|
46
|
+
if (!filePath) continue;
|
|
47
|
+
const file = await fs.open(filePath);
|
|
48
|
+
if (file?.isFile) {
|
|
49
|
+
const fileExt = extname(filePath);
|
|
50
|
+
const headers = new Headers({
|
|
51
|
+
"Content-Length": file.size.toString(),
|
|
52
|
+
"Content-Type": COMMON_MIME_TYPES[fileExt] || "application/octet-stream"
|
|
53
|
+
});
|
|
54
|
+
const stream = file.stream();
|
|
55
|
+
if (options.renderHTML && fileExt === ".html") {
|
|
56
|
+
const html = await read(stream);
|
|
57
|
+
return options.renderHTML({
|
|
58
|
+
html,
|
|
59
|
+
filename: filePath,
|
|
60
|
+
request: req
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const acceptEncoding = req.headers.get("accept-encoding") || "";
|
|
64
|
+
if (acceptEncoding.includes("br")) {
|
|
65
|
+
headers.set("Content-Encoding", "br");
|
|
66
|
+
headers.delete("Content-Length");
|
|
67
|
+
headers.set("Vary", "Accept-Encoding");
|
|
68
|
+
const { readable, writable } = await fs.createBrotliCompress();
|
|
69
|
+
await stream.pipeTo(writable);
|
|
70
|
+
return new Response(readable, { headers });
|
|
71
|
+
} else if (acceptEncoding.includes("gzip")) {
|
|
72
|
+
headers.set("Content-Encoding", "gzip");
|
|
73
|
+
headers.delete("Content-Length");
|
|
74
|
+
headers.set("Vary", "Accept-Encoding");
|
|
75
|
+
const { readable, writable } = await fs.createGzip();
|
|
76
|
+
await stream.pipeTo(writable);
|
|
77
|
+
return new Response(readable, { headers });
|
|
78
|
+
} else return new Response(stream, { headers });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return next(ctx);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract a file extension without relying on Node's path helpers so the
|
|
86
|
+
* function also works in non-Node runtimes.
|
|
87
|
+
*/
|
|
88
|
+
function extname(path) {
|
|
89
|
+
const lastSlash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
90
|
+
const lastDot = path.lastIndexOf(".");
|
|
91
|
+
return lastDot > lastSlash ? path.slice(lastDot) : "";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Consume a `ReadableStream` into a UTF-8 string.
|
|
95
|
+
*/
|
|
96
|
+
async function read(stream) {
|
|
97
|
+
return await new Response(stream).text();
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { serveStatic };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { d as RuntimeAdapter, f as RuntimeCapabilities, j as WaitUntilFunction, p as Server } from "./core-BGp2ZR_k.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/stream.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Minimal request entry shape consumed by `handleRequestEntry()`.
|
|
6
|
+
*
|
|
7
|
+
* This intentionally matches service-worker-like runtimes that expose a request
|
|
8
|
+
* plus completion hooks rather than direct socket APIs.
|
|
9
|
+
*/
|
|
10
|
+
interface RequestEntry {
|
|
11
|
+
readonly request: Request;
|
|
12
|
+
readonly waitUntil?: WaitUntilFunction;
|
|
13
|
+
completeWith(promise: Promise<Response>): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for consuming fetch events from an async stream.
|
|
17
|
+
*/
|
|
18
|
+
type StreamServerOptions = {
|
|
19
|
+
/**
|
|
20
|
+
* Source of runtime-specific fetch events to dispatch through `Server`.
|
|
21
|
+
*/
|
|
22
|
+
stream: AsyncIterableIterator<RequestEntry>;
|
|
23
|
+
/**
|
|
24
|
+
* The path to the stream worker file or address to be registered.
|
|
25
|
+
*/
|
|
26
|
+
url?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Complete a runtime request entry by routing it through `Server.fetch()`.
|
|
30
|
+
*/
|
|
31
|
+
declare function handleRequestEntry(server: Server, entry: RequestEntry): void;
|
|
32
|
+
/**
|
|
33
|
+
* Adapter for environments that expose requests as an async iterator instead of
|
|
34
|
+
* a socket listener.
|
|
35
|
+
*/
|
|
36
|
+
declare class StreamRuntimeAdapter implements RuntimeAdapter {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(options: StreamServerOptions);
|
|
39
|
+
get capabilities(): RuntimeCapabilities;
|
|
40
|
+
/**
|
|
41
|
+
* No-op because the stream runtime does not need listener setup.
|
|
42
|
+
*/
|
|
43
|
+
setup(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Start consuming the event stream once and dispatch each event to `Server`.
|
|
46
|
+
*/
|
|
47
|
+
serve(server: Server): Promise<{
|
|
48
|
+
url: string | undefined;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Stop dispatching future events from the stream.
|
|
52
|
+
*/
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { RequestEntry, StreamRuntimeAdapter, StreamServerOptions, handleRequestEntry };
|
package/dist/stream.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/stream.ts
|
|
2
|
+
/**
|
|
3
|
+
* Complete a runtime request entry by routing it through `Server.fetch()`.
|
|
4
|
+
*/
|
|
5
|
+
function handleRequestEntry(server, entry) {
|
|
6
|
+
const { request, waitUntil } = entry;
|
|
7
|
+
let context = server.createContext(request);
|
|
8
|
+
if (typeof waitUntil === "function") context = context.with({ waitUntil: (p) => waitUntil(p) });
|
|
9
|
+
entry.completeWith(Promise.resolve(server.handle(context)));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Adapter for environments that expose requests as an async iterator instead of
|
|
13
|
+
* a socket listener.
|
|
14
|
+
*/
|
|
15
|
+
var StreamRuntimeAdapter = class {
|
|
16
|
+
#options;
|
|
17
|
+
#served = false;
|
|
18
|
+
#closed = false;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.#options = options;
|
|
21
|
+
}
|
|
22
|
+
get capabilities() {
|
|
23
|
+
return streamCapabilities;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* No-op because the stream runtime does not need listener setup.
|
|
27
|
+
*/
|
|
28
|
+
setup() {}
|
|
29
|
+
/**
|
|
30
|
+
* Start consuming the event stream once and dispatch each event to `Server`.
|
|
31
|
+
*/
|
|
32
|
+
async serve(server) {
|
|
33
|
+
if (this.#served) return { url: this.#options.url };
|
|
34
|
+
this.#served = true;
|
|
35
|
+
queueMicrotask(async () => {
|
|
36
|
+
for await (const event of this.#options.stream) {
|
|
37
|
+
if (this.#closed) return;
|
|
38
|
+
handleRequestEntry(server, event);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return { url: this.#options.url };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Stop dispatching future events from the stream.
|
|
45
|
+
*/
|
|
46
|
+
async close() {
|
|
47
|
+
this.#closed = true;
|
|
48
|
+
this.#served = false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Minimal capabilities available in stream-only environments.
|
|
53
|
+
*/
|
|
54
|
+
const streamCapabilities = {
|
|
55
|
+
resolve: () => Promise.resolve(null),
|
|
56
|
+
open: () => Promise.resolve(null),
|
|
57
|
+
createGzip: () => createCompressionStream("gzip"),
|
|
58
|
+
createBrotliCompress() {
|
|
59
|
+
throw new Error("Does not provide Brotli compression.");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
function createCompressionStream(format) {
|
|
63
|
+
const CompressionStreamCtor = globalThis.CompressionStream;
|
|
64
|
+
if (typeof CompressionStreamCtor !== "function") throw new Error("Does not provide CompressionStream.");
|
|
65
|
+
return new CompressionStreamCtor(format);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { StreamRuntimeAdapter, handleRequestEntry };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sevok",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Composable Server Primitives Across Runtimes",
|
|
5
|
+
"homepage": "https://sevok.pages.dev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Maofeng <hornjs@qq.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/hornjs/sevok.git"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"README.md",
|
|
15
|
+
"README.zh-CN.md",
|
|
16
|
+
"bin",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.mts",
|
|
24
|
+
"default": "./dist/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./bun": {
|
|
27
|
+
"types": "./dist/bun.d.mts",
|
|
28
|
+
"default": "./dist/bun.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./cli": {
|
|
31
|
+
"types": "./dist/cli.d.mts",
|
|
32
|
+
"default": "./dist/cli.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./deno": {
|
|
35
|
+
"types": "./dist/deno.d.mts",
|
|
36
|
+
"default": "./dist/deno.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./log": {
|
|
39
|
+
"types": "./dist/log.d.mts",
|
|
40
|
+
"default": "./dist/log.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./node": {
|
|
43
|
+
"types": "./dist/node.d.mts",
|
|
44
|
+
"default": "./dist/node.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./static": {
|
|
47
|
+
"types": "./dist/static.d.mts",
|
|
48
|
+
"default": "./dist/static.mjs"
|
|
49
|
+
},
|
|
50
|
+
"./stream": {
|
|
51
|
+
"types": "./dist/stream.d.mts",
|
|
52
|
+
"default": "./dist/stream.mjs"
|
|
53
|
+
},
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@hornjs/evt": "^0.0.4",
|
|
58
|
+
"@types/bun": "^1.3.10",
|
|
59
|
+
"@types/deno": "^2.5.0"
|
|
60
|
+
},
|
|
61
|
+
"bin": {
|
|
62
|
+
"sevok": "./bin/sevok.mjs"
|
|
63
|
+
},
|
|
64
|
+
"types": "./dist/index.d.mts",
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|