@authuser/nest 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +201 -0
  4. package/dist/authuser.module.d.ts +12 -0
  5. package/dist/authuser.module.d.ts.map +1 -0
  6. package/dist/authuser.module.js +32 -0
  7. package/dist/authuser.module.js.map +1 -0
  8. package/dist/configure-http-app.d.ts +4 -0
  9. package/dist/configure-http-app.d.ts.map +1 -0
  10. package/dist/configure-http-app.js +64 -0
  11. package/dist/configure-http-app.js.map +1 -0
  12. package/dist/create-http-app.d.ts +6 -0
  13. package/dist/create-http-app.d.ts.map +1 -0
  14. package/dist/create-http-app.js +37 -0
  15. package/dist/create-http-app.js.map +1 -0
  16. package/dist/decorators.d.ts +7 -0
  17. package/dist/decorators.d.ts.map +1 -0
  18. package/dist/decorators.js +8 -0
  19. package/dist/decorators.js.map +1 -0
  20. package/dist/http-exception.filter.d.ts +5 -0
  21. package/dist/http-exception.filter.d.ts.map +1 -0
  22. package/dist/http-exception.filter.js +43 -0
  23. package/dist/http-exception.filter.js.map +1 -0
  24. package/dist/index.d.ts +9 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +7 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/openapi.d.ts +5 -0
  29. package/dist/openapi.d.ts.map +1 -0
  30. package/dist/openapi.js +31 -0
  31. package/dist/openapi.js.map +1 -0
  32. package/dist/options.d.ts +21 -0
  33. package/dist/options.d.ts.map +1 -0
  34. package/dist/options.js +84 -0
  35. package/dist/options.js.map +1 -0
  36. package/dist/types.d.ts +88 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +2 -0
  39. package/dist/types.js.map +1 -0
  40. package/docs/configuration.md +72 -0
  41. package/docs/migration.md +22 -0
  42. package/docs/performance.md +25 -0
  43. package/docs/security.md +31 -0
  44. package/package.json +78 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
