open-nest 0.0.0-stage → 0.0.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.
@@ -0,0 +1,61 @@
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
@@ -0,0 +1,46 @@
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 };
@@ -0,0 +1,61 @@
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
@@ -0,0 +1,46 @@
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 };
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "open-nest",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "Makes Nest applications compatible with Web Standard Request/Response handlers (Bun, Cloudflare, ...).",
5
+ "files": [
6
+ "package.json",
7
+ "readme.md",
8
+ "dist"
9
+ ],
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ "./fastify": {
14
+ "types": "./dist/fastify.d.mts",
15
+ "import": "./dist/fastify.mjs"
16
+ },
17
+ "./express": {
18
+ "types": "./dist/express.d.mts",
19
+ "import": "./dist/express.mjs"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "scripts": {
24
+ "dev": "tsdown --watch",
25
+ "build": "tsdown --clean",
26
+ "typecheck": "bun check",
27
+ "test": "bun test",
28
+ "lint": "oxlint --type-aware && oxfmt --check",
29
+ "format": "oxlint --fix --fix-suggestions && oxfmt"
30
+ },
31
+ "dependencies": {
32
+ "light-my-request": "6.8.0"
33
+ },
34
+ "devDependencies": {
35
+ "@arethetypeswrong/core": "0.18.5",
36
+ "@types/bun": "latest",
37
+ "@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",
42
+ "tsdown": "0.23.0",
43
+ "tsup": "8.5.1",
44
+ "typescript": "7.0.2"
45
+ },
46
+ "peerDependencies": {
47
+ "@nestjs/common": "^12.0.0",
48
+ "@nestjs/core": "^12.0.0",
49
+ "@nestjs/platform-express": "^12.0.0",
50
+ "@nestjs/platform-fastify": "^12.0.0",
51
+ "reflect-metadata": "^0.1.12 || ^0.2.0",
52
+ "rxjs": "^7.1.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "@nestjs/platform-express": {
56
+ "optional": true
57
+ },
58
+ "@nestjs/platform-fastify": {
59
+ "optional": true
60
+ }
61
+ }
62
+ }
package/readme.md ADDED
@@ -0,0 +1,94 @@
1
+ # `open-nest`
2
+
3
+ Turn a Nest application into a Web fetch handler. Pass a `Request`, get a `Response`, without calling `listen()`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add open-nest
9
+ ```
10
+
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.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ 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
+
21
+ const fetch = createFetcher({ module: AppModule });
22
+
23
+ await Bun.serve({
24
+ fetch,
25
+ port: 3000,
26
+ });
27
+ ```
28
+
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.
32
+
33
+ You can close the Nest app and clear the cache with `fetch.close()`.
34
+
35
+ ```ts
36
+ await fetch.close();
37
+ ```
38
+
39
+ #### Configure
40
+
41
+ `configure` runs after the app is created and before `init`. Use it for pipes, a global prefix, CORS, and the other app methods.
42
+
43
+ ```ts
44
+ const fetch = createFetcher({
45
+ module: AppModule,
46
+ configure: (app) => {
47
+ app.setGlobalPrefix("api");
48
+ },
49
+ });
50
+ ```
51
+
52
+ #### Nest options
53
+
54
+ `opts` is forwarded to `NestFactory.create` as `NestApplicationOptions`. When omitted, Nest keeps its own defaults.
55
+
56
+ ```ts
57
+ const fetch = createFetcher({
58
+ module: AppModule,
59
+ opts: { logger: ["error", "warn"] },
60
+ });
61
+ ```
62
+
63
+ ## Lifecycle
64
+
65
+ #### Develop
66
+
67
+ ```bash
68
+ bun dev
69
+ ```
70
+
71
+ #### Test
72
+
73
+ ```bash
74
+ bun test
75
+ ```
76
+
77
+ #### Build
78
+
79
+ ```bash
80
+ bun run build
81
+ ```
82
+
83
+ #### Typecheck
84
+
85
+ ```bash
86
+ bun typecheck
87
+ ```
88
+
89
+ #### Lint / Format
90
+
91
+ ```bash
92
+ bun lint
93
+ bun format
94
+ ```
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.