open-nest 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "open-nest",
3
- "version": "0.0.1",
4
- "description": "Makes Nest applications compatible with Web Standard Request/Response handlers (Bun, Cloudflare, ...).",
3
+ "version": "0.0.2",
4
+ "description": "Run any Nest application as a Web-Standard Request/Response handler. First class support for Bun, Node, and Cloudflare Workers.",
5
5
  "files": [
6
6
  "package.json",
7
7
  "readme.md",
@@ -10,13 +10,21 @@
10
10
  "type": "module",
11
11
  "sideEffects": false,
12
12
  "exports": {
13
- "./fastify": {
14
- "types": "./dist/fastify.d.mts",
15
- "import": "./dist/fastify.mjs"
13
+ ".": {
14
+ "types": "./dist/fetch.d.mts",
15
+ "import": "./dist/fetch.mjs"
16
16
  },
17
- "./express": {
18
- "types": "./dist/express.d.mts",
19
- "import": "./dist/express.mjs"
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"
20
28
  },
21
29
  "./package.json": "./package.json"
22
30
  },
@@ -26,36 +34,58 @@
26
34
  "typecheck": "bun check",
27
35
  "test": "bun test",
28
36
  "lint": "oxlint --type-aware && oxfmt --check",
29
- "format": "oxlint --fix --fix-suggestions && oxfmt"
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"
30
43
  },
31
44
  "dependencies": {
32
- "light-my-request": "6.8.0"
45
+ "ws": "8.22.0"
33
46
  },
34
47
  "devDependencies": {
35
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
+ "@nestjs/platform-express": "12.1.3",
54
+ "@nestjs/platform-socket.io": "12.1.3",
55
+ "@nestjs/websockets": "12.1.3",
36
56
  "@types/bun": "latest",
57
+ "@types/express": "5.0.6",
37
58
  "@types/node": "26.6.5",
38
- "oxfmt": "0.66.0",
39
- "oxlint": "1.81.0",
40
- "oxlint-tsgolint": "7.0.2001",
41
- "publint": "0.3.24",
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
+ "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",
42
75
  "tsdown": "0.23.0",
43
76
  "tsup": "8.5.1",
77
+ "tsx": "4.23.15",
44
78
  "typescript": "7.0.2"
45
79
  },
46
80
  "peerDependencies": {
47
81
  "@nestjs/common": "^12.0.0",
48
82
  "@nestjs/core": "^12.0.0",
49
- "@nestjs/platform-express": "^12.0.0",
50
- "@nestjs/platform-fastify": "^12.0.0",
83
+ "@nestjs/websockets": "^12.0.0",
51
84
  "reflect-metadata": "^0.1.12 || ^0.2.0",
52
85
  "rxjs": "^7.1.0"
53
86
  },
54
87
  "peerDependenciesMeta": {
55
- "@nestjs/platform-express": {
56
- "optional": true
57
- },
58
- "@nestjs/platform-fastify": {
88
+ "@nestjs/websockets": {
59
89
  "optional": true
60
90
  }
61
91
  }
package/readme.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # `open-nest`
2
2
 
3
- Turn a Nest application into a Web fetch handler. Pass a `Request`, get a `Response`, without calling `listen()`.
3
+ [![Workers](https://img.shields.io/badge/WORKERS-000000?style=for-the-badge&logo=cloudflareworkers&logoColor=white)](https://workers.cloudflare.com)
4
+ [![Bun](https://img.shields.io/badge/BUN-000000?style=for-the-badge&logo=bun&logoColor=white)](https://bun.sh)
5
+ [![Node](https://img.shields.io/badge/NODE-000000?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org)<br>
6
+ [![Validate](https://github.com/juriadams/open-nest/actions/workflows/validate.yml/badge.svg)](https://github.com/juriadams/open-nest/actions/workflows/validate.yml)
7
+ [![npm version](https://img.shields.io/npm/v/open-nest)](https://www.npmjs.com/package/open-nest)
8
+ [![bundle size](https://img.shields.io/bundlejs/size/open-nest)](https://bundlejs.com/?q=open-nest)
9
+
10
+ Run any Nest application as a Web-Standard `Request`/`Response` handler. First class support for **Bun**, **Node**, and **Cloudflare Workers**.
4
11
 
5
12
  ## Installation
6
13
 
@@ -8,87 +15,178 @@ Turn a Nest application into a Web fetch handler. Pass a `Request`, get a `Respo
8
15
  bun add open-nest
9
16
  ```
10
17
 
11
- Nest, `reflect-metadata`, `rxjs`, and your platform package are peer dependencies. An existing Nest app already has them. Import `open-nest/fastify` or `open-nest/express` to match the platform you use.
18
+ Peer dependencies are `@nestjs/common`, `@nestjs/core`, `reflect-metadata`, and `rxjs`. WebSocket gateways also need `@nestjs/websockets`. These should already ship with your Nest installation.
19
+
20
+ ## Examples
21
+
22
+ - [`examples/workers`](/examples/workers)<br />
23
+ <sup>[readme](/examples/workers/readme.md) ⋅ [code](/examples/workers/)</sup><br />
24
+ Run a Nest application on Cloudflare Workers.
25
+
26
+ - [`examples/bun`](/examples/bun)<br />
27
+ <sup>[readme](/examples/bun/readme.md) ⋅ [code](/examples/bun/)</sup><br />
28
+ Run a Nest application via `Bun.serve`.
29
+
30
+ - [`examples/bun-websockets`](/examples/bun-websockets)<br />
31
+ <sup>[readme](/examples/bun-websockets/readme.md) ⋅ [code](/examples/bun-websockets/)</sup><br />
32
+ Run a Nest application via `Bun.serve` with WebSockets.
33
+
34
+ - [`examples/node`](/examples/node)<br />
35
+ <sup>[readme](/examples/node/readme.md) ⋅ [code](/examples/node/)</sup><br />
36
+ Run a Nest application via `http.Server`.
37
+
38
+ - [`examples/node-websockets`](/examples/node-websockets)<br />
39
+ <sup>[readme](/examples/node-websockets/readme.md) ⋅ [code](/examples/node-websockets/)</sup><br />
40
+ Run a Nest application via `http.Server` with WebSockets.
12
41
 
13
42
  ## Usage
14
43
 
15
44
  ```ts
16
45
  import "reflect-metadata";
17
- import { Controller, Get, Module } from "@nestjs/common";
18
- import { createFetcher } from "open-nest/fastify";
19
- import { AppModule } from "@/app.module";
20
46
 
21
- const fetch = createFetcher({ module: AppModule });
47
+ import { createFetcher } from "open-nest";
48
+
49
+ import { AppModule } from "@/app/module";
50
+
51
+ const fetcher = createFetcher({ module: AppModule });
22
52
 
23
53
  await Bun.serve({
24
- fetch,
54
+ fetch: fetcher,
25
55
  port: 3000,
26
56
  });
27
57
  ```
28
58
 
29
- Express follows the same pattern, imported from `open-nest/express`.
30
-
31
- The Nest app boots on the first request and its instance is cached for all further requests.
59
+ `createFetcher` from `open-nest` is the HTTP entry. It returns a function from `Request` to `Response`. WebSocket gateways and static files are available for the respective runtime under `open-nest/bun` and `open-nest/node`.
32
60
 
33
- You can close the Nest app and clear the cache with `fetch.close()`.
61
+ ### Bun
34
62
 
35
63
  ```ts
36
- await fetch.close();
37
- ```
64
+ import "reflect-metadata";
38
65
 
39
- #### Configure
66
+ import { BunWsAdapter, createBunServer } from "open-nest/bun";
40
67
 
41
- `configure` runs after the app is created and before `init`. Use it for pipes, a global prefix, CORS, and the other app methods.
68
+ import { AppModule } from "@/app/module";
42
69
 
43
- ```ts
44
- const fetch = createFetcher({
70
+ await createBunServer({
45
71
  module: AppModule,
72
+ port: 3000,
46
73
  configure: (app) => {
47
- app.setGlobalPrefix("api");
74
+ app.useWebSocketAdapter(new BunWsAdapter());
75
+ app.useStaticAssets("public", { prefix: "/static" });
48
76
  },
49
77
  });
50
78
  ```
51
79
 
52
- #### Nest options
80
+ `createBunServer` from `open-nest/bun` serves that handler with `Bun.serve`. It upgrades gateway paths and serves files with `Bun.file()`.
53
81
 
54
- `opts` is forwarded to `NestFactory.create` as `NestApplicationOptions`. When omitted, Nest keeps its own defaults.
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()`.
85
+
86
+ ### Node
55
87
 
56
88
  ```ts
57
- const fetch = createFetcher({
89
+ import "reflect-metadata";
90
+
91
+ import { createNodeServer, NodeWsAdapter } from "open-nest/node";
92
+
93
+ import { AppModule } from "@/app/module";
94
+
95
+ await createNodeServer({
58
96
  module: AppModule,
59
- opts: { logger: ["error", "warn"] },
97
+ port: 3000,
98
+ configure: (app) => {
99
+ app.useWebSocketAdapter(new NodeWsAdapter());
100
+ app.useStaticAssets("public", { prefix: "/static" });
101
+ },
60
102
  });
61
103
  ```
62
104
 
63
- ## Lifecycle
105
+ `createNodeServer` from `open-nest/node` initializes the Nest application and starts and returns a `http.Server` instance. HTTP uses `node:http`, `node:stream`, and `node:fs` (`openAsBlob`).
64
106
 
65
- #### Develop
107
+ Gateway upgrades go through [`ws`](https://github.com/websockets/ws). The WebSocket client is the global `WebSocket` from Node 22 onward.
66
108
 
67
- ```bash
68
- bun dev
69
- ```
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`.
70
112
 
71
- #### Test
113
+ ### Workers
72
114
 
73
- ```bash
74
- bun test
115
+ ```ts
116
+ import "reflect-metadata";
117
+
118
+ import {
119
+ createWorkerFetcher,
120
+ workerContext,
121
+ type WorkerExecutionContext,
122
+ } from "open-nest/workers";
123
+
124
+ interface Env {
125
+ APP_NAME: string;
126
+ CACHE: KVNamespace;
127
+ }
128
+
129
+ @Controller()
130
+ class AppController {
131
+ @Get()
132
+ async index() {
133
+ const { env, ctx } = workerContext<Env>();
134
+ const lastOpenedAt = await env.CACHE.get("last-opened-at");
135
+
136
+ ctx.waitUntil(env.CACHE.put("last-opened-at", new Date().toISOString()));
137
+
138
+ return { data: { app: env.APP_NAME, lastOpenedAt }, error: null };
139
+ }
140
+ }
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
+ };
75
151
  ```
76
152
 
77
- #### Build
153
+ `createWorkerFetcher` from `open-nest/workers` initializes the Nest application and returns a handler of `(request, env, ctx)`.
78
154
 
79
- ```bash
80
- bun run build
155
+ Pass bindings and the execution context through, then read them with `workerContext()` from a controller or provider. The Worker needs the `nodejs_compat` compatibility flag.
156
+
157
+ - `createWorkerFetcher` initializes the Nest application once and returns a handler of `(request, env, ctx)`.
158
+ - `workerContext()` reads the bindings and the raw execution context for the request in flight.
159
+ - `WorkerExecutionContext` is the `ctx` argument: `waitUntil`, `passThroughOnException`, and `props`.
160
+
161
+ ### App Configuration
162
+
163
+ ```ts
164
+ const fetcher = createFetcher({
165
+ module: AppModule,
166
+ configure: (app) => {
167
+ app.enableCors();
168
+ app.setGlobalPrefix("api");
169
+ },
170
+ });
81
171
  ```
82
172
 
83
- #### Typecheck
173
+ `configure` runs after the app is created and before `init`. Use it for pipes, a global prefix, CORS, and the other app methods.
174
+
175
+ ### App Instance
84
176
 
85
- ```bash
86
- bun typecheck
177
+ ```ts
178
+ const app = await fetcher.app();
87
179
  ```
88
180
 
89
- #### Lint / Format
181
+ `app()` resolves the underlying Nest application.
90
182
 
91
- ```bash
92
- bun lint
93
- bun format
183
+ #### Nest Options
184
+
185
+ ```ts
186
+ const fetcher = createFetcher({
187
+ module: AppModule,
188
+ opts: { logger: ["error", "warn"] },
189
+ });
94
190
  ```
191
+
192
+ `opts` is forwarded to `NestFactory.create` as `NestApplicationOptions`. When omitted, Nest keeps its own defaults.
@@ -1,61 +0,0 @@
1
- import { NestExpressApplication } from "@nestjs/platform-express";
2
- import { NestApplicationOptions, Type } from "@nestjs/common";
3
- //#region src/adapters/express/index.d.ts
4
- /**
5
- * Options for {@link createFetcher}.
6
- */
7
- export interface CreateHandlerOptions {
8
- /**
9
- * Root Nest module.
10
- */
11
- module: Type<any>;
12
- /**
13
- * Optional hook to configure the app before init (pipes, prefix, cors, ...).
14
- *
15
- * @example
16
- * ```ts
17
- * const fetch = createFetcher({
18
- * module: AppModule,
19
- * configure: (app) => {
20
- * app.use(cors());
21
- * app.use(bodyParser.json());
22
- * },
23
- * });
24
- * ```
25
- */
26
- configure?: (app: NestExpressApplication) => void | Promise<void>;
27
- /**
28
- * Optional Nest factory options.
29
- *
30
- * @example
31
- * ```ts
32
- * const fetch = createFetcher({
33
- * module: AppModule,
34
- * opts: { logger: ["error", "warn"] },
35
- * });
36
- * ```
37
- */
38
- opts?: NestApplicationOptions;
39
- }
40
- export interface Fetcher {
41
- (request: Request): Promise<Response>;
42
- /**
43
- * Resolves the underlying Nest app instance.
44
- * @example
45
- * ```ts
46
- * const app = await fetch.app();
47
- * ```
48
- */
49
- app(): Promise<NestExpressApplication>;
50
- /**
51
- * Closes the underlying Nest app.
52
- *
53
- * @example
54
- * ```ts
55
- * await fetch.close();
56
- * ```
57
- */
58
- close(): Promise<void>;
59
- }
60
- export declare function createFetcher({ module, configure, opts }: CreateHandlerOptions): Fetcher;
61
- //#endregion
package/dist/express.mjs DELETED
@@ -1,46 +0,0 @@
1
- import { NestFactory } from "@nestjs/core";
2
- import { ExpressAdapter } from "@nestjs/platform-express";
3
- import inject from "light-my-request";
4
- //#region src/adapters/express/index.ts
5
- function createFetcher({ module, configure, opts }) {
6
- let pending;
7
- const instance = () => {
8
- if (pending) return pending;
9
- pending = (async () => {
10
- const app = await NestFactory.create(module, new ExpressAdapter(), opts);
11
- await configure?.(app);
12
- await app.init();
13
- return app;
14
- })();
15
- return pending;
16
- };
17
- const handler = (async (request) => {
18
- const app = await instance();
19
- const url = new URL(request.url);
20
- const hasBody = !["GET", "HEAD"].includes(request.method);
21
- const res = await inject(app.getHttpAdapter().getInstance(), {
22
- method: request.method,
23
- url: url.pathname + url.search,
24
- headers: Object.fromEntries(request.headers),
25
- payload: hasBody ? Buffer.from(await request.arrayBuffer()) : void 0
26
- });
27
- const headers = new Headers();
28
- for (const [k, v] of Object.entries(res.headers)) {
29
- if (k === "content-length" || k === "transfer-encoding") continue;
30
- if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
31
- else if (v != null) headers.set(k, String(v));
32
- }
33
- return new Response(res.rawPayload, {
34
- status: res.statusCode,
35
- headers
36
- });
37
- });
38
- handler.app = instance;
39
- handler.close = async () => {
40
- if (pending) await (await pending).close();
41
- pending = void 0;
42
- };
43
- return handler;
44
- }
45
- //#endregion
46
- export { createFetcher };
@@ -1,61 +0,0 @@
1
- import { NestFastifyApplication } from "@nestjs/platform-fastify";
2
- import { NestApplicationOptions, Type } from "@nestjs/common";
3
- //#region src/adapters/fastify/index.d.ts
4
- /**
5
- * Options for {@link createFetcher}.
6
- */
7
- export interface CreateHandlerOptions {
8
- /**
9
- * Root Nest module.
10
- */
11
- module: Type<any>;
12
- /**
13
- * Optional hook to configure the app before init (pipes, prefix, cors, ...).
14
- *
15
- * @example
16
- * ```ts
17
- * const fetch = createFetcher({
18
- * module: AppModule,
19
- * configure: (app) => {
20
- * app.use(cors());
21
- * app.use(bodyParser.json());
22
- * },
23
- * });
24
- * ```
25
- */
26
- configure?: (app: NestFastifyApplication) => void | Promise<void>;
27
- /**
28
- * Optional Nest factory options.
29
- *
30
- * @example
31
- * ```ts
32
- * const fetch = createFetcher({
33
- * module: AppModule,
34
- * opts: { logger: ["error", "warn"] },
35
- * });
36
- * ```
37
- */
38
- opts?: NestApplicationOptions;
39
- }
40
- export interface Fetcher {
41
- (request: Request): Promise<Response>;
42
- /**
43
- * Resolves the underlying Nest app instance.
44
- * @example
45
- * ```ts
46
- * const app = await fetch.app();
47
- * ```
48
- */
49
- app(): Promise<NestFastifyApplication>;
50
- /**
51
- * Closes the underlying Nest app.
52
- *
53
- * @example
54
- * ```ts
55
- * await fetch.close();
56
- * ```
57
- */
58
- close(): Promise<void>;
59
- }
60
- export declare function createFetcher({ module, configure, opts }: CreateHandlerOptions): Fetcher;
61
- //#endregion
package/dist/fastify.mjs DELETED
@@ -1,46 +0,0 @@
1
- import { NestFactory } from "@nestjs/core";
2
- import { FastifyAdapter } from "@nestjs/platform-fastify";
3
- //#region src/adapters/fastify/index.ts
4
- function createFetcher({ module, configure, opts }) {
5
- let pending;
6
- const instance = () => {
7
- if (pending) return pending;
8
- pending = (async () => {
9
- const app = await NestFactory.create(module, new FastifyAdapter(), opts);
10
- await configure?.(app);
11
- await app.init();
12
- await app.getHttpAdapter().getInstance().ready();
13
- return app;
14
- })();
15
- return pending;
16
- };
17
- const handler = (async (request) => {
18
- const app = await instance();
19
- const url = new URL(request.url);
20
- const hasBody = !["GET", "HEAD"].includes(request.method);
21
- const res = await app.getHttpAdapter().getInstance().inject({
22
- method: request.method,
23
- url: url.pathname + url.search,
24
- headers: Object.fromEntries(request.headers),
25
- payload: hasBody ? Buffer.from(await request.arrayBuffer()) : void 0
26
- });
27
- const headers = new Headers();
28
- for (const [k, v] of Object.entries(res.headers)) {
29
- if (k === "content-length" || k === "transfer-encoding") continue;
30
- if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
31
- else if (v != null) headers.set(k, String(v));
32
- }
33
- return new Response(res.rawPayload, {
34
- status: res.statusCode,
35
- headers
36
- });
37
- });
38
- handler.app = instance;
39
- handler.close = async () => {
40
- if (pending) await (await pending).close();
41
- pending = void 0;
42
- };
43
- return handler;
44
- }
45
- //#endregion
46
- export { createFetcher };