+ This project uses [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-09-03
9
+
10
+ ### Added
11
+
12
+ - NestJS 12 and Fastify 5 application factory.
13
+ - Secure, minimal and full presets.
14
+ - Secure headers, rate limiting, strict validation and bounded request bodies.
15
+ - Optional OpenAPI JSON and Swagger UI.
16
+ - Optional health endpoint and response compression.
17
+ - TypeScript 7 build and declarations.
18
+ - HTTP QUERY (RFC 10008) support in CORS defaults and integration coverage.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Authuser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ <div align="center">
2
+ <h1>@authuser/nest</h1>
3
+ <p><strong>A secure, fast and batteries-included NestJS foundation on Fastify.</strong></p>
4
+ </div>
5
+
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/@authuser/nest"><img alt="npm" src="https://img.shields.io/npm/v/%40authuser%2Fnest?style=flat-square"></a>
8
+ <a href="https://www.npmjs.com/package/@authuser/nest"><img alt="downloads" src="https://img.shields.io/npm/dm/%40authuser%2Fnest?style=flat-square"></a>
9
+ <img alt="Node 20+" src="https://img.shields.io/badge/node-%3E%3D20.11-339933?style=flat-square&logo=node.js&logoColor=white">
10
+ <img alt="NestJS 12" src="https://img.shields.io/badge/NestJS-12-E0234E?style=flat-square&logo=nestjs&logoColor=white">
11
+ <img alt="Fastify 5" src="https://img.shields.io/badge/Fastify-5-black?style=flat-square&logo=fastify">
12
+ <a href="./LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
13
+ </p>
14
+
15
+ `@authuser/nest` creates a normal `NestFastifyApplication` with careful production
16
+ defaults. It keeps controllers, modules, dependency injection and the rest of Nest
17
+ intact while removing repetitive bootstrap code.
18
+
19
+ ## Why this package?
20
+
21
+ - One install: Nest, Fastify, validation, security and optional docs are included.
22
+ - Fast path: request logs, compression and Swagger UI are off unless requested.
23
+ - Secure defaults: Helmet, strict DTO validation, bounded bodies, strict CORS and
24
+ rate limiting.
25
+ - OpenAPI is optional at runtime. Its code is dynamically imported only when enabled.
26
+ - HTTP QUERY (RFC 10008) passes through Nest 12/Fastify 5 and is included in CORS defaults.
27
+ - Native ESM, strict declarations and a TypeScript 7 toolchain.
28
+ - Escape hatch: the result is a regular Nest application and Fastify instance.
29
+
30
+ ## Requirements
31
+
32
+ - Node.js 20.11 or newer
33
+ - An ESM project is recommended (`"type": "module"`)
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install @authuser/nest
39
+ ```
40
+
41
+ No peer-dependency checklist is required. Runtime integrations such as
42
+ `@nestjs/swagger`, `class-validator` and the Fastify plugins are dependencies of
43
+ this package.
44
+
45
+ ## Quick start
46
+
47
+ ```ts
48
+ // src/main.ts
49
+ import { createNestApp } from '@authuser/nest';
50
+ import { AppModule } from './app.module.js';
51
+
52
+ const app = await createNestApp({
53
+ rootModule: AppModule,
54
+ appName: 'users-api',
55
+ preset: 'secure',
56
+ apiPrefix: 'api',
57
+ observability: {
58
+ logger: { level: process.env.LOG_LEVEL ?? 'info' },
59
+ },
60
+ });
61
+
62
+ await app.listen({ port: 3000, host: '0.0.0.0' });
63
+ ```
64
+
65
+ That application has Helmet, rate limiting, safe request IDs, strict validation,
66
+ a 1 MiB body limit and graceful shutdown hooks. Cross-origin requests are not
67
+ enabled implicitly.
68
+
69
+ ## Presets
70
+
71
+ | Preset | Intended use | Enabled by default |
72
+ | --- | --- | --- |
73
+ | `minimal` | Lowest framework overhead | validation and exception filter |
74
+ | `secure` | Public production API (default) | minimal + Helmet + rate limit |
75
+ | `full` | Feature-rich service | secure + compression + health + OpenAPI JSON |
76
+
77
+ Explicit options always override a preset.
78
+
79
+ ## OpenAPI and Swagger UI
80
+
81
+ OpenAPI is installed but disabled with `minimal` and `secure`. Enable only the
82
+ machine-readable document:
83
+
84
+ ```ts
85
+ const app = await createNestApp({
86
+ rootModule: AppModule,
87
+ openApi: {
88
+ enabled: true,
89
+ title: 'Users API',
90
+ version: '2.0.0',
91
+ jsonPath: '/openapi.json',
92
+ bearerAuth: true,
93
+ },
94
+ });
95
+ ```
96
+
97
+ Swagger UI is a separate opt-in because it adds routes and static assets:
98
+
99
+ ```ts
100
+ openApi: {
101
+ enabled: process.env.NODE_ENV !== 'production',
102
+ ui: true,
103
+ uiPath: '/docs',
104
+ }
105
+ ```
106
+
107
+ For an internet-facing production service, prefer exporting the JSON during CI or
108
+ protecting documentation routes at the gateway.
109
+
110
+ ## Configuration examples
111
+
112
+ ### CORS allowlist
113
+
114
+ ```ts
115
+ security: {
116
+ cors: {
117
+ origin: ['https://app.example.com'],
118
+ credentials: true,
119
+ methods: ['GET', 'POST', 'PATCH', 'DELETE'],
120
+ },
121
+ }
122
+ ```
123
+
124
+ ### Rate limiting behind a proxy
125
+
126
+ ```ts
127
+ network: { trustProxy: ['127.0.0.1', '10.0.0.0/8'] },
128
+ security: {
129
+ rateLimit: { max: 300, timeWindow: '1 minute' },
130
+ }
131
+ ```
132
+
133
+ Never enable `trustProxy: true` unless every direct connection comes through a
134
+ trusted proxy; client IPs are used as rate-limit keys.
135
+
136
+ ### Existing Nest + Fastify application
137
+
138
+ ```ts
139
+ import { configureHttpApp } from '@authuser/nest';
140
+
141
+ await configureHttpApp(app, {
142
+ preset: 'secure',
143
+ health: { enabled: true, path: '/healthz' },
144
+ });
145
+ ```
146
+
147
+ Call `configureHttpApp` before `app.init()` or `app.listen()` so Fastify can register
148
+ its plugins and routes.
149
+
150
+ ## Public API
151
+
152
+ - `createNestApp(options)` / `createHttpApp(options)` — create and configure an app.
153
+ - `configureHttpApp(app, options)` — configure an existing Fastify-based Nest app.
154
+ - `AuthuserModule.forRoot(options)` — expose immutable options through Nest DI.
155
+ - `AUTHUSER_OPTIONS` — injection token for those options.
156
+ - `Public()` and `Roles(...roles)` — metadata helpers for your own guards.
157
+ - `HttpExceptionFilter` — the default safe JSON exception filter.
158
+
159
+ The decorators intentionally do not provide authentication. Authentication policy
160
+ belongs to the consuming application; pretending otherwise would create a dangerous
161
+ security boundary.
162
+
163
+ See the [complete option reference](./docs/configuration.md), the
164
+ [security model](./docs/security.md), and the [performance guide](./docs/performance.md).
165
+
166
+ ## Performance notes
167
+
168
+ Fastify remains a strong fit when Nest's architecture is required. A raw Fastify
169
+ service will be lighter and usually faster because it removes Nest's routing and DI
170
+ overhead; frameworks such as µWebSockets.js can win synthetic throughput tests but
171
+ require a substantially different ecosystem and programming model. Measure the real
172
+ application before trading away Nest maintainability.
173
+
174
+ For the hottest path, use `preset: 'minimal'`, leave request logging and compression
175
+ off, declare response schemas in Fastify-compatible routes, and benchmark with your
176
+ actual payloads. See [docs/performance.md](./docs/performance.md).
177
+
178
+ ## Migration from `@authuser/nest-fastify-kit`
179
+
180
+ ```diff
181
+ - import { createHttpApp } from '@authuser/nest-fastify-kit';
182
+ + import { createHttpApp } from '@authuser/nest';
183
+ ```
184
+
185
+ The new package targets Nest 12, uses ESM, calls Swagger configuration `openApi`, and
186
+ ships required integrations as normal dependencies. See the
187
+ [migration guide](./docs/migration.md).
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ npm install
193
+ npm run check
194
+ ```
195
+
196
+ An executable API and an importable Postman collection are available in the
197
+ [`example`](./example) directory.
198
+
199
+ ## License
200
+
201
+ [MIT](./LICENSE) © Authuser
@@ -0,0 +1,12 @@
1
+ import { type DynamicModule } from '@nestjs/common';
2
+ import type { ConfigureHttpAppOptions } from './types.js';
3
+ export declare const AUTHUSER_OPTIONS: unique symbol;
4
+ export interface AuthuserModuleAsyncOptions {
5
+ inject?: Array<string | symbol | Function>;
6
+ useFactory: (...dependencies: never[]) => ConfigureHttpAppOptions | Promise<ConfigureHttpAppOptions>;
7
+ }
8
+ export declare class AuthuserModule {
9
+ static forRoot(options?: ConfigureHttpAppOptions): DynamicModule;
10
+ static forRootAsync(options: AuthuserModuleAsyncOptions): DynamicModule;
11
+ }
12
+ //# sourceMappingURL=authuser.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authuser.module.d.ts","sourceRoot":"","sources":["../src/authuser.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,eAAO,MAAM,gBAAgB,eAAuC,CAAC;AAErE,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC3C,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,KAAK,EAAE,KAAK,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtG;AAED,qBAEa,cAAc;IACzB,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,uBAA4B,GAAG,aAAa,CAMnE;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAOtE;CACF"}
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var AuthuserModule_1;
8
+ import { Global, Module } from '@nestjs/common';
9
+ export const AUTHUSER_OPTIONS = Symbol.for('@authuser/nest/options');
10
+ let AuthuserModule = AuthuserModule_1 = class AuthuserModule {
11
+ static forRoot(options = {}) {
12
+ return {
13
+ module: AuthuserModule_1,
14
+ providers: [{ provide: AUTHUSER_OPTIONS, useValue: Object.freeze({ ...options }) }],
15
+ exports: [AUTHUSER_OPTIONS],
16
+ };
17
+ }
18
+ static forRootAsync(options) {
19
+ const provider = {
20
+ provide: AUTHUSER_OPTIONS,
21
+ inject: options.inject ?? [],
22
+ useFactory: options.useFactory,
23
+ };
24
+ return { module: AuthuserModule_1, providers: [provider], exports: [provider] };
25
+ }
26
+ };
27
+ AuthuserModule = AuthuserModule_1 = __decorate([
28
+ Global(),
29
+ Module({})
30
+ ], AuthuserModule);
31
+ export { AuthuserModule };
32
+ //# sourceMappingURL=authuser.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authuser.module.js","sourceRoot":"","sources":["../src/authuser.module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAqC,MAAM,gBAAgB,CAAC;AAGnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAS9D,IAAM,cAAc,sBAApB,MAAM,cAAc;IAClB,AAAP,MAAM,CAAC,OAAO,CAAC,OAAO,GAA4B,EAAE;QAClD,OAAO;YACL,MAAM,kBAAgB;YACtB,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,CAAC,gBAAgB,CAAC;SAC5B,CAAC;IACJ,CAAC;IAEM,AAAP,MAAM,CAAC,YAAY,CAAC,OAAmC;QACrD,MAAM,QAAQ,GAAa;YACzB,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;YAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC;QACF,OAAO,EAAE,MAAM,kBAAgB,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;IAChF,CAAC;CACF,CAAA;AAjBY,cAAc;IAF1B,MAAM,EAAE;IACR,MAAM,CAAC,EAAE,CAAC;GACE,cAAc,CAiB1B"}
@@ -0,0 +1,4 @@
1
+ import type { NestFastifyApplication } from '@nestjs/platform-fastify';
2
+ import type { ConfigureHttpAppOptions } from './types.js';
3
+ export declare function configureHttpApp(app: NestFastifyApplication, input?: ConfigureHttpAppOptions): Promise<NestFastifyApplication>;
4
+ //# sourceMappingURL=configure-http-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-http-app.d.ts","sourceRoot":"","sources":["../src/configure-http-app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAKvE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,sBAAsB,EAC3B,KAAK,GAAE,uBAA4B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CA2DjC"}
@@ -0,0 +1,64 @@
1
+ import compress from '@fastify/compress';
2
+ import cors from '@fastify/cors';
3
+ import helmet from '@fastify/helmet';
4
+ import rateLimit from '@fastify/rate-limit';
5
+ import { ValidationPipe, VersioningType } from '@nestjs/common';
6
+ import { HttpExceptionFilter } from './http-exception.filter.js';
7
+ import { registerOpenApi } from './openapi.js';
8
+ import { resolveOptions } from './options.js';
9
+ export async function configureHttpApp(app, input = {}) {
10
+ const options = resolveOptions(input);
11
+ const fastify = app.getHttpAdapter().getInstance();
12
+ if (options.security.helmet) {
13
+ await fastify.register(helmet, {
14
+ global: true,
15
+ contentSecurityPolicy: options.security.contentSecurityPolicy,
16
+ });
17
+ }
18
+ if (options.security.cors !== false) {
19
+ await fastify.register(cors, options.security.cors);
20
+ }
21
+ if (options.security.rateLimit !== false && options.security.rateLimit.enabled) {
22
+ const { enabled: _enabled, ...rateLimitOptions } = options.security.rateLimit;
23
+ await fastify.register(rateLimit, rateLimitOptions);
24
+ }
25
+ if (options.compression.enabled) {
26
+ await fastify.register(compress, {
27
+ threshold: options.compression.threshold,
28
+ encodings: options.compression.encodings,
29
+ });
30
+ }
31
+ if (options.apiPrefix)
32
+ app.setGlobalPrefix(options.apiPrefix);
33
+ if (options.versioning) {
34
+ app.enableVersioning(options.versioning === true
35
+ ? { type: VersioningType.URI }
36
+ : options.versioning);
37
+ }
38
+ if (options.validation !== false)
39
+ app.useGlobalPipes(new ValidationPipe(options.validation));
40
+ app.useGlobalFilters(new HttpExceptionFilter());
41
+ if (options.shutdownHooks)
42
+ app.enableShutdownHooks();
43
+ if (options.observability.responseTimeHeader) {
44
+ const startTimes = new WeakMap();
45
+ fastify.addHook('onRequest', async (request) => {
46
+ startTimes.set(request, process.hrtime.bigint());
47
+ });
48
+ fastify.addHook('onSend', async (request, reply, payload) => {
49
+ const start = startTimes.get(request);
50
+ if (typeof start === 'bigint') {
51
+ reply.header('server-timing', `app;dur=${(Number(process.hrtime.bigint() - start) / 1e6).toFixed(2)}`);
52
+ }
53
+ return payload;
54
+ });
55
+ }
56
+ if (options.health.enabled) {
57
+ fastify.get(options.health.path, {
58
+ config: { rateLimit: false },
59
+ }, async (_request, reply) => reply.send(options.health.response));
60
+ }
61
+ await registerOpenApi(app, fastify, options.openApi);
62
+ return app;
63
+ }
64
+ //# sourceMappingURL=configure-http-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-http-app.js","sourceRoot":"","sources":["../src/configure-http-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type NestFastifyApplication } from '@nestjs/platform-fastify';
2
+ import type { CreateHttpAppOptions } from './types.js';
3
+ export declare function createHttpApp(options: CreateHttpAppOptions): Promise<NestFastifyApplication>;
4
+ /** A descriptive alias for new projects. */
5
+ export declare const createNestApp: typeof createHttpApp;
6
+ //# sourceMappingURL=create-http-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-http-app.d.ts","sourceRoot":"","sources":["../src/create-http-app.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIvD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4BlG;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,sBAAgB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { LogController } from 'fastify';
3
+ import { NestFactory } from '@nestjs/core';
4
+ import { FastifyAdapter, } from '@nestjs/platform-fastify';
5
+ import { configureHttpApp } from './configure-http-app.js';
6
+ import { resolveOptions } from './options.js';
7
+ const SAFE_REQUEST_ID = /^[A-Za-z0-9._:-]{1,128}$/;
8
+ export async function createHttpApp(options) {
9
+ const resolved = resolveOptions(options);
10
+ const network = options.network ?? {};
11
+ const adapter = new FastifyAdapter({
12
+ logger: resolved.observability.logger,
13
+ logController: new LogController({
14
+ disableRequestLogging: !resolved.observability.requestLogging,
15
+ }),
16
+ trustProxy: network.trustProxy ?? false,
17
+ bodyLimit: network.bodyLimit ?? 1_048_576,
18
+ routerOptions: { maxParamLength: network.maxParamLength ?? 100 },
19
+ connectionTimeout: network.connectionTimeout ?? 0,
20
+ requestTimeout: network.requestTimeout ?? 0,
21
+ keepAliveTimeout: network.keepAliveTimeout ?? 72_000,
22
+ requestIdHeader: 'x-request-id',
23
+ genReqId: safeRequestId,
24
+ });
25
+ const nestOptions = resolved.observability.logger === false
26
+ ? { ...options.nest, logger: false }
27
+ : { ...options.nest };
28
+ const app = await NestFactory.create(options.rootModule, adapter, nestOptions);
29
+ return configureHttpApp(app, options);
30
+ }
31
+ /** A descriptive alias for new projects. */
32
+ export const createNestApp = createHttpApp;
33
+ function safeRequestId(request) {
34
+ const incoming = request.headers['x-request-id'];
35
+ return typeof incoming === 'string' && SAFE_REQUEST_ID.test(incoming) ? incoming : randomUUID();
36
+ }
37
+ //# sourceMappingURL=create-http-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-http-app.js","sourceRoot":"","sources":["../src/create-http-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,cAAc,GAEf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC;QACjC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,MAAM;QACrC,aAAa,EAAE,IAAI,aAAa,CAAC;YAC/B,qBAAqB,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc;SAC9D,CAAC;QACF,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;QACvC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,SAAS;QACzC,aAAa,EAAE,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,GAAG,EAAE;QAChE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,CAAC;QACjD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,CAAC;QAC3C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,MAAM;QACpD,eAAe,EAAE,cAAc;QAC/B,QAAQ,EAAE,aAAa;KACxB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK;QACzD,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAc,EAAE;QAC7C,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,MAAM,CAClC,OAAO,CAAC,UAAU,EAClB,OAAO,EACP,WAAW,CACZ,CAAC;IAEF,OAAO,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAE3C,SAAS,aAAa,CAAC,OAAwB;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACjD,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AAClG,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const IS_PUBLIC_KEY = "authuser:isPublic";
2
+ export declare const ROLES_KEY = "authuser:roles";
3
+ /** Marks a handler or controller as public for application-defined guards. */
4
+ export declare const Public: () => MethodDecorator & ClassDecorator;
5
+ /** Attaches required roles for application-defined guards. */
6
+ export declare const Roles: (...roles: string[]) => MethodDecorator & ClassDecorator;
7
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,MAAM,QAAO,eAAe,GAAG,cAAkD,CAAC;AAE/F,8DAA8D;AAC9D,eAAO,MAAM,KAAK,aAAc,MAAM,EAAE,KAAG,eAAe,GAAG,cAA+C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+ export const IS_PUBLIC_KEY = 'authuser:isPublic';
3
+ export const ROLES_KEY = 'authuser:roles';
4
+ /** Marks a handler or controller as public for application-defined guards. */
5
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
6
+ /** Attaches required roles for application-defined guards. */
7
+ export const Roles = (...roles) => SetMetadata(ROLES_KEY, roles);
8
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC;AACjD,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,MAAM,GAAG,GAAqC,EAAE,CAAC,WAAW,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAe,EAAoC,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { ArgumentsHost, type ExceptionFilter } from '@nestjs/common';
2
+ export declare class HttpExceptionFilter implements ExceptionFilter {
3
+ catch(exception: unknown, host: ArgumentsHost): void;
4
+ }
5
+ //# sourceMappingURL=http-exception.filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-exception.filter.d.ts","sourceRoot":"","sources":["../src/http-exception.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAIb,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAGxB,qBAAa,mBAAoB,YAAW,eAAe;IACzD,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAqBnD;CACF"}
@@ -0,0 +1,43 @@
1
+ import { Catch, HttpException, HttpStatus, } from '@nestjs/common';
2
+ export class HttpExceptionFilter {
3
+ catch(exception, host) {
4
+ const context = host.switchToHttp();
5
+ const reply = context.getResponse();
6
+ const request = context.getRequest();
7
+ const httpException = getHttpException(exception);
8
+ const status = httpException?.getStatus() ?? HttpStatus.INTERNAL_SERVER_ERROR;
9
+ const response = httpException?.getResponse();
10
+ const details = typeof response === 'object' && response !== null ? response : {};
11
+ const message = typeof response === 'string'
12
+ ? response
13
+ : getMessage(details) ?? (status === 500 ? 'Internal server error' : 'Request failed');
14
+ if (status === 500)
15
+ request.log.error({ err: exception }, 'Unhandled request error');
16
+ void reply.status(status).send({
17
+ statusCode: status,
18
+ error: HttpStatus[status] ?? 'Error',
19
+ message,
20
+ requestId: request.id,
21
+ path: request.url,
22
+ });
23
+ }
24
+ }
25
+ Catch()(HttpExceptionFilter);
26
+ function getMessage(value) {
27
+ return 'message' in value ? value.message : undefined;
28
+ }
29
+ function getHttpException(exception) {
30
+ if (exception instanceof HttpException)
31
+ return exception;
32
+ if (typeof exception !== 'object' || exception === null)
33
+ return undefined;
34
+ const candidate = exception;
35
+ if (typeof candidate.getStatus !== 'function' || typeof candidate.getResponse !== 'function') {
36
+ return undefined;
37
+ }
38
+ const status = candidate.getStatus.call(exception);
39
+ return Number.isInteger(status) && status >= 400 && status <= 599
40
+ ? candidate
41
+ : undefined;
42
+ }
43
+ //# sourceMappingURL=http-exception.filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-exception.filter.js","sourceRoot":"","sources":["../src/http-exception.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,aAAa,EACb,UAAU,GAEX,MAAM,gBAAgB,CAAC;AAGxB,MAAM,OAAO,mBAAmB;IAC9B,KAAK,CAAC,SAAkB,EAAE,IAAmB;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAgB,CAAC;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAkB,CAAC;QACrD,MAAM,aAAa,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,aAAa,EAAE,SAAS,EAAE,IAAI,UAAU,CAAC,qBAAqB,CAAC;QAC9E,MAAM,QAAQ,GAAG,aAAa,EAAE,WAAW,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ;YAC1C,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAEzF,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAErF,KAAK,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;YAC7B,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,OAAO;YACpC,OAAO;YACP,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,IAAI,EAAE,OAAO,CAAC,GAAG;SAClB,CAAC,CAAC;IACL,CAAC;CACF;AAED,KAAK,EAAE,CAAC,mBAAmB,CAAC,CAAC;AAE7B,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAOD,SAAS,gBAAgB,CAAC,SAAkB;IAC1C,IAAI,SAAS,YAAY,aAAa;QAAE,OAAO,SAAS,CAAC;IACzD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAE1E,MAAM,SAAS,GAAG,SAAuC,CAAC;IAC1D,IAAI,OAAO,SAAS,CAAC,SAAS,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG;QAC/D,CAAC,CAAC,SAA8B;QAChC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import 'reflect-metadata';
2
+ export { AuthuserModule, AUTHUSER_OPTIONS } from './authuser.module.js';
3
+ export type { AuthuserModuleAsyncOptions } from './authuser.module.js';
4
+ export { configureHttpApp } from './configure-http-app.js';
5
+ export { createHttpApp, createNestApp } from './create-http-app.js';
6
+ export { Public, Roles, IS_PUBLIC_KEY, ROLES_KEY } from './decorators.js';
7
+ export { HttpExceptionFilter } from './http-exception.filter.js';
8
+ export type { CompressionOptions, ConfigureHttpAppOptions, CorsOptions, CreateHttpAppOptions, HealthOptions, NetworkOptions, ObservabilityOptions, OpenApiOptions, RateLimitOptions, SecurityOptions, SecurityPreset, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import 'reflect-metadata';
2
+ export { AuthuserModule, AUTHUSER_OPTIONS } from './authuser.module.js';
3
+ export { configureHttpApp } from './configure-http-app.js';
4
+ export { createHttpApp, createNestApp } from './create-http-app.js';
5
+ export { Public, Roles, IS_PUBLIC_KEY, ROLES_KEY } from './decorators.js';
6
+ export { HttpExceptionFilter } from './http-exception.filter.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { NestFastifyApplication } from '@nestjs/platform-fastify';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import type { ResolvedOptions } from './options.js';
4
+ export declare function registerOpenApi(app: NestFastifyApplication, fastify: FastifyInstance, options: ResolvedOptions['openApi']): Promise<void>;
5
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,wBAAsB,eAAe,CACnC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -0,0 +1,31 @@
1
+ export async function registerOpenApi(app, fastify, options) {
2
+ if (!options.enabled)
3
+ return;
4
+ const { DocumentBuilder, SwaggerModule } = await import('@nestjs/swagger');
5
+ let builder = new DocumentBuilder()
6
+ .setTitle(options.title)
7
+ .setDescription(options.description)
8
+ .setVersion(options.version);
9
+ if (options.bearerAuth)
10
+ builder = builder.addBearerAuth();
11
+ const document = SwaggerModule.createDocument(app, builder.build());
12
+ if (options.json) {
13
+ fastify.get(options.jsonPath, async (_request, reply) => reply.send(document));
14
+ }
15
+ if (options.ui) {
16
+ const { default: swagger } = await import('@fastify/swagger');
17
+ const { default: swaggerUi } = await import('@fastify/swagger-ui');
18
+ await fastify.register(swagger, {
19
+ mode: 'static',
20
+ // Nest and Fastify publish structurally compatible but distinct OpenAPI types.
21
+ specification: { document: document },
22
+ });
23
+ await fastify.register(swaggerUi, {
24
+ routePrefix: options.uiPath,
25
+ uiConfig: { docExpansion: 'list', deepLinking: true, spec: document },
26
+ staticCSP: true,
27
+ transformStaticCSP: (header) => header,
28
+ });
29
+ }
30
+ }
31
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAA2B,EAC3B,OAAwB,EACxB,OAAmC;IAEnC,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO;IAE7B,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3E,IAAI,OAAO,GAAG,IAAI,eAAe,EAAE;SAChC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;SACvB,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC;SACnC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,OAAO,CAAC,UAAU;QAAE,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAE1D,MAAM,QAAQ,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAEpE,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC9D,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACnE,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE;YAC9B,IAAI,EAAE,QAAQ;YACd,+EAA+E;YAC/E,aAAa,EAAE,EAAE,QAAQ,EAAE,QAAiB,EAAE;SAC/C,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE;YAChC,WAAW,EAAE,OAAO,CAAC,MAAM;YAC3B,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;YACrE,SAAS,EAAE,IAAI;YACf,kBAAkB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;SACvC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { CompressionOptions, ConfigureHttpAppOptions, CorsOptions, HealthOptions, OpenApiOptions, RateLimitOptions, SecurityPreset } from './types.js';
2
+ export interface ResolvedOptions {
3
+ appName: string;
4
+ preset: SecurityPreset;
5
+ apiPrefix?: string;
6
+ versioning: ConfigureHttpAppOptions['versioning'];
7
+ validation: ConfigureHttpAppOptions['validation'];
8
+ security: {
9
+ helmet: boolean;
10
+ contentSecurityPolicy: boolean;
11
+ cors: false | CorsOptions;
12
+ rateLimit: false | (Required<Pick<RateLimitOptions, 'enabled' | 'max' | 'timeWindow' | 'ban'>> & RateLimitOptions);
13
+ };
14
+ compression: Required<CompressionOptions>;
15
+ openApi: Required<OpenApiOptions>;
16
+ health: Required<HealthOptions>;
17
+ observability: Required<NonNullable<ConfigureHttpAppOptions['observability']>>;
18
+ shutdownHooks: boolean;
19
+ }
20
+ export declare function resolveOptions(options: ConfigureHttpAppOptions): ResolvedOptions;
21
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,QAAQ,EAAE;QACR,MAAM,EAAE,OAAO,CAAC;QAChB,qBAAqB,EAAE,OAAO,CAAC;QAC/B,IAAI,EAAE,KAAK,GAAG,WAAW,CAAC;QAC1B,SAAS,EAAE,KAAK,IAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,CAAC,CAAC,GAAG,gBAAgB,CAAA,CAAC;KAClH,CAAC;IACF,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC1C,OAAO,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAClC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAChC,aAAa,EAAE,QAAQ,CAAC,WAAW,CAAC,uBAAuB,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC/E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,uBAAuB,GAAG,eAAe,CA+EhF"}
@@ -0,0 +1,84 @@
1
+ export function resolveOptions(options) {
2
+ const preset = options.preset ?? 'secure';
3
+ const isSecure = preset !== 'minimal';
4
+ const isFull = preset === 'full';
5
+ const security = options.security ?? {};
6
+ const rateLimitInput = security.rateLimit === false ? false : security.rateLimit ?? {};
7
+ const compressionInput = typeof options.compression === 'object' ? options.compression : {};
8
+ const openApiInput = typeof options.openApi === 'object' ? options.openApi : {};
9
+ const healthInput = typeof options.health === 'object' ? options.health : {};
10
+ const resolved = {
11
+ appName: options.appName ?? 'nest-api',
12
+ preset,
13
+ versioning: options.versioning ?? false,
14
+ validation: options.validation === undefined
15
+ ? { whitelist: true, forbidNonWhitelisted: true, transform: false }
16
+ : options.validation,
17
+ security: {
18
+ helmet: security.helmet ?? isSecure,
19
+ contentSecurityPolicy: security.contentSecurityPolicy ?? true,
20
+ cors: security.cors === undefined || security.cors === false
21
+ ? false
22
+ : {
23
+ ...security.cors,
24
+ origin: security.cors.origin ?? false,
25
+ methods: security.cors.methods ?? [
26
+ 'GET',
27
+ 'HEAD',
28
+ 'POST',
29
+ 'PUT',
30
+ 'PATCH',
31
+ 'DELETE',
32
+ 'OPTIONS',
33
+ 'QUERY',
34
+ ],
35
+ optionsSuccessStatus: security.cors.optionsSuccessStatus ?? 204,
36
+ strictPreflight: security.cors.strictPreflight ?? true,
37
+ },
38
+ rateLimit: rateLimitInput === false
39
+ ? false
40
+ : {
41
+ ...rateLimitInput,
42
+ enabled: rateLimitInput.enabled ?? isSecure,
43
+ max: rateLimitInput.max ?? 100,
44
+ timeWindow: rateLimitInput.timeWindow ?? '1 minute',
45
+ ban: rateLimitInput.ban ?? -1,
46
+ },
47
+ },
48
+ compression: {
49
+ enabled: typeof options.compression === 'boolean' ? options.compression : compressionInput.enabled ?? isFull,
50
+ threshold: compressionInput.threshold ?? 1024,
51
+ encodings: compressionInput.encodings ?? ['br', 'gzip', 'deflate'],
52
+ },
53
+ openApi: {
54
+ enabled: typeof options.openApi === 'boolean' ? options.openApi : openApiInput.enabled ?? isFull,
55
+ title: openApiInput.title ?? options.appName ?? 'Nest API',
56
+ description: openApiInput.description ?? '',
57
+ version: openApiInput.version ?? '1.0.0',
58
+ json: openApiInput.json ?? true,
59
+ jsonPath: normalizePath(openApiInput.jsonPath ?? '/openapi.json'),
60
+ ui: openApiInput.ui ?? false,
61
+ uiPath: normalizePath(openApiInput.uiPath ?? '/docs'),
62
+ bearerAuth: openApiInput.bearerAuth ?? false,
63
+ },
64
+ health: {
65
+ enabled: typeof options.health === 'boolean' ? options.health : healthInput.enabled ?? isFull,
66
+ path: normalizePath(healthInput.path ?? '/health'),
67
+ response: healthInput.response ?? { status: 'ok' },
68
+ },
69
+ observability: {
70
+ logger: options.observability?.logger ?? true,
71
+ requestLogging: options.observability?.requestLogging ?? false,
72
+ responseTimeHeader: options.observability?.responseTimeHeader ?? false,
73
+ },
74
+ shutdownHooks: options.shutdownHooks ?? true,
75
+ };
76
+ if (options.apiPrefix !== undefined)
77
+ resolved.apiPrefix = options.apiPrefix.replace(/^\/+|\/+$/g, '');
78
+ return resolved;
79
+ }
80
+ function normalizePath(path) {
81
+ const normalized = `/${path}`.replace(/\/+/g, '/').replace(/\/$/, '');
82
+ return normalized || '/';
83
+ }
84
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import type { LoggerOptions } from 'pino';
2
+ import type { Type, ValidationPipeOptions, VersioningOptions } from '@nestjs/common';
3
+ import type { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
4
+ import type { FastifyRequest } from 'fastify';
5
+ export type SecurityPreset = 'minimal' | 'secure' | 'full';
6
+ export interface CorsOptions {
7
+ /** No cross-origin requests are allowed by default. */
8
+ origin?: boolean | string | RegExp | Array<string | RegExp>;
9
+ credentials?: boolean;
10
+ methods?: string[];
11
+ allowedHeaders?: string[];
12
+ exposedHeaders?: string[];
13
+ maxAge?: number;
14
+ /** Status returned for a successful preflight. HTTP 204 is the default. */
15
+ optionsSuccessStatus?: number;
16
+ strictPreflight?: boolean;
17
+ }
18
+ export interface RateLimitOptions {
19
+ enabled?: boolean;
20
+ max?: number;
21
+ timeWindow?: number | string;
22
+ ban?: number;
23
+ allowList?: string[];
24
+ keyGenerator?: (request: FastifyRequest) => string;
25
+ }
26
+ export interface SecurityOptions {
27
+ helmet?: boolean;
28
+ /** Helmet Content-Security-Policy. Enabled by default. */
29
+ contentSecurityPolicy?: boolean;
30
+ cors?: false | CorsOptions;
31
+ rateLimit?: false | RateLimitOptions;
32
+ }
33
+ export interface CompressionOptions {
34
+ enabled?: boolean;
35
+ threshold?: number;
36
+ encodings?: Array<'br' | 'gzip' | 'deflate' | 'identity'>;
37
+ }
38
+ export interface OpenApiOptions {
39
+ enabled?: boolean;
40
+ title?: string;
41
+ description?: string;
42
+ version?: string;
43
+ /** Serve the generated OpenAPI document. Set false to generate only. */
44
+ json?: boolean;
45
+ jsonPath?: string;
46
+ /** Swagger UI is opt-in and disabled by default. */
47
+ ui?: boolean;
48
+ uiPath?: string;
49
+ bearerAuth?: boolean;
50
+ }
51
+ export interface HealthOptions {
52
+ enabled?: boolean;
53
+ path?: string;
54
+ response?: Readonly<Record<string, unknown>>;
55
+ }
56
+ export interface ObservabilityOptions {
57
+ /** Fastify/Pino logging. False removes request logging overhead. */
58
+ logger?: boolean | LoggerOptions;
59
+ requestLogging?: boolean;
60
+ responseTimeHeader?: boolean;
61
+ }
62
+ export interface NetworkOptions {
63
+ trustProxy?: boolean | string | string[];
64
+ bodyLimit?: number;
65
+ maxParamLength?: number;
66
+ connectionTimeout?: number;
67
+ requestTimeout?: number;
68
+ keepAliveTimeout?: number;
69
+ }
70
+ export interface ConfigureHttpAppOptions {
71
+ appName?: string;
72
+ preset?: SecurityPreset;
73
+ apiPrefix?: string;
74
+ versioning?: boolean | VersioningOptions;
75
+ validation?: false | ValidationPipeOptions;
76
+ security?: SecurityOptions;
77
+ compression?: boolean | CompressionOptions;
78
+ openApi?: boolean | OpenApiOptions;
79
+ health?: boolean | HealthOptions;
80
+ observability?: ObservabilityOptions;
81
+ shutdownHooks?: boolean;
82
+ }
83
+ export interface CreateHttpAppOptions extends ConfigureHttpAppOptions {
84
+ rootModule: Type<unknown>;
85
+ network?: NetworkOptions;
86
+ nest?: Omit<NestApplicationOptions, 'logger'>;
87
+ }
88
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ # Configuration reference
2
+
3
+ All settings are optional except `rootModule` when using `createNestApp`.
4
+
5
+ ## Application
6
+
7
+ | Option | Default | Description |
8
+ | --- | --- | --- |
9
+ | `rootModule` | required | Root Nest module. |
10
+ | `appName` | `nest-api` | Name used by generated OpenAPI metadata. |
11
+ | `preset` | `secure` | `minimal`, `secure`, or `full`. |
12
+ | `apiPrefix` | none | Global prefix without a leading slash. |
13
+ | `versioning` | `false` | `true` for URI versioning, or Nest versioning options. |
14
+ | `shutdownHooks` | `true` | Enable Nest shutdown signal hooks. |
15
+ | `validation` | strict defaults | `false` or Nest `ValidationPipeOptions`. |
16
+ | `nest` | `{}` | Extra `NestApplicationOptions` except logger. |
17
+
18
+ Default validation uses `whitelist: true`, `forbidNonWhitelisted: true`, and
19
+ `transform: false`. Transformation is off because implicit coercion costs work and
20
+ can hide unexpected input. Enable it explicitly when DTO conversion is required.
21
+
22
+ ## Network
23
+
24
+ | Option | Default | Description |
25
+ | --- | --- | --- |
26
+ | `network.bodyLimit` | `1048576` | Maximum body in bytes. |
27
+ | `network.maxParamLength` | `100` | Maximum route parameter length. |
28
+ | `network.trustProxy` | `false` | Fastify trusted-proxy policy. |
29
+ | `network.keepAliveTimeout` | `72000` | Keep-alive timeout in milliseconds. |
30
+ | `network.connectionTimeout` | `0` | Socket inactivity timeout. |
31
+ | `network.requestTimeout` | `0` | Incoming request timeout. Configure a finite value at the edge. |
32
+
33
+ ## Security
34
+
35
+ `security.helmet` and `security.rateLimit` are enabled by secure/full presets.
36
+ CORS remains disabled unless an explicit configuration is supplied.
37
+ When enabled, the default advertised methods are GET, HEAD, POST, PUT, PATCH,
38
+ DELETE, OPTIONS and QUERY. Successful browser preflights return HTTP 204 by default;
39
+ `optionsSuccessStatus` can change this for a legacy client that requires 200.
40
+
41
+ Rate-limit defaults are 100 requests per minute per client IP. Available settings
42
+ include `max`, `timeWindow`, `ban`, `allowList`, and `keyGenerator`.
43
+
44
+ ## Compression
45
+
46
+ Compression is enabled by the full preset only. The default threshold is 1024 bytes
47
+ and encodings are Brotli, gzip and deflate. Compression can increase CPU usage and
48
+ can amplify secrets reflected beside attacker-controlled data, so enable it for
49
+ known payloads rather than automatically for every API.
50
+
51
+ ## OpenAPI
52
+
53
+ | Option | Default | Description |
54
+ | --- | --- | --- |
55
+ | `enabled` | full preset only | Generate an OpenAPI document. |
56
+ | `json` | `true` | Serve the document. |
57
+ | `jsonPath` | `/openapi.json` | JSON endpoint. |
58
+ | `ui` | `false` | Serve Swagger UI. |
59
+ | `uiPath` | `/docs` | UI route prefix. |
60
+ | `bearerAuth` | `false` | Add a bearer security scheme. |
61
+
62
+ Set `openApi: false` to guarantee the documentation integration is never loaded.
63
+
64
+ ## Health and observability
65
+
66
+ The full preset serves `{ "status": "ok" }` at `/health`. This is a liveness probe,
67
+ not a dependency-readiness check. Supply a custom response or implement a Nest
68
+ controller for deeper readiness logic.
69
+
70
+ Fastify's Pino logger is enabled, while per-request logging is disabled to reduce
71
+ noise and overhead. Enable it with `observability.requestLogging`. The optional
72
+ `responseTimeHeader` emits a standards-compatible `Server-Timing` value.
@@ -0,0 +1,22 @@
1
+ # Migration from `@authuser/nest-fastify-kit`
2
+
3
+ ## Requirements
4
+
5
+ - Upgrade the service to Node 20.11 or newer.
6
+ - Upgrade Nest packages to version 12.
7
+ - Use ESM-compatible imports and include `.js` on relative imports in NodeNext output.
8
+
9
+ ## API changes
10
+
11
+ The application factory keeps its old `createHttpApp` name and also exports
12
+ `createNestApp`. Replace `docs` with `openApi`; replace `network` and security values
13
+ using the typed option reference.
14
+
15
+ The package no longer supplies an authentication guard with implied behavior.
16
+ `Public` and `Roles` remain metadata decorators and must be consumed by an explicit
17
+ application guard.
18
+
19
+ Swagger, validation packages and Nest/Fastify runtime packages are installed as
20
+ dependencies. Remove redundant direct declarations only after confirming the app
21
+ does not import those packages itself and that your package manager deduplicates a
22
+ single compatible Nest version.
@@ -0,0 +1,25 @@
1
+ # Performance guide
2
+
3
+ There is no universal requests-per-second number: controller work, payload shape,
4
+ logging, validation, database latency, TLS and hardware dominate realistic results.
5
+
6
+ ## Recommended sequence
7
+
8
+ 1. Start with `preset: 'secure'` and production-shaped traffic.
9
+ 2. Benchmark latency percentiles and CPU, not only average throughput.
10
+ 3. Disable features one at a time to locate actual cost.
11
+ 4. Add Fastify JSON response schemas on hot routes to gain compiled serialization.
12
+ 5. Run multiple Node processes or containers; keep one event loop per CPU allocation.
13
+ 6. Load-test through the same proxy and TLS path used in production.
14
+
15
+ Request logging is disabled by default. Compression is full-preset only because it
16
+ trades bandwidth for CPU. OpenAPI generation happens at startup, and its modules are
17
+ not imported when disabled.
18
+
19
+ ## Choosing a lower-level stack
20
+
21
+ If profiling shows Nest itself is the bottleneck, raw Fastify is the closest smaller
22
+ step and preserves its plugin/schema ecosystem. A specialized HTTP engine can achieve
23
+ higher synthetic throughput, but moving away from Nest changes adapters, middleware,
24
+ testing and operational conventions. Make that decision from an application benchmark,
25
+ not a hello-world ranking.
@@ -0,0 +1,31 @@
1
+ # Security model
2
+
3
+ The secure preset establishes a baseline, not a complete application security model.
4
+
5
+ ## Included controls
6
+
7
+ - Helmet security headers with Content Security Policy.
8
+ - Strict DTO property allowlisting.
9
+ - A one MiB default request-body limit.
10
+ - Rate limiting by client IP.
11
+ - Request IDs restricted to 128 safe characters before log propagation.
12
+ - Generic internal errors; unexpected exceptions are logged server-side only.
13
+ - CORS disabled until an allowlist is configured.
14
+
15
+ ## Application responsibilities
16
+
17
+ - Authenticate requests and authorize every sensitive operation.
18
+ - Validate business rules, uploaded files and non-HTTP input.
19
+ - Configure trusted proxies exactly; a wrong value allows IP spoofing.
20
+ - Store secrets outside source control and rotate them.
21
+ - Apply endpoint-specific rate limits for login and recovery flows.
22
+ - Use TLS at the load balancer or application edge.
23
+ - Keep Node and dependencies patched and review `npm audit` output.
24
+ - Avoid exposing Swagger UI and detailed readiness data publicly.
25
+
26
+ `Public()` and `Roles()` only attach metadata. They do nothing until an application
27
+ guard reads that metadata and enforces the intended policy.
28
+
29
+ ## Reporting
30
+
31
+ Follow the private disclosure instructions in [SECURITY.md](../SECURITY.md).
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@authuser/nest",
3
+ "version": "0.1.0",
4
+ "description": "Secure, high-performance NestJS foundation powered by Fastify",
5
+ "keywords": [
6
+ "nestjs",
7
+ "fastify",
8
+ "openapi",
9
+ "swagger",
10
+ "security",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Authuser",
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "docs",
28
+ "README.md",
29
+ "CHANGELOG.md",
30
+ "LICENSE"
31
+ ],
32
+ "sideEffects": false,
33
+ "engines": {
34
+ "node": ">=20.11"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "scripts": {
41
+ "clean": "rimraf dist coverage",
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "test": "vitest run",
45
+ "test:coverage": "vitest run --coverage",
46
+ "prepack": "npm run clean && npm run build",
47
+ "check": "npm run typecheck && npm test && npm run build && npm pack --dry-run"
48
+ },
49
+ "dependencies": {
50
+ "@fastify/compress": "^9.2.0",
51
+ "@fastify/cors": "^11.3.0",
52
+ "@fastify/helmet": "^13.1.1",
53
+ "@fastify/rate-limit": "^11.2.0",
54
+ "@fastify/swagger": "^9.8.1",
55
+ "@fastify/swagger-ui": "^6.1.1",
56
+ "@nestjs/common": "^12.0.1",
57
+ "@nestjs/core": "^12.0.1",
58
+ "@nestjs/platform-fastify": "^12.0.1",
59
+ "@nestjs/swagger": "^12.0.1",
60
+ "class-transformer": "^0.5.1",
61
+ "class-validator": "^0.15.1",
62
+ "fastify": "^5.12.1",
63
+ "pino": "^10.3.1",
64
+ "reflect-metadata": "^0.2.2",
65
+ "rxjs": "^7.8.2"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^24.0.0",
69
+ "@vitest/coverage-v8": "^4.0.0",
70
+ "rimraf": "^6.0.0",
71
+ "typescript": "^7.0.2",
72
+ "vitest": "^4.0.0"
73
+ },
74
+ "directories": {
75
+ "doc": "docs",
76
+ "test": "test"
77
+ }
78
+ }