srvx 0.4.0 → 0.5.1
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/README.md +7 -11
- package/dist/adapters/bun.d.mts +4 -10
- package/dist/adapters/bun.mjs +17 -4
- package/dist/adapters/cloudflare.d.mts +1 -7
- package/dist/adapters/cloudflare.mjs +2 -1
- package/dist/adapters/deno.d.mts +1 -7
- package/dist/adapters/deno.mjs +4 -3
- package/dist/adapters/generic.d.mts +11 -0
- package/dist/adapters/generic.mjs +30 -0
- package/dist/adapters/node.d.mts +5 -83
- package/dist/adapters/node.mjs +13 -5
- package/dist/adapters/service-worker.d.mts +13 -0
- package/dist/adapters/service-worker.mjs +97 -0
- package/dist/shared/srvx.BD9sRkkl.mjs +16 -0
- package/dist/shared/{srvx.lC_d9z2b.mjs → srvx.FQfHxe2J.mjs} +25 -7
- package/dist/types.d.mts +28 -2
- package/package.json +19 -12
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
Universal Server API based on web platform standards. Works with [Deno](https://deno.com/), [Bun](https://bun.sh/) and [Node.js](https://nodejs.org/en).
|
|
11
11
|
|
|
12
|
-
- ✅ Seamless runtime integration with identical usage ([handler](https://srvx.
|
|
12
|
+
- ✅ Seamless runtime integration with identical usage ([handler](https://srvx.h3.dev/guide/handler) and [instance](https://srvx.h3.dev/guide/server))
|
|
13
13
|
- ✅ Zero overhead [Deno](https://deno.com/) and [Bun](https://bun.sh/) support
|
|
14
|
-
- ✅ [Node.js compatibility](https://srvx.
|
|
14
|
+
- ✅ [Node.js compatibility](https://srvx.h3.dev/guide/node) with ~native perf and [fast response](https://srvx.h3.dev/guide/node#fast-response) support
|
|
15
15
|
|
|
16
16
|
## Quick start
|
|
17
17
|
|
|
@@ -24,13 +24,9 @@ const server = serve({
|
|
|
24
24
|
return new Response("👋 Hello there!");
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
|
-
|
|
28
|
-
await server.ready();
|
|
29
|
-
|
|
30
|
-
console.log(`🚀 Server ready at ${server.url}`);
|
|
31
27
|
```
|
|
32
28
|
|
|
33
|
-
👉 **Visit the 📖 [Documentation](https://srvx.
|
|
29
|
+
👉 **Visit the 📖 [Documentation](https://srvx.h3.dev/) to learn more.**
|
|
34
30
|
|
|
35
31
|
## Development
|
|
36
32
|
|
|
@@ -50,11 +46,11 @@ console.log(`🚀 Server ready at ${server.url}`);
|
|
|
50
46
|
|
|
51
47
|
<!-- automd:contributors author=pi0 license=MIT -->
|
|
52
48
|
|
|
53
|
-
Published under the [MIT](https://github.com/
|
|
54
|
-
Made by [@pi0](https://github.com/pi0) and [community](https://github.com/
|
|
49
|
+
Published under the [MIT](https://github.com/h3js/srvx/blob/main/LICENSE) license.
|
|
50
|
+
Made by [@pi0](https://github.com/pi0) and [community](https://github.com/h3js/srvx/graphs/contributors) 💛
|
|
55
51
|
<br><br>
|
|
56
|
-
<a href="https://github.com/
|
|
57
|
-
<img src="https://contrib.rocks/image?repo=
|
|
52
|
+
<a href="https://github.com/h3js/srvx/graphs/contributors">
|
|
53
|
+
<img src="https://contrib.rocks/image?repo=h3js/srvx" />
|
|
58
54
|
</a>
|
|
59
55
|
|
|
60
56
|
<!-- /automd -->
|
package/dist/adapters/bun.d.mts
CHANGED
|
@@ -5,13 +5,7 @@ import 'node:https';
|
|
|
5
5
|
import 'node:net';
|
|
6
6
|
import '@cloudflare/workers-types';
|
|
7
7
|
|
|
8
|
-
declare const Response:
|
|
9
|
-
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
|
10
|
-
prototype: Response;
|
|
11
|
-
error(): Response;
|
|
12
|
-
json(data: any, init?: ResponseInit): Response;
|
|
13
|
-
redirect(url: string | URL, status?: number): Response;
|
|
14
|
-
};
|
|
8
|
+
declare const Response: typeof globalThis.Response;
|
|
15
9
|
declare function serve(options: ServerOptions): BunServer;
|
|
16
10
|
declare class BunServer implements Server<BunFetchHandler> {
|
|
17
11
|
readonly runtime = "bun";
|
|
@@ -20,10 +14,10 @@ declare class BunServer implements Server<BunFetchHandler> {
|
|
|
20
14
|
readonly serveOptions: Bun.ServeOptions | Bun.TLSServeOptions;
|
|
21
15
|
readonly fetch: BunFetchHandler;
|
|
22
16
|
constructor(options: ServerOptions);
|
|
23
|
-
serve(): Promise<
|
|
17
|
+
serve(): Promise<this>;
|
|
24
18
|
get url(): string | undefined;
|
|
25
|
-
ready(): Promise<
|
|
26
|
-
close(closeAll?: boolean): Promise<void
|
|
19
|
+
ready(): Promise<this>;
|
|
20
|
+
close(closeAll?: boolean): Promise<void>;
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
export { Response, serve };
|
package/dist/adapters/bun.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as resolveTLSOptions, a as
|
|
1
|
+
import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
|
|
2
2
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
3
3
|
import 'node:fs';
|
|
4
4
|
|
|
@@ -29,9 +29,9 @@ class BunServer {
|
|
|
29
29
|
};
|
|
30
30
|
const tls = resolveTLSOptions(this.options);
|
|
31
31
|
this.serveOptions = {
|
|
32
|
-
|
|
32
|
+
...resolvePortAndHost(this.options),
|
|
33
33
|
reusePort: this.options.reusePort,
|
|
34
|
-
|
|
34
|
+
error: this.options.onError,
|
|
35
35
|
...this.options.bun,
|
|
36
36
|
tls: {
|
|
37
37
|
cert: tls?.cert,
|
|
@@ -49,10 +49,23 @@ class BunServer {
|
|
|
49
49
|
if (!this.bun.server) {
|
|
50
50
|
this.bun.server = Bun.serve(this.serveOptions);
|
|
51
51
|
}
|
|
52
|
+
printListening(this.options, this.url);
|
|
52
53
|
return Promise.resolve(this);
|
|
53
54
|
}
|
|
54
55
|
get url() {
|
|
55
|
-
|
|
56
|
+
const server = this.bun?.server;
|
|
57
|
+
if (!server) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const address = server.address;
|
|
61
|
+
if (address) {
|
|
62
|
+
return fmtURL(
|
|
63
|
+
address.address,
|
|
64
|
+
address.port,
|
|
65
|
+
server.protocol === "https"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return server.url.href;
|
|
56
69
|
}
|
|
57
70
|
ready() {
|
|
58
71
|
return Promise.resolve(this);
|
|
@@ -5,13 +5,7 @@ import 'node:https';
|
|
|
5
5
|
import 'node:net';
|
|
6
6
|
import 'bun';
|
|
7
7
|
|
|
8
|
-
declare const Response:
|
|
9
|
-
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
|
10
|
-
prototype: Response;
|
|
11
|
-
error(): Response;
|
|
12
|
-
json(data: any, init?: ResponseInit): Response;
|
|
13
|
-
redirect(url: string | URL, status?: number): Response;
|
|
14
|
-
};
|
|
8
|
+
declare const Response: typeof globalThis.Response;
|
|
15
9
|
declare function serve(options: ServerOptions): Server<CF.ExportedHandlerFetchHandler>;
|
|
16
10
|
|
|
17
11
|
export { Response, serve };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
2
|
+
import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
|
|
2
3
|
|
|
3
4
|
const Response = globalThis.Response;
|
|
4
5
|
function serve(options) {
|
|
@@ -10,7 +11,7 @@ class CloudflareServer {
|
|
|
10
11
|
this.options = options;
|
|
11
12
|
const fetchHandler = wrapFetch(
|
|
12
13
|
this,
|
|
13
|
-
this.options.fetch
|
|
14
|
+
wrapFetchOnError(this.options.fetch, this.options.onError)
|
|
14
15
|
);
|
|
15
16
|
this.fetch = (request, env, context) => {
|
|
16
17
|
Object.defineProperties(request, {
|
package/dist/adapters/deno.d.mts
CHANGED
|
@@ -5,13 +5,7 @@ import 'node:net';
|
|
|
5
5
|
import 'bun';
|
|
6
6
|
import '@cloudflare/workers-types';
|
|
7
7
|
|
|
8
|
-
declare const Response:
|
|
9
|
-
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
|
10
|
-
prototype: Response;
|
|
11
|
-
error(): Response;
|
|
12
|
-
json(data: any, init?: ResponseInit): Response;
|
|
13
|
-
redirect(url: string | URL, status?: number): Response;
|
|
14
|
-
};
|
|
8
|
+
declare const Response: typeof globalThis.Response;
|
|
15
9
|
declare function serve(options: ServerOptions): DenoServer;
|
|
16
10
|
declare class DenoServer implements Server<DenoFetchHandler> {
|
|
17
11
|
#private;
|
package/dist/adapters/deno.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as resolveTLSOptions, a as
|
|
1
|
+
import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
|
|
2
2
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
3
3
|
import 'node:fs';
|
|
4
4
|
|
|
@@ -29,9 +29,9 @@ class DenoServer {
|
|
|
29
29
|
};
|
|
30
30
|
const tls = resolveTLSOptions(this.options);
|
|
31
31
|
this.serveOptions = {
|
|
32
|
-
|
|
33
|
-
hostname: this.options.hostname,
|
|
32
|
+
...resolvePortAndHost(this.options),
|
|
34
33
|
reusePort: this.options.reusePort,
|
|
34
|
+
onError: this.options.onError,
|
|
35
35
|
...tls ? { key: tls.key, cert: tls.cert, passphrase: tls.passphrase } : {},
|
|
36
36
|
...this.options.deno
|
|
37
37
|
};
|
|
@@ -55,6 +55,7 @@ class DenoServer {
|
|
|
55
55
|
if (this.options.deno?.onListen) {
|
|
56
56
|
this.options.deno.onListen(info);
|
|
57
57
|
}
|
|
58
|
+
printListening(this.options, this.url);
|
|
58
59
|
onListenPromise.resolve();
|
|
59
60
|
}
|
|
60
61
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ServerOptions, Server } from '../types.mjs';
|
|
2
|
+
import 'node:http';
|
|
3
|
+
import 'node:https';
|
|
4
|
+
import 'node:net';
|
|
5
|
+
import 'bun';
|
|
6
|
+
import '@cloudflare/workers-types';
|
|
7
|
+
|
|
8
|
+
declare const Response: typeof globalThis.Response;
|
|
9
|
+
declare function serve(options: ServerOptions): Server;
|
|
10
|
+
|
|
11
|
+
export { Response, serve };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
2
|
+
import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
|
|
3
|
+
|
|
4
|
+
const Response = globalThis.Response;
|
|
5
|
+
function serve(options) {
|
|
6
|
+
return new GenericServer(options);
|
|
7
|
+
}
|
|
8
|
+
class GenericServer {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.runtime = "generic";
|
|
11
|
+
this.options = options;
|
|
12
|
+
const fetchHandler = wrapFetch(
|
|
13
|
+
this,
|
|
14
|
+
wrapFetchOnError(this.options.fetch, this.options.onError)
|
|
15
|
+
);
|
|
16
|
+
this.fetch = (request) => {
|
|
17
|
+
return Promise.resolve(fetchHandler(request));
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
serve() {
|
|
21
|
+
}
|
|
22
|
+
ready() {
|
|
23
|
+
return Promise.resolve(this);
|
|
24
|
+
}
|
|
25
|
+
close() {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Response, serve };
|
package/dist/adapters/node.d.mts
CHANGED
|
@@ -13,44 +13,14 @@ type NodeResponse = InstanceType<typeof NodeResponse>;
|
|
|
13
13
|
* It is faster because in most cases it doesn't create a full Response instance.
|
|
14
14
|
*/
|
|
15
15
|
declare const NodeResponse: {
|
|
16
|
-
new (body?: BodyInit | null, init?: ResponseInit): {
|
|
17
|
-
|
|
18
|
-
"__#4366@#init"?: ResponseInit;
|
|
19
|
-
/**
|
|
20
|
-
* Prepare Node.js response object
|
|
21
|
-
*/
|
|
22
|
-
nodeResponse(): {
|
|
16
|
+
new (body?: BodyInit | null, init?: ResponseInit): globalThis.Response & {
|
|
17
|
+
readonly nodeResponse: () => {
|
|
23
18
|
status: number;
|
|
24
19
|
statusText: string;
|
|
25
20
|
headers: NodeHttp__default.OutgoingHttpHeader[];
|
|
26
|
-
body: string | Buffer
|
|
21
|
+
body: string | Buffer | Uint8Array | DataView | ReadableStream<Uint8Array> | Readable | undefined | null;
|
|
27
22
|
};
|
|
28
|
-
/** Lazy initialized response instance */
|
|
29
|
-
"__#4366@#responseObj"?: globalThis.Response;
|
|
30
|
-
/** Lazy initialized headers instance */
|
|
31
|
-
"__#4366@#headersObj"?: Headers;
|
|
32
|
-
clone(): globalThis.Response;
|
|
33
|
-
readonly "__#4366@#response": globalThis.Response;
|
|
34
|
-
readonly headers: Headers;
|
|
35
|
-
readonly ok: boolean;
|
|
36
|
-
readonly redirected: boolean;
|
|
37
|
-
readonly status: number;
|
|
38
|
-
readonly statusText: string;
|
|
39
|
-
readonly type: ResponseType;
|
|
40
|
-
readonly url: string;
|
|
41
|
-
"__#4366@#fastBody"<T extends object>(as: new (...args: any[]) => T): T | null | false;
|
|
42
|
-
readonly body: ReadableStream<Uint8Array> | null;
|
|
43
|
-
readonly bodyUsed: boolean;
|
|
44
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
45
|
-
blob(): Promise<Blob>;
|
|
46
|
-
bytes(): Promise<Uint8Array>;
|
|
47
|
-
formData(): Promise<FormData>;
|
|
48
|
-
text(): Promise<string>;
|
|
49
|
-
json(): Promise<any>;
|
|
50
23
|
};
|
|
51
|
-
json(data: any, init?: ResponseInit): globalThis.Response;
|
|
52
|
-
error(): globalThis.Response;
|
|
53
|
-
redirect(url: string | URL, status?: number): globalThis.Response;
|
|
54
24
|
};
|
|
55
25
|
|
|
56
26
|
declare const NodeRequest: {
|
|
@@ -60,65 +30,17 @@ declare const NodeRequest: {
|
|
|
60
30
|
}): ServerRequest;
|
|
61
31
|
};
|
|
62
32
|
|
|
63
|
-
declare const kNodeInspect: unique symbol;
|
|
64
|
-
|
|
65
33
|
declare const NodeRequestHeaders: {
|
|
66
34
|
new (nodeCtx: {
|
|
67
35
|
req: NodeHttp__default.IncomingMessage;
|
|
68
36
|
res?: NodeHttp__default.ServerResponse;
|
|
69
|
-
}):
|
|
70
|
-
_node: {
|
|
71
|
-
req: NodeHttp__default.IncomingMessage;
|
|
72
|
-
res?: NodeHttp__default.ServerResponse;
|
|
73
|
-
};
|
|
74
|
-
append(name: string, value: string): void;
|
|
75
|
-
delete(name: string): void;
|
|
76
|
-
get(name: string): string | null;
|
|
77
|
-
getSetCookie(): string[];
|
|
78
|
-
has(name: string): boolean;
|
|
79
|
-
set(name: string, value: string): void;
|
|
80
|
-
readonly count: number;
|
|
81
|
-
getAll(_name: "set-cookie" | "Set-Cookie"): string[];
|
|
82
|
-
toJSON(): Record<string, string>;
|
|
83
|
-
forEach(cb: (value: string, key: string, parent: /*elided*/ any) => void, thisArg?: any): void;
|
|
84
|
-
entries(): HeadersIterator<[string, string]>;
|
|
85
|
-
keys(): HeadersIterator<string>;
|
|
86
|
-
values(): HeadersIterator<string>;
|
|
87
|
-
[Symbol.iterator](): HeadersIterator<[string, string]>;
|
|
88
|
-
readonly [Symbol.toStringTag]: string;
|
|
89
|
-
[kNodeInspect](): {
|
|
90
|
-
[k: string]: string;
|
|
91
|
-
};
|
|
92
|
-
};
|
|
37
|
+
}): globalThis.Headers;
|
|
93
38
|
};
|
|
94
39
|
declare const NodeResponseHeaders: {
|
|
95
40
|
new (nodeCtx: {
|
|
96
41
|
req?: NodeHttp__default.IncomingMessage;
|
|
97
42
|
res: NodeHttp__default.ServerResponse;
|
|
98
|
-
}):
|
|
99
|
-
_node: {
|
|
100
|
-
req?: NodeHttp__default.IncomingMessage;
|
|
101
|
-
res: NodeHttp__default.ServerResponse;
|
|
102
|
-
};
|
|
103
|
-
append(name: string, value: string): void;
|
|
104
|
-
delete(name: string): void;
|
|
105
|
-
get(name: string): string | null;
|
|
106
|
-
getSetCookie(): string[];
|
|
107
|
-
has(name: string): boolean;
|
|
108
|
-
set(name: string, value: string): void;
|
|
109
|
-
readonly count: number;
|
|
110
|
-
getAll(_name: "set-cookie" | "Set-Cookie"): string[];
|
|
111
|
-
toJSON(): Record<string, string>;
|
|
112
|
-
forEach(cb: (value: string, key: string, parent: /*elided*/ any) => void, thisArg?: any): void;
|
|
113
|
-
entries(): HeadersIterator<[string, string]>;
|
|
114
|
-
keys(): HeadersIterator<string>;
|
|
115
|
-
values(): HeadersIterator<string>;
|
|
116
|
-
[Symbol.iterator](): HeadersIterator<[string, string]>;
|
|
117
|
-
readonly [Symbol.toStringTag]: string;
|
|
118
|
-
[kNodeInspect](): {
|
|
119
|
-
[k: string]: string;
|
|
120
|
-
};
|
|
121
|
-
};
|
|
43
|
+
}): globalThis.Headers;
|
|
122
44
|
};
|
|
123
45
|
|
|
124
46
|
declare function serve(options: ServerOptions): Server;
|
package/dist/adapters/node.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import NodeHttp from 'node:http';
|
|
2
2
|
import NodeHttps from 'node:https';
|
|
3
3
|
import { splitSetCookieString } from 'cookie-es';
|
|
4
|
-
import { r as resolveTLSOptions, a as
|
|
4
|
+
import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
|
|
5
5
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
6
|
+
import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
|
|
6
7
|
import 'node:fs';
|
|
7
8
|
|
|
8
9
|
async function sendNodeResponse(nodeRes, webRes) {
|
|
@@ -906,7 +907,10 @@ class NodeServer {
|
|
|
906
907
|
constructor(options) {
|
|
907
908
|
this.runtime = "node";
|
|
908
909
|
this.options = options;
|
|
909
|
-
const fetchHandler = wrapFetch(
|
|
910
|
+
const fetchHandler = wrapFetch(
|
|
911
|
+
this,
|
|
912
|
+
wrapFetchOnError(this.options.fetch, this.options.onError)
|
|
913
|
+
);
|
|
910
914
|
this.fetch = fetchHandler;
|
|
911
915
|
const handler = (nodeReq, nodeRes) => {
|
|
912
916
|
const request = new NodeRequest({ req: nodeReq, res: nodeRes });
|
|
@@ -914,9 +918,10 @@ class NodeServer {
|
|
|
914
918
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
|
|
915
919
|
};
|
|
916
920
|
const tls = resolveTLSOptions(this.options);
|
|
921
|
+
const { port, hostname: host } = resolvePortAndHost(this.options);
|
|
917
922
|
this.serveOptions = {
|
|
918
|
-
port
|
|
919
|
-
host
|
|
923
|
+
port,
|
|
924
|
+
host,
|
|
920
925
|
exclusive: !this.options.reusePort,
|
|
921
926
|
...tls ? { cert: tls.cert, key: tls.key, passphrase: tls.passphrase } : {},
|
|
922
927
|
...this.options.node
|
|
@@ -936,7 +941,10 @@ class NodeServer {
|
|
|
936
941
|
return Promise.resolve(this.#listeningPromise).then(() => this);
|
|
937
942
|
}
|
|
938
943
|
this.#listeningPromise = new Promise((resolve) => {
|
|
939
|
-
this.node.server.listen(this.serveOptions, () =>
|
|
944
|
+
this.node.server.listen(this.serveOptions, () => {
|
|
945
|
+
printListening(this.options, this.url);
|
|
946
|
+
resolve();
|
|
947
|
+
});
|
|
940
948
|
});
|
|
941
949
|
}
|
|
942
950
|
get url() {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ServerRequest, ServerOptions, Server } from '../types.mjs';
|
|
2
|
+
import 'node:http';
|
|
3
|
+
import 'node:https';
|
|
4
|
+
import 'node:net';
|
|
5
|
+
import 'bun';
|
|
6
|
+
import '@cloudflare/workers-types';
|
|
7
|
+
|
|
8
|
+
declare const Response: typeof globalThis.Response;
|
|
9
|
+
type ServiceWorkerHandler = (request: ServerRequest, event: FetchEvent) => Response | Promise<Response>;
|
|
10
|
+
declare function serve(options: ServerOptions): Server<ServiceWorkerHandler>;
|
|
11
|
+
|
|
12
|
+
export { Response, serve };
|
|
13
|
+
export type { ServiceWorkerHandler };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
2
|
+
import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
|
|
3
|
+
|
|
4
|
+
const Response = globalThis.Response;
|
|
5
|
+
const isBrowserWindow = typeof window !== "undefined" && typeof navigator !== "undefined";
|
|
6
|
+
const isServiceWorker = typeof self !== "undefined" && "skipWaiting" in self;
|
|
7
|
+
function serve(options) {
|
|
8
|
+
return new ServiceWorkerServer(options);
|
|
9
|
+
}
|
|
10
|
+
class ServiceWorkerServer {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.runtime = "service-worker";
|
|
13
|
+
this.options = options;
|
|
14
|
+
const fetchHandler = wrapFetch(
|
|
15
|
+
this,
|
|
16
|
+
wrapFetchOnError(this.options.fetch, this.options.onError)
|
|
17
|
+
);
|
|
18
|
+
this.fetch = (request, event) => {
|
|
19
|
+
Object.defineProperties(request, {
|
|
20
|
+
runtime: {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
value: { runtime: "service-worker", serviceWorker: { event } }
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return Promise.resolve(fetchHandler(request));
|
|
26
|
+
};
|
|
27
|
+
if (!options.manual) {
|
|
28
|
+
this.serve();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
#fetchListener;
|
|
32
|
+
#listeningPromise;
|
|
33
|
+
serve() {
|
|
34
|
+
if (isBrowserWindow) {
|
|
35
|
+
if (!navigator.serviceWorker) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"Service worker is not supported in the current window."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const swURL = this.options.serviceWorker?.url;
|
|
41
|
+
if (!swURL) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"Service worker URL is not provided. Please set the `serviceWorker.url` serve option or manually register."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
this.#listeningPromise = navigator.serviceWorker.register(swURL, {
|
|
47
|
+
type: "module",
|
|
48
|
+
scope: this.options.serviceWorker?.scope
|
|
49
|
+
}).then((registration) => {
|
|
50
|
+
if (registration.active) {
|
|
51
|
+
location.replace(location.href);
|
|
52
|
+
} else {
|
|
53
|
+
registration.addEventListener("updatefound", () => {
|
|
54
|
+
location.replace(location.href);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} else if (isServiceWorker) {
|
|
59
|
+
this.#fetchListener = async (event) => {
|
|
60
|
+
if (/\/[^/]*\.[a-zA-Z0-9]+$/.test(new URL(event.request.url).pathname)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const response = await this.fetch(event.request, event);
|
|
64
|
+
if (response.status !== 404) {
|
|
65
|
+
event.respondWith(response);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
addEventListener("fetch", this.#fetchListener);
|
|
69
|
+
self.addEventListener("install", () => {
|
|
70
|
+
self.skipWaiting();
|
|
71
|
+
});
|
|
72
|
+
self.addEventListener("activate", () => {
|
|
73
|
+
self.clients?.claim?.();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
ready() {
|
|
78
|
+
return Promise.resolve(this.#listeningPromise).then(() => this);
|
|
79
|
+
}
|
|
80
|
+
async close() {
|
|
81
|
+
if (this.#fetchListener) {
|
|
82
|
+
removeEventListener("fetch", this.#fetchListener);
|
|
83
|
+
}
|
|
84
|
+
if (isBrowserWindow) {
|
|
85
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
86
|
+
for (const registration of registrations) {
|
|
87
|
+
if (registration.active) {
|
|
88
|
+
await registration.unregister();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (isServiceWorker) {
|
|
92
|
+
await self.registration.unregister();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { Response, serve };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function wrapFetchOnError(fetchHandler, onError) {
|
|
2
|
+
if (!onError) return fetchHandler;
|
|
3
|
+
return (...params) => {
|
|
4
|
+
try {
|
|
5
|
+
const result = fetchHandler(...params);
|
|
6
|
+
if (result instanceof Promise) {
|
|
7
|
+
return result.catch(onError);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return onError(error);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { wrapFetchOnError as w };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
return typeof portInput === "number" ? portInput : Number.parseInt(portInput, 10);
|
|
3
|
+
function resolvePortAndHost(opts) {
|
|
4
|
+
const _port = opts.port ?? globalThis.process?.env.PORT ?? 3e3;
|
|
5
|
+
const port = typeof _port === "number" ? _port : Number.parseInt(_port, 10);
|
|
6
|
+
const hostname = opts.hostname ?? globalThis.process?.env.HOST;
|
|
7
|
+
return { port, hostname };
|
|
9
8
|
}
|
|
10
9
|
function fmtURL(host, port, secure) {
|
|
11
10
|
if (!host || !port) {
|
|
@@ -16,6 +15,25 @@ function fmtURL(host, port, secure) {
|
|
|
16
15
|
}
|
|
17
16
|
return `http${secure ? "s" : ""}://${host}:${port}/`;
|
|
18
17
|
}
|
|
18
|
+
function printListening(opts, url) {
|
|
19
|
+
if (!url || (opts.silent ?? globalThis.process?.env?.TEST)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const _url = new URL(url);
|
|
23
|
+
const allInterfaces = _url.hostname === "[::]" || _url.hostname === "0.0.0.0";
|
|
24
|
+
if (allInterfaces) {
|
|
25
|
+
_url.hostname = "localhost";
|
|
26
|
+
url = _url.href;
|
|
27
|
+
}
|
|
28
|
+
let listeningOn = `\u279C Listening on:`;
|
|
29
|
+
let additionalInfo = allInterfaces ? " (all interfaces)" : "";
|
|
30
|
+
if (globalThis.process.stdout?.isTTY) {
|
|
31
|
+
listeningOn = `\x1B[32m${listeningOn}\x1B[0m`;
|
|
32
|
+
url = `\x1B[36m${url}\x1B[0m`;
|
|
33
|
+
additionalInfo = `\x1B[2m${additionalInfo}\x1B[0m`;
|
|
34
|
+
}
|
|
35
|
+
console.log(` ${listeningOn} ${url}${additionalInfo}`);
|
|
36
|
+
}
|
|
19
37
|
function resolveTLSOptions(opts) {
|
|
20
38
|
if (!opts.tls || opts.protocol === "http") {
|
|
21
39
|
return;
|
|
@@ -54,4 +72,4 @@ function resolveCertOrKey(value) {
|
|
|
54
72
|
return readFileSync(value, "utf8");
|
|
55
73
|
}
|
|
56
74
|
|
|
57
|
-
export {
|
|
75
|
+
export { resolvePortAndHost as a, fmtURL as f, printListening as p, resolveTLSOptions as r };
|
package/dist/types.d.mts
CHANGED
|
@@ -60,6 +60,10 @@ interface ServerOptions {
|
|
|
60
60
|
* If `protocol` is not set, Server will use `http` as the default protocol or `https` if both `tls.cert` and `tls.key` options are provided.
|
|
61
61
|
*/
|
|
62
62
|
protocol?: "http" | "https";
|
|
63
|
+
/**
|
|
64
|
+
* If set to `true`, server will not print the listening address.
|
|
65
|
+
*/
|
|
66
|
+
silent?: boolean;
|
|
63
67
|
/**
|
|
64
68
|
* TLS server options.
|
|
65
69
|
*/
|
|
@@ -77,6 +81,12 @@ interface ServerOptions {
|
|
|
77
81
|
*/
|
|
78
82
|
passphrase?: string;
|
|
79
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Runtime agnostic error handler (optional).
|
|
86
|
+
*
|
|
87
|
+
* @note This handler will take precedence over runtime specific error handlers.
|
|
88
|
+
*/
|
|
89
|
+
onError?: ErrorHandler;
|
|
80
90
|
/**
|
|
81
91
|
* Node.js server options.
|
|
82
92
|
*/
|
|
@@ -93,12 +103,26 @@ interface ServerOptions {
|
|
|
93
103
|
* @docs https://docs.deno.com/api/deno/~/Deno.serve
|
|
94
104
|
*/
|
|
95
105
|
deno?: Deno.ServeOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Service worker options
|
|
108
|
+
*/
|
|
109
|
+
serviceWorker?: {
|
|
110
|
+
/**
|
|
111
|
+
* The path to the service worker file to be registered.
|
|
112
|
+
*/
|
|
113
|
+
url?: string;
|
|
114
|
+
/**
|
|
115
|
+
* The scope of the service worker.
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
scope?: string;
|
|
119
|
+
};
|
|
96
120
|
}
|
|
97
121
|
interface Server<Handler = ServerHandler> {
|
|
98
122
|
/**
|
|
99
123
|
* Current runtime name
|
|
100
124
|
*/
|
|
101
|
-
readonly runtime: "node" | "deno" | "bun" | "cloudflare";
|
|
125
|
+
readonly runtime: "node" | "deno" | "bun" | "cloudflare" | "service-worker" | "generic";
|
|
102
126
|
/**
|
|
103
127
|
* Server options
|
|
104
128
|
*/
|
|
@@ -201,9 +225,11 @@ interface ServerRequest extends Request {
|
|
|
201
225
|
ip?: string | undefined;
|
|
202
226
|
}
|
|
203
227
|
type FetchHandler = (request: Request) => Response | Promise<Response>;
|
|
228
|
+
type ErrorHandler = (error: unknown) => Response | Promise<Response>;
|
|
204
229
|
type BunFetchHandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
|
|
205
230
|
type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
|
|
206
231
|
type NodeHttpHandler = (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
|
|
207
232
|
type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
|
|
208
233
|
|
|
209
|
-
export {
|
|
234
|
+
export { Response, serve };
|
|
235
|
+
export type { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FetchHandler, NodeHttpHandler, Server, ServerHandler, ServerOptions, ServerPlugin, ServerPluginInstance, ServerRequest, ServerRuntimeContext };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srvx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
|
|
5
|
-
"repository": "
|
|
5
|
+
"repository": "h3js/srvx",
|
|
6
|
+
"homepage": "https://srvx.h3.dev",
|
|
6
7
|
"license": "MIT",
|
|
7
8
|
"sideEffects": false,
|
|
8
9
|
"type": "module",
|
|
@@ -12,12 +13,16 @@
|
|
|
12
13
|
"./bun": "./dist/adapters/bun.mjs",
|
|
13
14
|
"./node": "./dist/adapters/node.mjs",
|
|
14
15
|
"./cloudflare": "./dist/adapters/cloudflare.mjs",
|
|
16
|
+
"./generic": "./dist/adapters/generic.mjs",
|
|
17
|
+
"./service-worker": "./dist/adapters/service-worker.mjs",
|
|
15
18
|
".": {
|
|
16
19
|
"types": "./dist/types.d.mts",
|
|
17
20
|
"deno": "./dist/adapters/deno.mjs",
|
|
18
21
|
"bun": "./dist/adapters/bun.mjs",
|
|
19
22
|
"workerd": "./dist/adapters/cloudflare.mjs",
|
|
20
|
-
"
|
|
23
|
+
"browser": "./dist/adapters/service-worker.mjs",
|
|
24
|
+
"node": "./dist/adapters/node.mjs",
|
|
25
|
+
"default": "./dist/adapters/generic.mjs"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"types": "./dist/types.d.mts",
|
|
@@ -36,6 +41,7 @@
|
|
|
36
41
|
"play:deno": "deno run -A playground/app.mjs",
|
|
37
42
|
"play:mkcert": "openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj /CN=srvx.local",
|
|
38
43
|
"play:node": "node playground/app.mjs",
|
|
44
|
+
"play:sw": "pnpm build && pnpx serve playground",
|
|
39
45
|
"release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
|
|
40
46
|
"test": "pnpm lint && pnpm test:types && vitest run --coverage",
|
|
41
47
|
"test:types": "tsc --noEmit --skipLibCheck"
|
|
@@ -47,26 +53,27 @@
|
|
|
47
53
|
"cookie-es": "^2.0.0"
|
|
48
54
|
},
|
|
49
55
|
"devDependencies": {
|
|
50
|
-
"@cloudflare/workers-types": "^4.
|
|
51
|
-
"@hono/node-server": "^1.14.
|
|
56
|
+
"@cloudflare/workers-types": "^4.20250423.0",
|
|
57
|
+
"@hono/node-server": "^1.14.1",
|
|
52
58
|
"@mjackson/node-fetch-server": "^0.6.1",
|
|
53
|
-
"@types/bun": "^1.2.
|
|
59
|
+
"@types/bun": "^1.2.10",
|
|
54
60
|
"@types/deno": "^2.2.0",
|
|
55
|
-
"@types/node": "^22.
|
|
56
|
-
"@
|
|
61
|
+
"@types/node": "^22.14.1",
|
|
62
|
+
"@types/serviceworker": "^0.0.132",
|
|
63
|
+
"@vitest/coverage-v8": "^3.1.2",
|
|
57
64
|
"automd": "^0.4.0",
|
|
58
65
|
"changelogen": "^0.6.1",
|
|
59
|
-
"eslint": "^9.
|
|
66
|
+
"eslint": "^9.25.1",
|
|
60
67
|
"eslint-config-unjs": "^0.4.2",
|
|
61
68
|
"execa": "^9.5.2",
|
|
62
69
|
"get-port-please": "^3.1.2",
|
|
63
70
|
"jiti": "^2.4.2",
|
|
64
71
|
"prettier": "^3.5.3",
|
|
65
|
-
"typescript": "^5.8.
|
|
72
|
+
"typescript": "^5.8.3",
|
|
66
73
|
"unbuild": "^3.5.0",
|
|
67
|
-
"vitest": "^3.
|
|
74
|
+
"vitest": "^3.1.2"
|
|
68
75
|
},
|
|
69
|
-
"packageManager": "pnpm@10.
|
|
76
|
+
"packageManager": "pnpm@10.9.0",
|
|
70
77
|
"engines": {
|
|
71
78
|
"node": ">=20.11.1"
|
|
72
79
|
}
|