open-nest 0.0.2 → 0.1.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/{index-DCLeOkkF.d.mts → index.d.mts} +54 -9
- package/dist/{fetch-Bmr9TEe3.mjs → index.mjs} +89 -1
- package/package.json +8 -56
- package/readme.md +108 -86
- package/dist/bun.d.mts +0 -48
- package/dist/bun.mjs +0 -100
- package/dist/fetch.d.mts +0 -2
- package/dist/fetch.mjs +0 -2
- package/dist/gateway-CrNeg78D.d.mts +0 -45
- package/dist/gateway-D7PGV2DW.mjs +0 -89
- package/dist/node.d.mts +0 -53
- package/dist/node.mjs +0 -190
- package/dist/workers.d.mts +0 -38
- package/dist/workers.mjs +0 -28
|
@@ -128,12 +128,57 @@ interface MultipartLimits {
|
|
|
128
128
|
interface MultipartOptions {
|
|
129
129
|
limits?: MultipartLimits;
|
|
130
130
|
}
|
|
131
|
-
declare function FileInterceptor(field: string, options?: MultipartOptions): Type<NestInterceptor>;
|
|
132
|
-
declare function FilesInterceptor(field: string, maxCount?: number, options?: MultipartOptions): Type<NestInterceptor>;
|
|
133
|
-
declare function AnyFilesInterceptor(options?: MultipartOptions): Type<NestInterceptor>;
|
|
131
|
+
export declare function FileInterceptor(field: string, options?: MultipartOptions): Type<NestInterceptor>;
|
|
132
|
+
export declare function FilesInterceptor(field: string, maxCount?: number, options?: MultipartOptions): Type<NestInterceptor>;
|
|
133
|
+
export declare function AnyFilesInterceptor(options?: MultipartOptions): Type<NestInterceptor>;
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/lib/gateway.d.ts
|
|
136
|
+
interface GatewayClient {
|
|
137
|
+
send(data: string): void;
|
|
138
|
+
data: {
|
|
139
|
+
path: string;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
interface Gateway {
|
|
143
|
+
path: string;
|
|
144
|
+
connect?: (client: GatewayClient) => void;
|
|
145
|
+
}
|
|
146
|
+
interface MessageHandler {
|
|
147
|
+
message: string;
|
|
148
|
+
callback: (data: unknown) => unknown;
|
|
149
|
+
}
|
|
150
|
+
interface Subscription {
|
|
151
|
+
unsubscribe(): void;
|
|
152
|
+
}
|
|
153
|
+
interface ResultStream {
|
|
154
|
+
subscribe(observer: {
|
|
155
|
+
next: (value: unknown) => void;
|
|
156
|
+
error?: (error: unknown) => void;
|
|
157
|
+
}): Subscription;
|
|
158
|
+
}
|
|
159
|
+
export declare function gatewayPath(path: string): string;
|
|
160
|
+
/**
|
|
161
|
+
* Records Nest gateways during `init`. The runtime server performs the upgrade.
|
|
162
|
+
*/
|
|
163
|
+
export declare class WsGateway<T extends GatewayClient = GatewayClient> {
|
|
164
|
+
private readonly gateways;
|
|
165
|
+
private readonly clients;
|
|
166
|
+
create(_port: number, options?: {
|
|
167
|
+
path?: string;
|
|
168
|
+
}): Gateway;
|
|
169
|
+
bindClientConnect(server: Gateway, callback: (client: T) => void): void;
|
|
170
|
+
bindMessageHandlers(client: T, handlers: MessageHandler[], transform: (result: unknown) => ResultStream): void;
|
|
171
|
+
bindClientDisconnect(client: T, callback: () => void): void;
|
|
172
|
+
close(): void;
|
|
173
|
+
matches(pathname: string): boolean;
|
|
174
|
+
hasGateways(): boolean;
|
|
175
|
+
open(client: T): void;
|
|
176
|
+
receive(client: T, raw: string | Uint8Array): void;
|
|
177
|
+
closed(client: T): void;
|
|
178
|
+
}
|
|
134
179
|
//#endregion
|
|
135
180
|
//#region src/fetch/index.d.ts
|
|
136
|
-
interface CorsOptions {
|
|
181
|
+
export interface CorsOptions {
|
|
137
182
|
origin?: boolean | string | RegExp | Array<string | RegExp> | ((origin: string) => boolean);
|
|
138
183
|
methods?: string | string[];
|
|
139
184
|
allowedHeaders?: string | string[];
|
|
@@ -144,7 +189,7 @@ interface CorsOptions {
|
|
|
144
189
|
/**
|
|
145
190
|
* Options for {@link createFetcher}.
|
|
146
191
|
*/
|
|
147
|
-
interface CreateHandlerOptions {
|
|
192
|
+
export interface CreateHandlerOptions {
|
|
148
193
|
/**
|
|
149
194
|
* Root Nest module.
|
|
150
195
|
*/
|
|
@@ -162,7 +207,7 @@ interface CreateHandlerOptions {
|
|
|
162
207
|
*/
|
|
163
208
|
bodyLimit?: number;
|
|
164
209
|
}
|
|
165
|
-
interface Fetcher {
|
|
210
|
+
export interface Fetcher {
|
|
166
211
|
(request: Request): Promise<Response>;
|
|
167
212
|
/**
|
|
168
213
|
* Resolves the underlying Nest app instance.
|
|
@@ -173,7 +218,7 @@ interface Fetcher {
|
|
|
173
218
|
*/
|
|
174
219
|
close(): Promise<void>;
|
|
175
220
|
}
|
|
176
|
-
declare class FetchAdapter extends AbstractHttpAdapter<undefined, FetchRequest, FetchResponse> {
|
|
221
|
+
export declare class FetchAdapter extends AbstractHttpAdapter<undefined, FetchRequest, FetchResponse> {
|
|
177
222
|
private bodyLimit;
|
|
178
223
|
private rawBody;
|
|
179
224
|
private shuttingDown;
|
|
@@ -212,6 +257,6 @@ declare class FetchAdapter extends AbstractHttpAdapter<undefined, FetchRequest,
|
|
|
212
257
|
enableCors(options?: CorsOptions): void;
|
|
213
258
|
setBodyLimit(limit: number): void;
|
|
214
259
|
}
|
|
215
|
-
declare function createFetcher({ module, configure, opts, bodyLimit }: CreateHandlerOptions, adapter?: FetchAdapter): Fetcher;
|
|
260
|
+
export declare function createFetcher({ module, configure, opts, bodyLimit }: CreateHandlerOptions, adapter?: FetchAdapter): Fetcher;
|
|
216
261
|
//#endregion
|
|
217
|
-
export {
|
|
262
|
+
export type { GatewayClient, MultipartLimits, MultipartOptions };
|
|
@@ -664,6 +664,94 @@ function AnyFilesInterceptor(options) {
|
|
|
664
664
|
return mixin(MixinInterceptor);
|
|
665
665
|
}
|
|
666
666
|
//#endregion
|
|
667
|
+
//#region src/lib/gateway.ts
|
|
668
|
+
function gatewayPath(path) {
|
|
669
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
670
|
+
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
|
671
|
+
return path;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Records Nest gateways during `init`. The runtime server performs the upgrade.
|
|
675
|
+
*/
|
|
676
|
+
var WsGateway = class {
|
|
677
|
+
gateways = [];
|
|
678
|
+
clients = /* @__PURE__ */ new WeakMap();
|
|
679
|
+
create(_port, options) {
|
|
680
|
+
const gateway = { path: gatewayPath(options?.path ?? "/") };
|
|
681
|
+
this.gateways.push(gateway);
|
|
682
|
+
return gateway;
|
|
683
|
+
}
|
|
684
|
+
bindClientConnect(server, callback) {
|
|
685
|
+
server.connect = callback;
|
|
686
|
+
}
|
|
687
|
+
bindMessageHandlers(client, handlers, transform) {
|
|
688
|
+
const prev = this.clients.get(client);
|
|
689
|
+
this.clients.set(client, {
|
|
690
|
+
handlers,
|
|
691
|
+
transform,
|
|
692
|
+
subscriptions: prev?.subscriptions ?? [],
|
|
693
|
+
onClose: prev?.onClose
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
bindClientDisconnect(client, callback) {
|
|
697
|
+
const state = this.clients.get(client);
|
|
698
|
+
if (!state) return;
|
|
699
|
+
state.onClose = callback;
|
|
700
|
+
}
|
|
701
|
+
close() {}
|
|
702
|
+
matches(pathname) {
|
|
703
|
+
const path = gatewayPath(pathname);
|
|
704
|
+
return this.gateways.some((gateway) => gateway.path === path);
|
|
705
|
+
}
|
|
706
|
+
hasGateways() {
|
|
707
|
+
return this.gateways.length > 0;
|
|
708
|
+
}
|
|
709
|
+
open(client) {
|
|
710
|
+
this.gateways.find((item) => item.path === client.data.path)?.connect?.(client);
|
|
711
|
+
}
|
|
712
|
+
receive(client, raw) {
|
|
713
|
+
const state = this.clients.get(client);
|
|
714
|
+
if (!state) return;
|
|
715
|
+
let event;
|
|
716
|
+
let data;
|
|
717
|
+
try {
|
|
718
|
+
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
719
|
+
const parsed = JSON.parse(text);
|
|
720
|
+
if (typeof parsed.event !== "string") return;
|
|
721
|
+
event = parsed.event;
|
|
722
|
+
data = parsed.data;
|
|
723
|
+
} catch {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
const handler = state.handlers.find((item) => item.message === event);
|
|
727
|
+
if (!handler) return;
|
|
728
|
+
let stream;
|
|
729
|
+
try {
|
|
730
|
+
stream = state.transform(handler.callback(data));
|
|
731
|
+
} catch {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const subscription = stream.subscribe({
|
|
735
|
+
next: (value) => {
|
|
736
|
+
if (value === void 0) return;
|
|
737
|
+
client.send(JSON.stringify({
|
|
738
|
+
event,
|
|
739
|
+
data: value
|
|
740
|
+
}));
|
|
741
|
+
},
|
|
742
|
+
error: () => {}
|
|
743
|
+
});
|
|
744
|
+
state.subscriptions.push(subscription);
|
|
745
|
+
}
|
|
746
|
+
closed(client) {
|
|
747
|
+
const state = this.clients.get(client);
|
|
748
|
+
if (!state) return;
|
|
749
|
+
for (const subscription of state.subscriptions) subscription.unsubscribe();
|
|
750
|
+
state.onClose?.();
|
|
751
|
+
this.clients.delete(client);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
//#endregion
|
|
667
755
|
//#region src/fetch/index.ts
|
|
668
756
|
const BODY_LIMIT = 1048576;
|
|
669
757
|
var FetchAdapter = class extends AbstractHttpAdapter {
|
|
@@ -955,4 +1043,4 @@ function createFetcher({ module, configure, opts, bodyLimit }, adapter = new Fet
|
|
|
955
1043
|
return handler;
|
|
956
1044
|
}
|
|
957
1045
|
//#endregion
|
|
958
|
-
export {
|
|
1046
|
+
export { AnyFilesInterceptor, FetchAdapter, FileInterceptor, FilesInterceptor, WsGateway, createFetcher, gatewayPath };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-nest",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Run any Nest application as a Web-Standard Request/Response handler.
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run any Nest application as a Web-Standard Request/Response handler.",
|
|
5
5
|
"files": [
|
|
6
6
|
"package.json",
|
|
7
7
|
"readme.md",
|
|
@@ -11,71 +11,23 @@
|
|
|
11
11
|
"sideEffects": false,
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"./bun": {
|
|
18
|
-
"types": "./dist/bun.d.mts",
|
|
19
|
-
"import": "./dist/bun.mjs"
|
|
20
|
-
},
|
|
21
|
-
"./node": {
|
|
22
|
-
"types": "./dist/node.d.mts",
|
|
23
|
-
"import": "./dist/node.mjs"
|
|
24
|
-
},
|
|
25
|
-
"./workers": {
|
|
26
|
-
"types": "./dist/workers.d.mts",
|
|
27
|
-
"import": "./dist/workers.mjs"
|
|
14
|
+
"bun": "./src/fetch/index.ts",
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
28
17
|
},
|
|
29
18
|
"./package.json": "./package.json"
|
|
30
19
|
},
|
|
31
20
|
"scripts": {
|
|
32
|
-
"dev": "tsdown --watch",
|
|
33
21
|
"build": "tsdown --clean",
|
|
22
|
+
"dev": "tsdown --watch",
|
|
34
23
|
"typecheck": "bun check",
|
|
35
|
-
"test": "bun test"
|
|
36
|
-
"lint": "oxlint --type-aware && oxfmt --check",
|
|
37
|
-
"format": "oxlint --fix --fix-suggestions && oxfmt",
|
|
38
|
-
"example:bun": "bun examples/bun/index.ts",
|
|
39
|
-
"example:bun-websockets": "bun examples/bun-websockets/index.ts",
|
|
40
|
-
"example:node": "node --no-strip-types --import tsx examples/node/index.ts",
|
|
41
|
-
"example:node-websockets": "node --no-strip-types --import tsx examples/node-websockets/index.ts",
|
|
42
|
-
"example:workers": "bunx wrangler dev --config examples/workers/wrangler.jsonc --tsconfig ../../tsconfig.json"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"ws": "8.22.0"
|
|
24
|
+
"test": "bun test"
|
|
46
25
|
},
|
|
47
26
|
"devDependencies": {
|
|
48
|
-
"@arethetypeswrong/core": "0.18.5",
|
|
49
|
-
"@nats-io/transport-node": "3.4.0",
|
|
50
|
-
"@nestjs/common": "12.1.3",
|
|
51
|
-
"@nestjs/core": "12.1.3",
|
|
52
|
-
"@nestjs/microservices": "12.1.3",
|
|
53
27
|
"@nestjs/platform-express": "12.1.3",
|
|
54
|
-
"@nestjs/platform-socket.io": "12.1.3",
|
|
55
|
-
"@nestjs/websockets": "12.1.3",
|
|
56
|
-
"@types/bun": "latest",
|
|
57
28
|
"@types/express": "5.0.6",
|
|
58
|
-
"@types/node": "26.6.5",
|
|
59
|
-
"@types/ws": "8.18.2",
|
|
60
|
-
"amqp-connection-manager": "5.0.0",
|
|
61
|
-
"amqplib": "2.2.0",
|
|
62
|
-
"class-transformer": "0.5.1",
|
|
63
|
-
"class-validator": "0.15.1",
|
|
64
29
|
"express": "5.3.0",
|
|
65
|
-
"helmet": "8.3.0"
|
|
66
|
-
"ioredis": "6.0.0",
|
|
67
|
-
"kafkajs": "2.2.4",
|
|
68
|
-
"mqtt": "5.16.0",
|
|
69
|
-
"oxfmt": "0.72.0",
|
|
70
|
-
"oxlint": "1.87.0",
|
|
71
|
-
"oxlint-tsgolint": "7.0.2003",
|
|
72
|
-
"publint": "0.3.25",
|
|
73
|
-
"reflect-metadata": "0.2.2",
|
|
74
|
-
"rxjs": "7.8.2",
|
|
75
|
-
"tsdown": "0.23.0",
|
|
76
|
-
"tsup": "8.5.1",
|
|
77
|
-
"tsx": "4.23.15",
|
|
78
|
-
"typescript": "7.0.2"
|
|
30
|
+
"helmet": "8.3.0"
|
|
79
31
|
},
|
|
80
32
|
"peerDependencies": {
|
|
81
33
|
"@nestjs/common": "^12.0.0",
|
package/readme.md
CHANGED
|
@@ -7,37 +7,62 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/open-nest)
|
|
8
8
|
[](https://bundlejs.com/?q=open-nest)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**open-nest** (`open-nest`) runs a Nest application as a Web-standard `Request`/`Response` handler.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
bun add open-nest
|
|
16
|
-
```
|
|
14
|
+
The same Nest module serves HTTP on Bun, Node, and Cloudflare Workers. Each runtime is its own package. The handler is created once and reused for every request.
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
### Features:
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
- **One handler**
|
|
19
|
+
`createFetcher` turns a Nest module into `(request) => Response`.
|
|
20
|
+
|
|
21
|
+
- **Bun, Node, and Workers**
|
|
22
|
+
`@open-nest/bun` uses `Bun.serve`. `@open-nest/node` uses `node:http`. `@open-nest/workers` uses the Worker `fetch` handler.
|
|
23
|
+
|
|
24
|
+
- **WebSockets**
|
|
25
|
+
`BunWsAdapter` lets `Bun.serve` upgrade the connection. `NodeWsAdapter` upgrades through [`ws`](https://github.com/websockets/ws).
|
|
26
|
+
|
|
27
|
+
- **Static files**
|
|
28
|
+
`BunAdapter` reads files with `Bun.file()`. `NodeAdapter` reads them with `node:fs`.
|
|
29
|
+
|
|
30
|
+
- **Worker bindings**
|
|
31
|
+
`workerContext()` returns `env` and `ctx` for the request in flight, including `ctx.waitUntil`.
|
|
32
|
+
|
|
33
|
+
## Contents
|
|
34
|
+
|
|
35
|
+
- [Installation](#installation)
|
|
36
|
+
Add `open-nest` and a runtime package.
|
|
21
37
|
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
Run a Nest application on Cloudflare Workers.
|
|
38
|
+
- [Usage](#usage)
|
|
39
|
+
Create a handler and serve it.
|
|
25
40
|
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
Run a Nest application via `Bun.serve`.
|
|
41
|
+
- [Examples](#examples)
|
|
42
|
+
Small apps for each runtime.
|
|
29
43
|
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
- [Bun](#bun)
|
|
45
|
+
- [Node](#node)
|
|
46
|
+
- [Workers](#workers)
|
|
33
47
|
|
|
34
|
-
- [
|
|
35
|
-
|
|
36
|
-
Run a Nest application via `http.Server`.
|
|
48
|
+
- [Packages](#packages)
|
|
49
|
+
What each package is for.
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Peer dependencies are `@nestjs/common`, `@nestjs/core`, `reflect-metadata`, and `rxjs`. WebSocket gateways also need `@nestjs/websockets`. These already ship with a Nest installation.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
bun add open-nest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Add the runtime you serve with:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
bun add @open-nest/bun
|
|
63
|
+
bun add @open-nest/node
|
|
64
|
+
bun add @open-nest/workers
|
|
65
|
+
```
|
|
41
66
|
|
|
42
67
|
## Usage
|
|
43
68
|
|
|
@@ -48,24 +73,37 @@ import { createFetcher } from "open-nest";
|
|
|
48
73
|
|
|
49
74
|
import { AppModule } from "@/app/module";
|
|
50
75
|
|
|
76
|
+
// 1. Create a handler from a Nest module.
|
|
51
77
|
const fetcher = createFetcher({ module: AppModule });
|
|
52
78
|
|
|
79
|
+
// 2. Serve it anywhere that speaks Request and Response.
|
|
53
80
|
await Bun.serve({
|
|
54
81
|
fetch: fetcher,
|
|
55
82
|
port: 3000,
|
|
56
83
|
});
|
|
57
84
|
```
|
|
58
85
|
|
|
59
|
-
`createFetcher`
|
|
86
|
+
`createFetcher` initializes the Nest application once and returns a function from `Request` to `Response`.
|
|
60
87
|
|
|
61
|
-
|
|
88
|
+
`configure` runs after the app is created and before `init`. `opts` is forwarded to `NestFactory.create`. `app()` resolves the Nest application.
|
|
62
89
|
|
|
63
90
|
```ts
|
|
64
|
-
|
|
91
|
+
const fetcher = createFetcher({
|
|
92
|
+
module: AppModule,
|
|
93
|
+
configure: (app) => {
|
|
94
|
+
app.enableCors();
|
|
95
|
+
app.setGlobalPrefix("api");
|
|
96
|
+
},
|
|
97
|
+
opts: { logger: ["error", "warn"] },
|
|
98
|
+
});
|
|
65
99
|
|
|
66
|
-
|
|
100
|
+
const app = await fetcher.app();
|
|
101
|
+
```
|
|
67
102
|
|
|
68
|
-
|
|
103
|
+
### Bun
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { BunWsAdapter, createBunServer } from "@open-nest/bun";
|
|
69
107
|
|
|
70
108
|
await createBunServer({
|
|
71
109
|
module: AppModule,
|
|
@@ -77,20 +115,12 @@ await createBunServer({
|
|
|
77
115
|
});
|
|
78
116
|
```
|
|
79
117
|
|
|
80
|
-
`createBunServer`
|
|
81
|
-
|
|
82
|
-
- `createBunServer` initializes the Nest application once and starts and returns a `Bun.serve` instance.
|
|
83
|
-
- `BunWsAdapter` is the Nest WebSocket adapter. `Bun.serve` performs the upgrade.
|
|
84
|
-
- `BunAdapter` extends `FetchAdapter` and implements `useStaticAssets` with `Bun.file()`.
|
|
118
|
+
`createBunServer` starts `Bun.serve` and returns it. `BunWsAdapter` records gateway paths so `Bun.serve` can upgrade them. Static files are served with `Bun.file()`.
|
|
85
119
|
|
|
86
120
|
### Node
|
|
87
121
|
|
|
88
122
|
```ts
|
|
89
|
-
import "
|
|
90
|
-
|
|
91
|
-
import { createNodeServer, NodeWsAdapter } from "open-nest/node";
|
|
92
|
-
|
|
93
|
-
import { AppModule } from "@/app/module";
|
|
123
|
+
import { createNodeServer, NodeWsAdapter } from "@open-nest/node";
|
|
94
124
|
|
|
95
125
|
await createNodeServer({
|
|
96
126
|
module: AppModule,
|
|
@@ -102,30 +132,29 @@ await createNodeServer({
|
|
|
102
132
|
});
|
|
103
133
|
```
|
|
104
134
|
|
|
105
|
-
`createNodeServer`
|
|
106
|
-
|
|
107
|
-
Gateway upgrades go through [`ws`](https://github.com/websockets/ws). The WebSocket client is the global `WebSocket` from Node 22 onward.
|
|
108
|
-
|
|
109
|
-
- `createNodeServer` initializes the Nest application once and starts and returns a `http.Server` instance.
|
|
110
|
-
- `NodeWsAdapter` is the Nest WebSocket adapter. Upgrades go through `ws`.
|
|
111
|
-
- `NodeAdapter` extends `FetchAdapter` and implements `useStaticAssets` with `node:fs`.
|
|
135
|
+
`createNodeServer` starts an `http.Server` and returns it. Gateway upgrades go through `ws`. The WebSocket client is the global `WebSocket` from Node 22 onward. Static files are served with `node:fs`.
|
|
112
136
|
|
|
113
137
|
### Workers
|
|
114
138
|
|
|
115
139
|
```ts
|
|
116
|
-
import "reflect-metadata";
|
|
117
|
-
|
|
118
140
|
import {
|
|
119
141
|
createWorkerFetcher,
|
|
120
142
|
workerContext,
|
|
121
143
|
type WorkerExecutionContext,
|
|
122
|
-
} from "open-nest/workers";
|
|
144
|
+
} from "@open-nest/workers";
|
|
123
145
|
|
|
124
146
|
interface Env {
|
|
125
147
|
APP_NAME: string;
|
|
126
148
|
CACHE: KVNamespace;
|
|
127
149
|
}
|
|
128
150
|
|
|
151
|
+
const fetcher = createWorkerFetcher<Env>({ module: AppModule });
|
|
152
|
+
|
|
153
|
+
export default {
|
|
154
|
+
fetch: (request: Request, env: Env, ctx: WorkerExecutionContext) =>
|
|
155
|
+
fetcher(request, env, ctx),
|
|
156
|
+
};
|
|
157
|
+
|
|
129
158
|
@Controller()
|
|
130
159
|
class AppController {
|
|
131
160
|
@Get()
|
|
@@ -138,55 +167,48 @@ class AppController {
|
|
|
138
167
|
return { data: { app: env.APP_NAME, lastOpenedAt }, error: null };
|
|
139
168
|
}
|
|
140
169
|
}
|
|
141
|
-
|
|
142
|
-
@Module({ controllers: [AppController] })
|
|
143
|
-
class AppModule {}
|
|
144
|
-
|
|
145
|
-
const fetcher = createWorkerFetcher<Env>({ module: AppModule });
|
|
146
|
-
|
|
147
|
-
export default {
|
|
148
|
-
fetch: (request: Request, env: Env, ctx: WorkerExecutionContext) =>
|
|
149
|
-
fetcher(request, env, ctx),
|
|
150
|
-
};
|
|
151
170
|
```
|
|
152
171
|
|
|
153
|
-
`createWorkerFetcher`
|
|
172
|
+
`createWorkerFetcher` returns a handler of `(request, env, ctx)`. `workerContext()` reads those bindings for the request in flight. The Worker needs the `nodejs_compat` compatibility flag.
|
|
154
173
|
|
|
155
|
-
|
|
174
|
+
## Examples
|
|
156
175
|
|
|
157
|
-
- `
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
- [`examples/bun`](/examples/bun)
|
|
177
|
+
<sup>[readme](/examples/bun/readme.md) ⋅ [code](/examples/bun/)</sup>
|
|
178
|
+
Nest on `Bun.serve`. `GET /health` returns `{ data: null, error: null }`.
|
|
160
179
|
|
|
161
|
-
|
|
180
|
+
- [`examples/bun-websockets`](/examples/bun-websockets)
|
|
181
|
+
<sup>[readme](/examples/bun-websockets/readme.md) ⋅ [code](/examples/bun-websockets/)</sup>
|
|
182
|
+
A gateway at `/ws` that echoes `events`.
|
|
162
183
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
configure: (app) => {
|
|
167
|
-
app.enableCors();
|
|
168
|
-
app.setGlobalPrefix("api");
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
```
|
|
184
|
+
- [`examples/node`](/examples/node)
|
|
185
|
+
<sup>[readme](/examples/node/readme.md) ⋅ [code](/examples/node/)</sup>
|
|
186
|
+
Nest on `http.Server`. `GET /` returns `{ data: null, error: null }`.
|
|
172
187
|
|
|
173
|
-
`
|
|
188
|
+
- [`examples/node-websockets`](/examples/node-websockets)
|
|
189
|
+
<sup>[readme](/examples/node-websockets/readme.md) ⋅ [code](/examples/node-websockets/)</sup>
|
|
190
|
+
The same gateway, upgraded through `ws`.
|
|
174
191
|
|
|
175
|
-
|
|
192
|
+
- [`examples/workers`](/examples/workers)
|
|
193
|
+
<sup>[readme](/examples/workers/readme.md) ⋅ [code](/examples/workers/)</sup>
|
|
194
|
+
A Worker with an `APP_NAME` variable and a `CACHE` KV binding.
|
|
176
195
|
|
|
177
|
-
|
|
178
|
-
const app = await fetcher.app();
|
|
179
|
-
```
|
|
196
|
+
From the repository root, `bun run dev` watches the packages and starts every example. Bun listens on port 4001, Bun websockets on 4002, Node on 4003, Node websockets on 4004, and Workers on 8787.
|
|
180
197
|
|
|
181
|
-
|
|
198
|
+
## Packages
|
|
182
199
|
|
|
183
|
-
|
|
200
|
+
- [`open-nest`](/packages/open-nest)
|
|
201
|
+
<sup>[readme](/packages/open-nest/readme.md)</sup>
|
|
202
|
+
The HTTP handler: `createFetcher`, `FetchAdapter`, and multipart interceptors.
|
|
184
203
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
- [`@open-nest/bun`](/packages/@open-nest/bun)
|
|
205
|
+
<sup>[readme](/packages/@open-nest/bun/readme.md) ⋅ [examples](/examples/bun)</sup>
|
|
206
|
+
`Bun.serve`, WebSocket upgrades, and static files.
|
|
207
|
+
|
|
208
|
+
- [`@open-nest/node`](/packages/@open-nest/node)
|
|
209
|
+
<sup>[readme](/packages/@open-nest/node/readme.md) ⋅ [examples](/examples/node)</sup>
|
|
210
|
+
`node:http`, WebSocket upgrades through `ws`, and static files.
|
|
191
211
|
|
|
192
|
-
|
|
212
|
+
- [`@open-nest/workers`](/packages/@open-nest/workers)
|
|
213
|
+
<sup>[readme](/packages/@open-nest/workers/readme.md) ⋅ [examples](/examples/workers)</sup>
|
|
214
|
+
Worker `fetch`, plus `env` and `ctx` for the request in flight.
|
package/dist/bun.d.mts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { i as Fetcher, r as FetchAdapter } from "./index-DCLeOkkF.mjs";
|
|
2
|
-
import { n as WsGateway, t as GatewayClient } from "./gateway-CrNeg78D.mjs";
|
|
3
|
-
import { INestApplication, NestApplicationOptions, Type } from "@nestjs/common";
|
|
4
|
-
//#region src/adapters/bun/index.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Records Nest gateways during `init`. `Bun.serve` performs the upgrade.
|
|
7
|
-
*/
|
|
8
|
-
export declare class BunWsAdapter extends WsGateway<GatewayClient> {
|
|
9
|
-
constructor();
|
|
10
|
-
dispose(): Promise<void>;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Fetch adapter plus static files. Workers stay on {@link FetchAdapter}.
|
|
14
|
-
*/
|
|
15
|
-
export declare class BunAdapter extends FetchAdapter {
|
|
16
|
-
useStaticAssets(root?: string, options?: {
|
|
17
|
-
prefix?: string;
|
|
18
|
-
index?: string | false;
|
|
19
|
-
}): this;
|
|
20
|
-
}
|
|
21
|
-
export interface CreateBunServerOptions {
|
|
22
|
-
/**
|
|
23
|
-
* Root Nest module.
|
|
24
|
-
*/
|
|
25
|
-
module: Type<unknown>;
|
|
26
|
-
/**
|
|
27
|
-
* Port passed to `Bun.serve`. `0` asks the OS for a free port.
|
|
28
|
-
*/
|
|
29
|
-
port?: number;
|
|
30
|
-
/**
|
|
31
|
-
* Runs after the app is created and before init.
|
|
32
|
-
*/
|
|
33
|
-
configure?: (app: INestApplication) => void | Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Forwarded to `NestFactory.create`.
|
|
36
|
-
*/
|
|
37
|
-
opts?: NestApplicationOptions;
|
|
38
|
-
}
|
|
39
|
-
export interface BunServer {
|
|
40
|
-
port: number;
|
|
41
|
-
fetch: Fetcher;
|
|
42
|
-
close(): Promise<void>;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Boots a Nest app and serves it with `Bun.serve`, including WebSocket upgrades.
|
|
46
|
-
*/
|
|
47
|
-
export declare function createBunServer({ module, port, configure, opts }: CreateBunServerOptions): Promise<BunServer>;
|
|
48
|
-
//#endregion
|
package/dist/bun.mjs
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { n as createFetcher, t as FetchAdapter } from "./fetch-Bmr9TEe3.mjs";
|
|
2
|
-
import { n as gatewayPath, t as WsGateway } from "./gateway-D7PGV2DW.mjs";
|
|
3
|
-
import { resolve, sep } from "node:path";
|
|
4
|
-
//#region src/adapters/bun/index.ts
|
|
5
|
-
const adapters = [];
|
|
6
|
-
/**
|
|
7
|
-
* Records Nest gateways during `init`. `Bun.serve` performs the upgrade.
|
|
8
|
-
*/
|
|
9
|
-
var BunWsAdapter = class extends WsGateway {
|
|
10
|
-
constructor() {
|
|
11
|
-
super();
|
|
12
|
-
adapters.push(this);
|
|
13
|
-
}
|
|
14
|
-
async dispose() {
|
|
15
|
-
const index = adapters.indexOf(this);
|
|
16
|
-
if (index !== -1) adapters.splice(index, 1);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Fetch adapter plus static files. Workers stay on {@link FetchAdapter}.
|
|
21
|
-
*/
|
|
22
|
-
var BunAdapter = class extends FetchAdapter {
|
|
23
|
-
useStaticAssets(root, options) {
|
|
24
|
-
if (!root) return this;
|
|
25
|
-
const prefix = options?.prefix ?? "/";
|
|
26
|
-
const index = options?.index === void 0 ? "index.html" : options.index;
|
|
27
|
-
const rootResolved = resolve(root);
|
|
28
|
-
this.router.use(prefix, async (req, res, next) => {
|
|
29
|
-
if (req.method !== "GET" && req.method !== "HEAD") return next();
|
|
30
|
-
let relative;
|
|
31
|
-
try {
|
|
32
|
-
relative = decodeURIComponent(req.path);
|
|
33
|
-
} catch {
|
|
34
|
-
return next();
|
|
35
|
-
}
|
|
36
|
-
if (relative === "/") {
|
|
37
|
-
if (index === false) return next();
|
|
38
|
-
relative = index;
|
|
39
|
-
} else relative = relative.replace(/^\//, "");
|
|
40
|
-
const target = resolve(rootResolved, relative);
|
|
41
|
-
if (target !== rootResolved && !target.startsWith(rootResolved + sep)) return next();
|
|
42
|
-
const file = Bun.file(target);
|
|
43
|
-
if (!await file.exists()) return next();
|
|
44
|
-
res.setHeader("content-type", file.type || "application/octet-stream");
|
|
45
|
-
res.setHeader("content-length", String(file.size));
|
|
46
|
-
if (req.method === "HEAD") {
|
|
47
|
-
res.end();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
res.end(new Uint8Array(await file.arrayBuffer()));
|
|
51
|
-
});
|
|
52
|
-
return this;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Boots a Nest app and serves it with `Bun.serve`, including WebSocket upgrades.
|
|
57
|
-
*/
|
|
58
|
-
async function createBunServer({ module, port, configure, opts }) {
|
|
59
|
-
const fallback = new BunWsAdapter();
|
|
60
|
-
const seen = new Set(adapters);
|
|
61
|
-
const fetcher = createFetcher({
|
|
62
|
-
module,
|
|
63
|
-
opts,
|
|
64
|
-
configure: async (app) => {
|
|
65
|
-
app.useWebSocketAdapter(fallback);
|
|
66
|
-
await configure?.(app);
|
|
67
|
-
}
|
|
68
|
-
}, new BunAdapter());
|
|
69
|
-
await fetcher.app();
|
|
70
|
-
const active = [...adapters].reverse().find((adapter) => !seen.has(adapter) && adapter.hasGateways()) ?? fallback;
|
|
71
|
-
const server = Bun.serve({
|
|
72
|
-
port: port ?? 3e3,
|
|
73
|
-
fetch: (request, bun) => {
|
|
74
|
-
const pathname = new URL(request.url).pathname;
|
|
75
|
-
if (active.matches(pathname) && bun.upgrade(request, { data: { path: gatewayPath(pathname) } })) return;
|
|
76
|
-
return fetcher(request);
|
|
77
|
-
},
|
|
78
|
-
websocket: {
|
|
79
|
-
open: (client) => {
|
|
80
|
-
active.open(client);
|
|
81
|
-
},
|
|
82
|
-
message: (client, message) => {
|
|
83
|
-
active.receive(client, message);
|
|
84
|
-
},
|
|
85
|
-
close: (client) => {
|
|
86
|
-
active.closed(client);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
return {
|
|
91
|
-
port: server.port ?? port ?? 3e3,
|
|
92
|
-
fetch: fetcher,
|
|
93
|
-
close: async () => {
|
|
94
|
-
await server.stop(true);
|
|
95
|
-
await fetcher.close();
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
//#endregion
|
|
100
|
-
export { BunAdapter, BunWsAdapter, createBunServer };
|
package/dist/fetch.d.mts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as createFetcher, c as FilesInterceptor, i as Fetcher, l as MultipartLimits, n as CreateHandlerOptions, o as AnyFilesInterceptor, r as FetchAdapter, s as FileInterceptor, t as CorsOptions, u as MultipartOptions } from "./index-DCLeOkkF.mjs";
|
|
2
|
-
export { AnyFilesInterceptor, CorsOptions, CreateHandlerOptions, FetchAdapter, Fetcher, FileInterceptor, FilesInterceptor, type MultipartLimits, type MultipartOptions, createFetcher };
|
package/dist/fetch.mjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region src/lib/gateway.d.ts
|
|
2
|
-
interface GatewayClient {
|
|
3
|
-
send(data: string): void;
|
|
4
|
-
data: {
|
|
5
|
-
path: string;
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
interface Gateway {
|
|
9
|
-
path: string;
|
|
10
|
-
connect?: (client: GatewayClient) => void;
|
|
11
|
-
}
|
|
12
|
-
interface MessageHandler {
|
|
13
|
-
message: string;
|
|
14
|
-
callback: (data: unknown) => unknown;
|
|
15
|
-
}
|
|
16
|
-
interface Subscription {
|
|
17
|
-
unsubscribe(): void;
|
|
18
|
-
}
|
|
19
|
-
interface ResultStream {
|
|
20
|
-
subscribe(observer: {
|
|
21
|
-
next: (value: unknown) => void;
|
|
22
|
-
error?: (error: unknown) => void;
|
|
23
|
-
}): Subscription;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Records Nest gateways during `init`. The runtime server performs the upgrade.
|
|
27
|
-
*/
|
|
28
|
-
declare class WsGateway<T extends GatewayClient = GatewayClient> {
|
|
29
|
-
private readonly gateways;
|
|
30
|
-
private readonly clients;
|
|
31
|
-
create(_port: number, options?: {
|
|
32
|
-
path?: string;
|
|
33
|
-
}): Gateway;
|
|
34
|
-
bindClientConnect(server: Gateway, callback: (client: T) => void): void;
|
|
35
|
-
bindMessageHandlers(client: T, handlers: MessageHandler[], transform: (result: unknown) => ResultStream): void;
|
|
36
|
-
bindClientDisconnect(client: T, callback: () => void): void;
|
|
37
|
-
close(): void;
|
|
38
|
-
matches(pathname: string): boolean;
|
|
39
|
-
hasGateways(): boolean;
|
|
40
|
-
open(client: T): void;
|
|
41
|
-
receive(client: T, raw: string | Uint8Array): void;
|
|
42
|
-
closed(client: T): void;
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
export { WsGateway as n, GatewayClient as t };
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
//#region src/lib/gateway.ts
|
|
2
|
-
function gatewayPath(path) {
|
|
3
|
-
if (!path.startsWith("/")) path = `/${path}`;
|
|
4
|
-
if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
|
5
|
-
return path;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Records Nest gateways during `init`. The runtime server performs the upgrade.
|
|
9
|
-
*/
|
|
10
|
-
var WsGateway = class {
|
|
11
|
-
gateways = [];
|
|
12
|
-
clients = /* @__PURE__ */ new WeakMap();
|
|
13
|
-
create(_port, options) {
|
|
14
|
-
const gateway = { path: gatewayPath(options?.path ?? "/") };
|
|
15
|
-
this.gateways.push(gateway);
|
|
16
|
-
return gateway;
|
|
17
|
-
}
|
|
18
|
-
bindClientConnect(server, callback) {
|
|
19
|
-
server.connect = callback;
|
|
20
|
-
}
|
|
21
|
-
bindMessageHandlers(client, handlers, transform) {
|
|
22
|
-
const prev = this.clients.get(client);
|
|
23
|
-
this.clients.set(client, {
|
|
24
|
-
handlers,
|
|
25
|
-
transform,
|
|
26
|
-
subscriptions: prev?.subscriptions ?? [],
|
|
27
|
-
onClose: prev?.onClose
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
bindClientDisconnect(client, callback) {
|
|
31
|
-
const state = this.clients.get(client);
|
|
32
|
-
if (!state) return;
|
|
33
|
-
state.onClose = callback;
|
|
34
|
-
}
|
|
35
|
-
close() {}
|
|
36
|
-
matches(pathname) {
|
|
37
|
-
const path = gatewayPath(pathname);
|
|
38
|
-
return this.gateways.some((gateway) => gateway.path === path);
|
|
39
|
-
}
|
|
40
|
-
hasGateways() {
|
|
41
|
-
return this.gateways.length > 0;
|
|
42
|
-
}
|
|
43
|
-
open(client) {
|
|
44
|
-
this.gateways.find((item) => item.path === client.data.path)?.connect?.(client);
|
|
45
|
-
}
|
|
46
|
-
receive(client, raw) {
|
|
47
|
-
const state = this.clients.get(client);
|
|
48
|
-
if (!state) return;
|
|
49
|
-
let event;
|
|
50
|
-
let data;
|
|
51
|
-
try {
|
|
52
|
-
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
53
|
-
const parsed = JSON.parse(text);
|
|
54
|
-
if (typeof parsed.event !== "string") return;
|
|
55
|
-
event = parsed.event;
|
|
56
|
-
data = parsed.data;
|
|
57
|
-
} catch {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const handler = state.handlers.find((item) => item.message === event);
|
|
61
|
-
if (!handler) return;
|
|
62
|
-
let stream;
|
|
63
|
-
try {
|
|
64
|
-
stream = state.transform(handler.callback(data));
|
|
65
|
-
} catch {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const subscription = stream.subscribe({
|
|
69
|
-
next: (value) => {
|
|
70
|
-
if (value === void 0) return;
|
|
71
|
-
client.send(JSON.stringify({
|
|
72
|
-
event,
|
|
73
|
-
data: value
|
|
74
|
-
}));
|
|
75
|
-
},
|
|
76
|
-
error: () => {}
|
|
77
|
-
});
|
|
78
|
-
state.subscriptions.push(subscription);
|
|
79
|
-
}
|
|
80
|
-
closed(client) {
|
|
81
|
-
const state = this.clients.get(client);
|
|
82
|
-
if (!state) return;
|
|
83
|
-
for (const subscription of state.subscriptions) subscription.unsubscribe();
|
|
84
|
-
state.onClose?.();
|
|
85
|
-
this.clients.delete(client);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
//#endregion
|
|
89
|
-
export { gatewayPath as n, WsGateway as t };
|
package/dist/node.d.mts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { i as Fetcher, r as FetchAdapter } from "./index-DCLeOkkF.mjs";
|
|
2
|
-
import { n as WsGateway, t as GatewayClient } from "./gateway-CrNeg78D.mjs";
|
|
3
|
-
import { INestApplication, NestApplicationOptions, Type } from "@nestjs/common";
|
|
4
|
-
//#region src/adapters/node/index.d.ts
|
|
5
|
-
interface NodeSocket extends GatewayClient {
|
|
6
|
-
data: {
|
|
7
|
-
path: string;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Records Nest gateways during `init`. `node:http` performs the upgrade.
|
|
12
|
-
*/
|
|
13
|
-
export declare class NodeWsAdapter extends WsGateway<NodeSocket> {
|
|
14
|
-
constructor();
|
|
15
|
-
dispose(): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Fetch adapter plus static files via `node:fs`.
|
|
19
|
-
*/
|
|
20
|
-
export declare class NodeAdapter extends FetchAdapter {
|
|
21
|
-
useStaticAssets(root?: string, options?: {
|
|
22
|
-
prefix?: string;
|
|
23
|
-
index?: string | false;
|
|
24
|
-
}): this;
|
|
25
|
-
}
|
|
26
|
-
export interface CreateNodeServerOptions {
|
|
27
|
-
/**
|
|
28
|
-
* Root Nest module.
|
|
29
|
-
*/
|
|
30
|
-
module: Type<unknown>;
|
|
31
|
-
/**
|
|
32
|
-
* Port passed to `node:http`. `0` asks the OS for a free port.
|
|
33
|
-
*/
|
|
34
|
-
port?: number;
|
|
35
|
-
/**
|
|
36
|
-
* Runs after the app is created and before init.
|
|
37
|
-
*/
|
|
38
|
-
configure?: (app: INestApplication) => void | Promise<void>;
|
|
39
|
-
/**
|
|
40
|
-
* Forwarded to `NestFactory.create`.
|
|
41
|
-
*/
|
|
42
|
-
opts?: NestApplicationOptions;
|
|
43
|
-
}
|
|
44
|
-
export interface NodeServer {
|
|
45
|
-
port: number;
|
|
46
|
-
fetch: Fetcher;
|
|
47
|
-
close(): Promise<void>;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Boots a Nest app and serves it with `node:http`, including WebSocket upgrades.
|
|
51
|
-
*/
|
|
52
|
-
export declare function createNodeServer({ module, port, configure, opts }: CreateNodeServerOptions): Promise<NodeServer>;
|
|
53
|
-
//#endregion
|
package/dist/node.mjs
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { n as createFetcher, t as FetchAdapter } from "./fetch-Bmr9TEe3.mjs";
|
|
2
|
-
import { n as gatewayPath, t as WsGateway } from "./gateway-D7PGV2DW.mjs";
|
|
3
|
-
import { Readable } from "node:stream";
|
|
4
|
-
import { extname, resolve, sep } from "node:path";
|
|
5
|
-
import { openAsBlob } from "node:fs";
|
|
6
|
-
import { stat } from "node:fs/promises";
|
|
7
|
-
import { createServer } from "node:http";
|
|
8
|
-
import { WebSocketServer } from "ws";
|
|
9
|
-
//#region src/adapters/node/index.ts
|
|
10
|
-
const adapters = [];
|
|
11
|
-
/**
|
|
12
|
-
* Records Nest gateways during `init`. `node:http` performs the upgrade.
|
|
13
|
-
*/
|
|
14
|
-
var NodeWsAdapter = class extends WsGateway {
|
|
15
|
-
constructor() {
|
|
16
|
-
super();
|
|
17
|
-
adapters.push(this);
|
|
18
|
-
}
|
|
19
|
-
async dispose() {
|
|
20
|
-
const index = adapters.indexOf(this);
|
|
21
|
-
if (index !== -1) adapters.splice(index, 1);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Fetch adapter plus static files via `node:fs`.
|
|
26
|
-
*/
|
|
27
|
-
var NodeAdapter = class extends FetchAdapter {
|
|
28
|
-
useStaticAssets(root, options) {
|
|
29
|
-
if (!root) return this;
|
|
30
|
-
const prefix = options?.prefix ?? "/";
|
|
31
|
-
const index = options?.index === void 0 ? "index.html" : options.index;
|
|
32
|
-
const rootResolved = resolve(root);
|
|
33
|
-
this.router.use(prefix, async (req, res, next) => {
|
|
34
|
-
if (req.method !== "GET" && req.method !== "HEAD") return next();
|
|
35
|
-
let relative;
|
|
36
|
-
try {
|
|
37
|
-
relative = decodeURIComponent(req.path);
|
|
38
|
-
} catch {
|
|
39
|
-
return next();
|
|
40
|
-
}
|
|
41
|
-
if (relative === "/") {
|
|
42
|
-
if (index === false) return next();
|
|
43
|
-
relative = index;
|
|
44
|
-
} else relative = relative.replace(/^\//, "");
|
|
45
|
-
const target = resolve(rootResolved, relative);
|
|
46
|
-
if (target !== rootResolved && !target.startsWith(rootResolved + sep)) return next();
|
|
47
|
-
let size;
|
|
48
|
-
try {
|
|
49
|
-
const info = await stat(target);
|
|
50
|
-
if (!info.isFile()) return next();
|
|
51
|
-
size = info.size;
|
|
52
|
-
} catch {
|
|
53
|
-
return next();
|
|
54
|
-
}
|
|
55
|
-
const type = contentType(target);
|
|
56
|
-
const blob = await openAsBlob(target);
|
|
57
|
-
res.setHeader("content-type", type);
|
|
58
|
-
res.setHeader("content-length", String(size));
|
|
59
|
-
if (req.method === "HEAD") {
|
|
60
|
-
res.end();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const reader = blob.stream().getReader();
|
|
64
|
-
for (;;) {
|
|
65
|
-
const { done, value } = await reader.read();
|
|
66
|
-
if (done) break;
|
|
67
|
-
res.write(value);
|
|
68
|
-
}
|
|
69
|
-
res.end();
|
|
70
|
-
});
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
function contentType(file) {
|
|
75
|
-
switch (extname(file).toLowerCase()) {
|
|
76
|
-
case ".html": return "text/html; charset=utf-8";
|
|
77
|
-
case ".txt": return "text/plain; charset=utf-8";
|
|
78
|
-
case ".json": return "application/json";
|
|
79
|
-
case ".css": return "text/css; charset=utf-8";
|
|
80
|
-
case ".js": return "text/javascript; charset=utf-8";
|
|
81
|
-
case ".svg": return "image/svg+xml";
|
|
82
|
-
case ".png": return "image/png";
|
|
83
|
-
case ".jpg":
|
|
84
|
-
case ".jpeg": return "image/jpeg";
|
|
85
|
-
case ".webp": return "image/webp";
|
|
86
|
-
case ".pdf": return "application/pdf";
|
|
87
|
-
case ".wasm": return "application/wasm";
|
|
88
|
-
default: return "application/octet-stream";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Boots a Nest app and serves it with `node:http`, including WebSocket upgrades.
|
|
93
|
-
*/
|
|
94
|
-
async function createNodeServer({ module, port, configure, opts }) {
|
|
95
|
-
const fallback = new NodeWsAdapter();
|
|
96
|
-
const seen = new Set(adapters);
|
|
97
|
-
const fetcher = createFetcher({
|
|
98
|
-
module,
|
|
99
|
-
opts,
|
|
100
|
-
configure: async (app) => {
|
|
101
|
-
app.useWebSocketAdapter(fallback);
|
|
102
|
-
await configure?.(app);
|
|
103
|
-
}
|
|
104
|
-
}, new NodeAdapter());
|
|
105
|
-
await fetcher.app();
|
|
106
|
-
const active = [...adapters].reverse().find((adapter) => !seen.has(adapter) && adapter.hasGateways()) ?? fallback;
|
|
107
|
-
const sockets = new WebSocketServer({ noServer: true });
|
|
108
|
-
const server = createServer((req, res) => {
|
|
109
|
-
writeFetch(fetcher, req, res);
|
|
110
|
-
});
|
|
111
|
-
server.on("upgrade", (req, socket, head) => {
|
|
112
|
-
const pathname = gatewayPath(new URL(req.url ?? "/", "http://localhost").pathname);
|
|
113
|
-
if (!active.matches(pathname)) {
|
|
114
|
-
socket.destroy();
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
sockets.handleUpgrade(req, socket, head, (ws) => {
|
|
118
|
-
const client = {
|
|
119
|
-
data: { path: pathname },
|
|
120
|
-
send(data) {
|
|
121
|
-
ws.send(data);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
active.open(client);
|
|
125
|
-
ws.on("message", (data) => {
|
|
126
|
-
const raw = typeof data === "string" ? data : Array.isArray(data) ? Buffer.concat(data) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
127
|
-
active.receive(client, raw);
|
|
128
|
-
});
|
|
129
|
-
ws.on("close", () => {
|
|
130
|
-
active.closed(client);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
await new Promise((resolve) => {
|
|
135
|
-
server.listen(port ?? 3e3, "127.0.0.1", () => resolve());
|
|
136
|
-
});
|
|
137
|
-
const address = server.address();
|
|
138
|
-
return {
|
|
139
|
-
port: typeof address === "object" && address ? address.port : port ?? 3e3,
|
|
140
|
-
fetch: fetcher,
|
|
141
|
-
close: async () => {
|
|
142
|
-
for (const client of sockets.clients) client.terminate();
|
|
143
|
-
await new Promise((resolve, reject) => {
|
|
144
|
-
sockets.close((error) => error ? reject(error) : resolve());
|
|
145
|
-
});
|
|
146
|
-
server.closeAllConnections();
|
|
147
|
-
await new Promise((resolve, reject) => {
|
|
148
|
-
server.close((error) => error ? reject(error) : resolve());
|
|
149
|
-
});
|
|
150
|
-
await fetcher.close();
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
async function writeFetch(fetcher, req, res) {
|
|
155
|
-
try {
|
|
156
|
-
const response = await fetcher(toRequest(req));
|
|
157
|
-
const headers = Object.fromEntries(response.headers.entries());
|
|
158
|
-
res.writeHead(response.status, headers);
|
|
159
|
-
if (!response.body) {
|
|
160
|
-
res.end();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
await new Promise((resolve, reject) => {
|
|
164
|
-
Readable.fromWeb(response.body).pipe(res).on("finish", () => resolve()).on("error", reject);
|
|
165
|
-
});
|
|
166
|
-
} catch {
|
|
167
|
-
if (!res.headersSent) res.writeHead(500);
|
|
168
|
-
res.end();
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function toRequest(req) {
|
|
172
|
-
const host = req.headers.host ?? "127.0.0.1";
|
|
173
|
-
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
174
|
-
const headers = new Headers();
|
|
175
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
176
|
-
if (value == null) continue;
|
|
177
|
-
if (Array.isArray(value)) for (const item of value) headers.append(name, item);
|
|
178
|
-
else headers.set(name, value);
|
|
179
|
-
}
|
|
180
|
-
const method = req.method ?? "GET";
|
|
181
|
-
const bodyless = method === "GET" || method === "HEAD";
|
|
182
|
-
return new Request(url.href, {
|
|
183
|
-
method,
|
|
184
|
-
headers,
|
|
185
|
-
body: bodyless ? void 0 : Readable.toWeb(req),
|
|
186
|
-
duplex: "half"
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
|
-
export { NodeAdapter, NodeWsAdapter, createNodeServer };
|
package/dist/workers.d.mts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { i as Fetcher, n as CreateHandlerOptions } from "./index-DCLeOkkF.mjs";
|
|
2
|
-
//#region src/adapters/workers/index.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* The `ctx` argument from a Worker `fetch` handler.
|
|
5
|
-
*/
|
|
6
|
-
export interface WorkerExecutionContext {
|
|
7
|
-
waitUntil(promise: Promise<unknown>): void;
|
|
8
|
-
passThroughOnException(): void;
|
|
9
|
-
readonly props?: unknown;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Bindings and the raw execution context for the request in flight.
|
|
13
|
-
*/
|
|
14
|
-
export interface WorkerContext<Env> {
|
|
15
|
-
env: Env;
|
|
16
|
-
ctx: WorkerExecutionContext;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Bindings and execution context for the current Worker request.
|
|
20
|
-
*/
|
|
21
|
-
export declare function workerContext<Env>(): WorkerContext<Env>;
|
|
22
|
-
export interface WorkerFetcher<Env> {
|
|
23
|
-
(request: Request, env: Env, ctx: WorkerExecutionContext): Promise<Response>;
|
|
24
|
-
/**
|
|
25
|
-
* Resolves the underlying Nest app instance.
|
|
26
|
-
*/
|
|
27
|
-
app: Fetcher["app"];
|
|
28
|
-
/**
|
|
29
|
-
* Closes the underlying Nest app.
|
|
30
|
-
*/
|
|
31
|
-
close: Fetcher["close"];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* `createFetcher` whose handler also receives Worker bindings and `ctx`.
|
|
35
|
-
* `workerContext()` reads them for the duration of the request.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createWorkerFetcher<Env>(options: CreateHandlerOptions): WorkerFetcher<Env>;
|
|
38
|
-
//#endregion
|
package/dist/workers.mjs
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { n as createFetcher } from "./fetch-Bmr9TEe3.mjs";
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
//#region src/adapters/workers/index.ts
|
|
4
|
-
const storage = new AsyncLocalStorage();
|
|
5
|
-
/**
|
|
6
|
-
* Bindings and execution context for the current Worker request.
|
|
7
|
-
*/
|
|
8
|
-
function workerContext() {
|
|
9
|
-
const context = storage.getStore();
|
|
10
|
-
if (!context) throw new Error("Called outside a worker request.");
|
|
11
|
-
return context;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* `createFetcher` whose handler also receives Worker bindings and `ctx`.
|
|
15
|
-
* `workerContext()` reads them for the duration of the request.
|
|
16
|
-
*/
|
|
17
|
-
function createWorkerFetcher(options) {
|
|
18
|
-
const fetcher = createFetcher(options);
|
|
19
|
-
const handler = (async (request, env, ctx) => storage.run({
|
|
20
|
-
env,
|
|
21
|
-
ctx
|
|
22
|
-
}, () => fetcher(request)));
|
|
23
|
-
handler.app = () => fetcher.app();
|
|
24
|
-
handler.close = () => fetcher.close();
|
|
25
|
-
return handler;
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
|
-
export { createWorkerFetcher, workerContext };
|