srvx 0.1.3 → 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/dist/types.d.mts CHANGED
@@ -1,62 +1,7 @@
1
1
  import * as NodeHttp from 'node:http';
2
2
  import * as NodeNet from 'node:net';
3
- import * as BunTypes from 'bun';
4
- import BunTypes__default from 'bun';
5
-
6
- declare abstract class Server {
7
- #private;
8
- /**
9
- * Current listening runtime name
10
- */
11
- abstract readonly runtime: "node" | "deno" | "bun";
12
- /**
13
- * Node.js listening server instance.
14
- */
15
- nodeServer?: NodeHttp.Server;
16
- /**
17
- * Bun listening server instance.
18
- */
19
- bunServer?: BunTypes__default.Server;
20
- /**
21
- * Deno listening server instance.
22
- */
23
- denoServer?: Deno.HttpServer;
24
- /**
25
- * Server options
26
- */
27
- options: Omit<ServerOptions, "fetch">;
28
- /**
29
- * Server fetch handler
30
- */
31
- fetch: ServerHandler;
32
- constructor(options: ServerOptions);
33
- protected abstract _listen(): void | Promise<void>;
34
- /**
35
- * Listening address (hostname or IP).
36
- */
37
- abstract readonly addr: string | null;
38
- /**
39
- * Listening port.
40
- */
41
- abstract readonly port: number | null;
42
- /**
43
- * Listening URL.
44
- */
45
- get url(): string | null;
46
- /**
47
- * Returns a promise that resolves when the server is ready.
48
- */
49
- ready(): Promise<Server>;
50
- /**
51
- * Stop listening to prevent new connections from being accepted.
52
- *
53
- * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
54
- *
55
- * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
56
- * @default false
57
- */
58
- abstract close(closeActiveConnections?: boolean): void | Promise<void>;
59
- }
3
+ import * as Bun from 'bun';
4
+ import * as CF from '@cloudflare/workers-types';
60
5
 
61
6
  type MaybePromise<T> = T | Promise<T>;
62
7
  /**
@@ -66,7 +11,7 @@ declare function serve(options: ServerOptions): Server;
66
11
  /**
67
12
  * Web fetch compatible request handler
68
13
  */
69
- type ServerHandler = (request: xRequest) => MaybePromise<Response>;
14
+ type ServerHandler = (request: ServerRequest) => MaybePromise<Response>;
70
15
  /**
71
16
  * Server options
72
17
  */
@@ -76,9 +21,13 @@ interface ServerOptions {
76
21
  */
77
22
  fetch: ServerHandler;
78
23
  /**
79
- * Server plugins
24
+ * Server plugins.
80
25
  */
81
26
  plugins?: (ServerPlugin | ServerPluginInstance)[];
27
+ /**
28
+ * If set to `true`, server will not start listening automatically.
29
+ */
30
+ manual?: boolean;
82
31
  /**
83
32
  * The port server should be listening to.
84
33
  *
@@ -102,22 +51,7 @@ interface ServerOptions {
102
51
  */
103
52
  reusePort?: boolean;
104
53
  /**
105
- * If this option is enabled, `request.xRemoteAddress` will be available.
106
- * **Example:**
107
- *
108
- * ```js
109
- * serve({
110
- * port: 3000,
111
- * xRemoteAddress: true,
112
- * fetch: (request) =>
113
- * new Response(`Your ip address is ${request.xRemoteAddress}`),
114
- * });
115
- * ```
116
- * **Note:** In order to to provide cross-runtime consistency, a small wrapper function will be enabled for Deno and Bun runtimes if `xRemoteAddress` option is set.
117
- */
118
- xRemoteAddress?: boolean;
119
- /**
120
- * Node.js http server options.
54
+ * Node.js server options.
121
55
  */
122
56
  node?: NodeHttp.ServerOptions & NodeNet.ListenOptions;
123
57
  /**
@@ -125,7 +59,7 @@ interface ServerOptions {
125
59
  *
126
60
  * @docs https://bun.sh/docs/api/http
127
61
  */
128
- bun?: Omit<BunTypes.ServeOptions, "fetch">;
62
+ bun?: Omit<Bun.ServeOptions, "fetch">;
129
63
  /**
130
64
  * Deno server options
131
65
  *
@@ -133,7 +67,57 @@ interface ServerOptions {
133
67
  */
134
68
  deno?: Deno.ServeOptions;
135
69
  }
136
- type ServerPlugin = (server: Server) => MaybePromise<ServerPluginInstance>;
70
+ interface Server<Handler = ServerHandler> {
71
+ /**
72
+ * Current runtime name
73
+ */
74
+ readonly runtime: "node" | "deno" | "bun" | "cloudflare";
75
+ /**
76
+ * Server options
77
+ */
78
+ readonly options: ServerOptions;
79
+ /**
80
+ * Server URL address.
81
+ */
82
+ readonly url?: string;
83
+ /**
84
+ * Node.js context.
85
+ */
86
+ readonly node?: {
87
+ server?: NodeHttp.Server;
88
+ handler: (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
89
+ };
90
+ /**
91
+ * Bun context.
92
+ */
93
+ readonly bun?: {
94
+ server?: Bun.Server;
95
+ };
96
+ /**
97
+ * Deno context.
98
+ */
99
+ readonly deno?: {
100
+ server?: Deno.HttpServer;
101
+ };
102
+ /**
103
+ * Server fetch handler
104
+ */
105
+ readonly fetch: Handler;
106
+ /**
107
+ * Returns a promise that resolves when the server is ready.
108
+ */
109
+ ready(): Promise<Server<Handler>>;
110
+ /**
111
+ * Stop listening to prevent new connections from being accepted.
112
+ *
113
+ * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
114
+ *
115
+ * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
116
+ * @default false
117
+ */
118
+ close(closeActiveConnections?: boolean): Promise<void>;
119
+ }
120
+ type ServerPlugin = (server: Server) => ServerPluginInstance;
137
121
  interface ServerPluginInstance {
138
122
  /**
139
123
  * Plugin display name
@@ -143,33 +127,44 @@ interface ServerPluginInstance {
143
127
  * Hook to allow running logic before user fetch handler
144
128
  * If an response value is returned, user fetch handler and the next plugins will be skipped.
145
129
  */
146
- request?: (request: xRequest) => MaybePromise<Response | void>;
130
+ request?: (request: ServerRequest) => MaybePromise<Response | void>;
147
131
  /**
148
132
  * Hook to allow running logic after user fetch handler
149
133
  * If a response value is returned, user response and the next plugins will be skipped.
150
134
  */
151
- response?: (request: xRequest, response: Response) => MaybePromise<void | Response>;
135
+ response?: (request: ServerRequest, response: Response) => MaybePromise<void | Response>;
152
136
  }
153
- /**
154
- * https://developer.mozilla.org/en-US/docs/Web/API/Headers
155
- */
156
- type xHeaders = Omit<Headers, "count" | "toJSON" | "getAll">;
157
- /**
158
- * https://developer.mozilla.org/en-US/docs/Web/API/Request
159
- */
160
- interface xRequest extends Omit<Request, "headers" | "clone"> {
161
- headers: xHeaders;
162
- clone(): xRequest;
163
- xNode?: {
137
+ interface ServerRequest extends Request {
138
+ /**
139
+ * Remote address of the client.
140
+ */
141
+ remoteAddress?: string | undefined;
142
+ /**
143
+ * Underlying Node.js server request info.
144
+ */
145
+ node?: {
164
146
  req: NodeHttp.IncomingMessage;
165
147
  res: NodeHttp.ServerResponse;
166
148
  };
167
149
  /**
168
- * Remote address of the client.
150
+ * Underlying Deno server request info.
151
+ */
152
+ deno?: {
153
+ info: Deno.ServeHandlerInfo<Deno.NetAddr>;
154
+ };
155
+ /**
156
+ * Underlying Bun server request context.
169
157
  */
170
- xRemoteAddress?: string | null;
171
- /** Optional user context */
172
- xContext?: Record<string, unknown>;
158
+ bun?: {
159
+ server: Bun.Server;
160
+ };
161
+ /**
162
+ * Underlying Cloudflare request context.
163
+ */
164
+ cloudflare?: {
165
+ env: unknown;
166
+ context: CF.ExecutionContext;
167
+ };
173
168
  }
174
169
 
175
- export { Server, type ServerHandler, type ServerOptions, type ServerPlugin, type ServerPluginInstance, serve, type xHeaders, type xRequest };
170
+ export { type Server, type ServerHandler, type ServerOptions, type ServerPlugin, type ServerPluginInstance, type ServerRequest, serve };
package/package.json CHANGED
@@ -1,28 +1,24 @@
1
1
  {
2
2
  "name": "srvx",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
5
5
  "repository": "unjs/srvx",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
8
8
  "type": "module",
9
9
  "exports": {
10
+ "./types": "./dist/types.d.mts",
11
+ "./deno": "./dist/adapters/deno.mjs",
12
+ "./bun": "./dist/adapters/bun.mjs",
13
+ "./node": "./dist/adapters/node.mjs",
14
+ "./cloudflare": "./dist/adapters/cloudflare.mjs",
10
15
  ".": {
11
- "types": "./dist/types.d.mts",
12
- "bun": "./dist/bun.mjs",
13
- "deno": "./dist/deno.mjs",
14
- "node": "./dist/node.mjs"
15
- },
16
- "./node-utils": {
17
- "types": "./dist/node-utils/index.d.mts",
18
- "import": "./dist/node-utils/index.mjs"
19
- },
20
- "./types": {
21
- "types": "./dist/types.d.mts",
22
- "import": "./dist/types.mjs"
16
+ "deno": "./dist/adapters/deno.mjs",
17
+ "bun": "./dist/adapters/bun.mjs",
18
+ "workerd": "./dist/adapters/cloudflare.mjs",
19
+ "node": "./dist/adapters/node.mjs"
23
20
  }
24
21
  },
25
- "types": "./dist/types.d.mts",
26
22
  "files": [
27
23
  "dist"
28
24
  ],
@@ -32,33 +28,40 @@
32
28
  "dev": "vitest dev",
33
29
  "lint": "eslint . && prettier -c .",
34
30
  "lint:fix": "automd && eslint . --fix && prettier -w .",
31
+ "play:node": "node playground/app.mjs",
32
+ "play:deno": "deno run -A playground/app.mjs",
33
+ "play:bun": "bun playground/app.mjs",
34
+ "play:cf": "pnpx wrangler dev playground/app.mjs",
35
35
  "prepack": "pnpm build",
36
36
  "release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
37
37
  "test": "pnpm lint && pnpm test:types && vitest run --coverage",
38
38
  "test:types": "tsc --noEmit --skipLibCheck"
39
39
  },
40
+ "resolutions": {
41
+ "srvx": "link:."
42
+ },
40
43
  "dependencies": {
41
- "cookie-es": "^1.2.2"
44
+ "cookie-es": "^2.0.0"
42
45
  },
43
46
  "devDependencies": {
44
- "@hono/node-server": "^1.13.7",
45
- "@mjackson/node-fetch-server": "^0.4.1",
46
- "@types/bun": "^1.1.14",
47
- "@types/deno": "^2.0.0",
48
- "@types/node": "^22.10.1",
49
- "@vitest/coverage-v8": "^2.1.8",
50
- "automd": "^0.3.12",
51
- "changelogen": "^0.5.7",
52
- "eslint": "^9.16.0",
47
+ "@cloudflare/workers-types": "^4.20250303.0",
48
+ "@hono/node-server": "^1.13.8",
49
+ "@mjackson/node-fetch-server": "^0.6.1",
50
+ "@types/bun": "^1.2.4",
51
+ "@types/deno": "^2.2.0",
52
+ "@types/node": "^22.13.10",
53
+ "@vitest/coverage-v8": "^3.0.8",
54
+ "automd": "^0.4.0",
55
+ "changelogen": "^0.6.1",
56
+ "eslint": "^9.22.0",
53
57
  "eslint-config-unjs": "^0.4.2",
54
- "execa": "^9.5.1",
58
+ "execa": "^9.5.2",
55
59
  "get-port-please": "^3.1.2",
56
- "jiti": "^2.4.1",
57
- "prettier": "^3.4.1",
58
- "srvx": "^0.1.1",
59
- "typescript": "^5.7.2",
60
- "unbuild": "^2.0.0",
61
- "vitest": "^2.1.8"
60
+ "jiti": "^2.4.2",
61
+ "prettier": "^3.5.3",
62
+ "typescript": "^5.8.2",
63
+ "unbuild": "^3.5.0",
64
+ "vitest": "^3.0.8"
62
65
  },
63
- "packageManager": "pnpm@9.14.4"
66
+ "packageManager": "pnpm@10.6.2"
64
67
  }
package/dist/bun.d.mts DELETED
@@ -1,8 +0,0 @@
1
- import { ServerOptions, Server } from './types.mjs';
2
- import 'node:http';
3
- import 'node:net';
4
- import 'bun';
5
-
6
- declare function serve(options: ServerOptions): Server;
7
-
8
- export { serve };
package/dist/bun.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { ServerOptions, Server } from './types.js';
2
- import 'node:http';
3
- import 'node:net';
4
- import 'bun';
5
-
6
- declare function serve(options: ServerOptions): Server;
7
-
8
- export { serve };
package/dist/bun.mjs DELETED
@@ -1,42 +0,0 @@
1
- import { S as Server, r as resolvePort } from './shared/srvx.4f681732.mjs';
2
-
3
- function serve(options) {
4
- return new BunServer(options);
5
- }
6
- class BunServer extends Server {
7
- constructor() {
8
- super(...arguments);
9
- this.runtime = "bun";
10
- }
11
- _listen() {
12
- let serverFetch = this.fetch;
13
- if (this.options.xRemoteAddress) {
14
- const userFetch = this.fetch;
15
- serverFetch = (request) => {
16
- Object.defineProperty(request, "xRemoteAddress", {
17
- get: () => this.bunServer?.requestIP(request)?.address,
18
- enumerable: true
19
- });
20
- return userFetch(request);
21
- };
22
- }
23
- this.bunServer = Bun.serve({
24
- port: resolvePort(this.options.port, globalThis.process?.env.PORT),
25
- hostname: this.options.hostname,
26
- reusePort: this.options.reusePort,
27
- ...this.options.bun,
28
- fetch: serverFetch
29
- });
30
- }
31
- get port() {
32
- return this.bunServer?.port ?? null;
33
- }
34
- get addr() {
35
- return this.bunServer?.hostname ?? null;
36
- }
37
- close(closeAll) {
38
- this.bunServer?.stop(closeAll);
39
- }
40
- }
41
-
42
- export { serve };
package/dist/deno.d.mts DELETED
@@ -1,8 +0,0 @@
1
- import { ServerOptions, Server } from './types.mjs';
2
- import 'node:http';
3
- import 'node:net';
4
- import 'bun';
5
-
6
- declare function serve(options: ServerOptions): Server;
7
-
8
- export { serve };
package/dist/deno.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { ServerOptions, Server } from './types.js';
2
- import 'node:http';
3
- import 'node:net';
4
- import 'bun';
5
-
6
- declare function serve(options: ServerOptions): Server;
7
-
8
- export { serve };
package/dist/deno.mjs DELETED
@@ -1,56 +0,0 @@
1
- import { S as Server, r as resolvePort } from './shared/srvx.4f681732.mjs';
2
-
3
- function serve(options) {
4
- return new DenoServer(options);
5
- }
6
- class DenoServer extends Server {
7
- constructor() {
8
- super(...arguments);
9
- this.runtime = "deno";
10
- }
11
- #listeningInfo;
12
- _listen() {
13
- const onListenPromise = Promise.withResolvers();
14
- let serverFetch = this.fetch;
15
- if (this.options.xRemoteAddress) {
16
- const userFetch = serverFetch;
17
- serverFetch = (request, info) => {
18
- Object.defineProperty(request, "xRemoteAddress", {
19
- get: () => info?.remoteAddr?.hostname,
20
- enumerable: true
21
- });
22
- return userFetch(request);
23
- };
24
- }
25
- this.denoServer = Deno.serve(
26
- {
27
- port: resolvePort(
28
- this.options.port,
29
- globalThis.Deno?.env.get("PORT")
30
- ),
31
- hostname: this.options.hostname,
32
- reusePort: this.options.reusePort,
33
- ...this.options.deno,
34
- onListen: (info) => {
35
- if (this.options.deno?.onListen) {
36
- this.options.deno.onListen(info);
37
- }
38
- this.#listeningInfo = info;
39
- onListenPromise.resolve();
40
- }
41
- },
42
- serverFetch
43
- );
44
- }
45
- get port() {
46
- return this.#listeningInfo?.port ?? null;
47
- }
48
- get addr() {
49
- return this.#listeningInfo?.hostname ?? null;
50
- }
51
- close(_closeAll) {
52
- this.denoServer?.shutdown();
53
- }
54
- }
55
-
56
- export { serve };
@@ -1,138 +0,0 @@
1
- import NodeHttp__default from 'node:http';
2
- import { xHeaders, xRequest } from '../types.mjs';
3
- import 'node:net';
4
- import 'bun';
5
-
6
- declare const kNodeReq: unique symbol;
7
- declare const kNodeRes: unique symbol;
8
- declare const kNodeInspect: unique symbol;
9
-
10
- declare const NodeRequestProxy: {
11
- new (nodeReq: NodeHttp__default.IncomingMessage): {
12
- cache: RequestCache;
13
- credentials: RequestCredentials;
14
- destination: RequestDestination;
15
- integrity: string;
16
- keepalive: boolean;
17
- mode: RequestMode;
18
- redirect: RequestRedirect;
19
- referrer: string;
20
- referrerPolicy: ReferrerPolicy;
21
- headers: xHeaders;
22
- bodyUsed: boolean;
23
- _url: URL;
24
- "__#448@#abortSignal"?: AbortController;
25
- "__#448@#hasBody": boolean | undefined;
26
- "__#448@#bodyBytes"?: Promise<Uint8Array>;
27
- "__#448@#blobBody"?: Promise<Blob>;
28
- "__#448@#formDataBody"?: Promise<FormData>;
29
- "__#448@#jsonBody"?: Promise<any>;
30
- "__#448@#textBody"?: Promise<string>;
31
- "__#448@#bodyStream"?: undefined | ReadableStream<Uint8Array>;
32
- readonly xRemoteAddress: string | undefined;
33
- clone(): xRequest;
34
- readonly url: string;
35
- readonly method: string;
36
- readonly signal: AbortSignal;
37
- readonly _hasBody: boolean;
38
- readonly body: ReadableStream<Uint8Array<ArrayBufferLike>> | null;
39
- bytes(): Promise<Uint8Array>;
40
- arrayBuffer(): Promise<ArrayBuffer>;
41
- blob(): Promise<Blob>;
42
- formData(): Promise<FormData>;
43
- text(): Promise<string>;
44
- json(): Promise<any>;
45
- [kNodeReq]: NodeHttp__default.IncomingMessage;
46
- readonly [Symbol.toStringTag]: string;
47
- [kNodeInspect](): {
48
- method: string;
49
- url: string;
50
- headers: xHeaders;
51
- };
52
- };
53
- };
54
-
55
- type NodeFastResponse = InstanceType<typeof NodeFastResponse>;
56
- declare const NodeFastResponse: {
57
- new (body?: BodyInit | null, init?: ResponseInit): {
58
- "__#449@#body"?: BodyInit | null;
59
- "__#449@#init"?: ResponseInit;
60
- /**
61
- * Prepare Node.js response object
62
- */
63
- xNodeResponse(): {
64
- status: number;
65
- statusText: string;
66
- headers: NodeHttp__default.OutgoingHttpHeader[];
67
- body: string | Uint8Array<ArrayBufferLike> | ReadableStream<Uint8Array<ArrayBufferLike>> | Buffer<ArrayBufferLike> | DataView<ArrayBufferLike> | null | undefined;
68
- };
69
- /** Lazy initialized response instance */
70
- "__#449@#responseObj"?: Response;
71
- /** Lazy initialized headers instance */
72
- "__#449@#headersObj"?: Headers;
73
- clone(): Response;
74
- readonly "__#449@#response": Response;
75
- readonly headers: Headers;
76
- readonly ok: boolean;
77
- readonly redirected: boolean;
78
- readonly status: number;
79
- readonly statusText: string;
80
- readonly type: ResponseType;
81
- readonly url: string;
82
- "__#449@#fastBody"<T extends object>(as: new (...args: any[]) => T): T | null | false;
83
- readonly body: ReadableStream<Uint8Array> | null;
84
- readonly bodyUsed: boolean;
85
- arrayBuffer(): Promise<ArrayBuffer>;
86
- blob(): Promise<Blob>;
87
- bytes(): Promise<Uint8Array>;
88
- formData(): Promise<FormData>;
89
- text(): Promise<string>;
90
- json(): Promise<any>;
91
- };
92
- };
93
-
94
- declare const NodeReqHeadersProxy: {
95
- new (req: NodeHttp__default.IncomingMessage): {
96
- append(name: string, value: string): void;
97
- delete(name: string): void;
98
- get(name: string): string | null;
99
- getSetCookie(): string[];
100
- has(name: string): boolean;
101
- set(name: string, value: string): void;
102
- toJSON(): Record<string, string>;
103
- forEach(cb: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
104
- entries(): HeadersIterator<[string, string]>;
105
- keys(): HeadersIterator<string>;
106
- values(): HeadersIterator<string>;
107
- [kNodeReq]: NodeHttp__default.IncomingMessage;
108
- [Symbol.iterator](): HeadersIterator<[string, string]>;
109
- readonly [Symbol.toStringTag]: string;
110
- [kNodeInspect](): {
111
- [k: string]: string;
112
- };
113
- };
114
- };
115
- declare const NodeResHeadersProxy: {
116
- new (res: NodeHttp__default.ServerResponse): {
117
- append(name: string, value: string): void;
118
- delete(name: string): void;
119
- get(name: string): string | null;
120
- getSetCookie(): string[];
121
- has(name: string): boolean;
122
- set(name: string, value: string): void;
123
- forEach(cb: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
124
- entries(): HeadersIterator<[string, string]>;
125
- keys(): HeadersIterator<string>;
126
- values(): HeadersIterator<string>;
127
- [kNodeRes]: NodeHttp__default.ServerResponse;
128
- [Symbol.iterator](): HeadersIterator<[string, string]>;
129
- readonly [Symbol.toStringTag]: string;
130
- [kNodeInspect](): {
131
- [k: string]: string;
132
- };
133
- };
134
- };
135
-
136
- declare function sendNodeResponse(nodeRes: NodeHttp__default.ServerResponse, webRes: Response | NodeFastResponse): Promise<void>;
137
-
138
- export { NodeFastResponse, NodeReqHeadersProxy, NodeRequestProxy, NodeResHeadersProxy, sendNodeResponse };