create-forgeon 0.2.5 → 0.2.7

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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/modules/executor.mjs +4 -0
  3. package/src/modules/executor.test.mjs +214 -0
  4. package/src/modules/i18n.mjs +113 -0
  5. package/src/modules/rate-limit.mjs +346 -0
  6. package/src/modules/rbac.mjs +324 -0
  7. package/src/modules/registry.mjs +39 -3
  8. package/src/run-add-module.mjs +83 -6
  9. package/templates/base/README.md +2 -2
  10. package/templates/base/docs/AI/MODULE_SPEC.md +9 -4
  11. package/templates/module-fragments/rate-limit/00_title.md +1 -0
  12. package/templates/module-fragments/rate-limit/10_overview.md +6 -0
  13. package/templates/module-fragments/rate-limit/20_idea.md +11 -0
  14. package/templates/module-fragments/rate-limit/30_what_it_adds.md +10 -0
  15. package/templates/module-fragments/rate-limit/40_how_it_works.md +13 -0
  16. package/templates/module-fragments/rate-limit/50_how_to_use.md +21 -0
  17. package/templates/module-fragments/rate-limit/60_configuration.md +15 -0
  18. package/templates/module-fragments/rate-limit/70_operational_notes.md +10 -0
  19. package/templates/module-fragments/rate-limit/90_status_implemented.md +3 -0
  20. package/templates/module-fragments/rbac/00_title.md +1 -0
  21. package/templates/module-fragments/rbac/10_overview.md +6 -0
  22. package/templates/module-fragments/rbac/20_idea.md +9 -0
  23. package/templates/module-fragments/rbac/30_what_it_adds.md +11 -0
  24. package/templates/module-fragments/rbac/40_how_it_works.md +20 -0
  25. package/templates/module-fragments/rbac/50_how_to_use.md +19 -0
  26. package/templates/module-fragments/rbac/60_configuration.md +9 -0
  27. package/templates/module-fragments/rbac/70_operational_notes.md +10 -0
  28. package/templates/module-fragments/rbac/90_status_implemented.md +3 -0
  29. package/templates/module-presets/rate-limit/packages/rate-limit/package.json +22 -0
  30. package/templates/module-presets/rate-limit/packages/rate-limit/src/forgeon-rate-limit.module.ts +50 -0
  31. package/templates/module-presets/rate-limit/packages/rate-limit/src/index.ts +5 -0
  32. package/templates/module-presets/rate-limit/packages/rate-limit/src/rate-limit-config.loader.ts +25 -0
  33. package/templates/module-presets/rate-limit/packages/rate-limit/src/rate-limit-config.module.ts +8 -0
  34. package/templates/module-presets/rate-limit/packages/rate-limit/src/rate-limit-config.service.ts +35 -0
  35. package/templates/module-presets/rate-limit/packages/rate-limit/src/rate-limit-env.schema.ts +16 -0
  36. package/templates/module-presets/rate-limit/packages/rate-limit/tsconfig.json +9 -0
  37. package/templates/module-presets/rbac/packages/rbac/package.json +19 -0
  38. package/templates/module-presets/rbac/packages/rbac/src/forgeon-rbac.guard.ts +91 -0
  39. package/templates/module-presets/rbac/packages/rbac/src/forgeon-rbac.module.ts +8 -0
  40. package/templates/module-presets/rbac/packages/rbac/src/index.ts +6 -0
  41. package/templates/module-presets/rbac/packages/rbac/src/rbac.constants.ts +4 -0
  42. package/templates/module-presets/rbac/packages/rbac/src/rbac.decorators.ts +11 -0
  43. package/templates/module-presets/rbac/packages/rbac/src/rbac.helpers.ts +31 -0
  44. package/templates/module-presets/rbac/packages/rbac/src/rbac.types.ts +7 -0
  45. package/templates/module-presets/rbac/packages/rbac/tsconfig.json +9 -0
@@ -39,9 +39,45 @@ const MODULE_PRESETS = {
39
39
  description: 'JWT auth preset with contracts/api module split, guard+strategy, and DB-aware refresh token storage wiring.',
40
40
  docFragments: ['00_title', '10_overview', '20_scope', '90_status_implemented'],
41
41
  },
42
- queue: {
43
- id: 'queue',
44
- label: 'Queue Worker',
42
+ 'rate-limit': {
43
+ id: 'rate-limit',
44
+ label: 'Rate Limit',
45
+ category: 'auth-security',
46
+ implemented: true,
47
+ description: 'Request throttling preset with env-based limits, proxy-aware trust, and a runtime probe endpoint.',
48
+ docFragments: [
49
+ '00_title',
50
+ '10_overview',
51
+ '20_idea',
52
+ '30_what_it_adds',
53
+ '40_how_it_works',
54
+ '50_how_to_use',
55
+ '60_configuration',
56
+ '70_operational_notes',
57
+ '90_status_implemented',
58
+ ],
59
+ },
60
+ rbac: {
61
+ id: 'rbac',
62
+ label: 'RBAC / Permissions',
63
+ category: 'auth-security',
64
+ implemented: true,
65
+ description: 'Role and permission decorators with a Nest guard and a protected probe endpoint.',
66
+ docFragments: [
67
+ '00_title',
68
+ '10_overview',
69
+ '20_idea',
70
+ '30_what_it_adds',
71
+ '40_how_it_works',
72
+ '50_how_to_use',
73
+ '60_configuration',
74
+ '70_operational_notes',
75
+ '90_status_implemented',
76
+ ],
77
+ },
78
+ queue: {
79
+ id: 'queue',
80
+ label: 'Queue Worker',
45
81
  category: 'background-jobs',
46
82
  implemented: false,
47
83
  description: 'Queue processing preset (BullMQ-style app wiring).',
@@ -7,14 +7,81 @@ import { addModule } from './modules/executor.mjs';
7
7
  import { listModulePresets } from './modules/registry.mjs';
8
8
  import { printModuleAdded, runIntegrationFlow } from './integrations/flow.mjs';
9
9
  import { writeJson } from './utils/fs.mjs';
10
-
10
+
11
11
  function printModuleList() {
12
12
  const modules = listModulePresets();
13
13
  console.log('Available modules:');
14
14
  for (const moduleItem of modules) {
15
15
  const status = moduleItem.implemented ? 'implemented' : 'planned';
16
16
  console.log(`- ${moduleItem.id} (${status}) - ${moduleItem.description}`);
17
- }
17
+ }
18
+ }
19
+
20
+ function toSortedObject(value) {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
22
+ return {};
23
+ }
24
+
25
+ return Object.fromEntries(
26
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)),
27
+ );
28
+ }
29
+
30
+ function collectDependencyManifestState(targetRoot) {
31
+ const state = new Map();
32
+ if (!fs.existsSync(targetRoot)) {
33
+ return state;
34
+ }
35
+
36
+ const queue = [targetRoot];
37
+ const skipDirs = new Set(['node_modules', '.git', 'dist', 'build']);
38
+
39
+ while (queue.length > 0) {
40
+ const currentDir = queue.shift();
41
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
42
+
43
+ for (const entry of entries) {
44
+ if (entry.isDirectory()) {
45
+ if (!skipDirs.has(entry.name)) {
46
+ queue.push(path.join(currentDir, entry.name));
47
+ }
48
+ continue;
49
+ }
50
+
51
+ if (!entry.isFile() || entry.name !== 'package.json') {
52
+ continue;
53
+ }
54
+
55
+ const filePath = path.join(currentDir, entry.name);
56
+ const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
57
+ const snapshot = {
58
+ name: packageJson.name ?? null,
59
+ dependencies: toSortedObject(packageJson.dependencies),
60
+ devDependencies: toSortedObject(packageJson.devDependencies),
61
+ optionalDependencies: toSortedObject(packageJson.optionalDependencies),
62
+ peerDependencies: toSortedObject(packageJson.peerDependencies),
63
+ onlyBuiltDependencies: Array.isArray(packageJson.pnpm?.onlyBuiltDependencies)
64
+ ? [...packageJson.pnpm.onlyBuiltDependencies].sort()
65
+ : [],
66
+ };
67
+
68
+ state.set(path.relative(targetRoot, filePath), JSON.stringify(snapshot));
69
+ }
70
+ }
71
+
72
+ return state;
73
+ }
74
+
75
+ function getChangedDependencyManifestPaths(beforeState, afterState) {
76
+ const changed = [];
77
+
78
+ for (const [filePath, nextSnapshot] of afterState.entries()) {
79
+ if (beforeState.get(filePath) !== nextSnapshot) {
80
+ changed.push(filePath);
81
+ }
82
+ }
83
+
84
+ return changed.sort();
18
85
  }
19
86
 
20
87
  function ensureSyncTooling({ packageRoot, targetRoot }) {
@@ -64,10 +131,11 @@ export async function runAddModule(argv = process.argv.slice(2)) {
64
131
  throw new Error('Module id is required. Use `create-forgeon add --list` to see available modules.');
65
132
  }
66
133
 
67
- const srcDir = path.dirname(fileURLToPath(import.meta.url));
68
- const packageRoot = path.resolve(srcDir, '..');
69
- const targetRoot = path.resolve(process.cwd(), options.project);
70
-
134
+ const srcDir = path.dirname(fileURLToPath(import.meta.url));
135
+ const packageRoot = path.resolve(srcDir, '..');
136
+ const targetRoot = path.resolve(process.cwd(), options.project);
137
+ const dependencyManifestStateBefore = collectDependencyManifestState(targetRoot);
138
+
71
139
  const result = addModule({
72
140
  moduleId: options.moduleId,
73
141
  targetRoot,
@@ -80,4 +148,13 @@ export async function runAddModule(argv = process.argv.slice(2)) {
80
148
  packageRoot,
81
149
  relatedModuleId: result.preset.id,
82
150
  });
151
+
152
+ const dependencyManifestStateAfter = collectDependencyManifestState(targetRoot);
153
+ const changedDependencyManifestPaths = getChangedDependencyManifestPaths(
154
+ dependencyManifestStateBefore,
155
+ dependencyManifestStateAfter,
156
+ );
157
+ if (changedDependencyManifestPaths.length > 0) {
158
+ console.log('Next: run pnpm install');
159
+ }
83
160
  }
@@ -37,9 +37,9 @@ pnpm forgeon:sync-integrations
37
37
  ```
38
38
 
39
39
  Current sync coverage:
40
- - `jwt-auth + swagger`: adds OpenAPI decorators for auth controller/DTOs.
40
+ - `jwt-auth + db-prisma`: wires persistent refresh-token storage for auth.
41
41
 
42
- `create-forgeon add <module>` also runs integration sync automatically (best effort).
42
+ `create-forgeon add <module>` scans for relevant integration groups and can apply them immediately.
43
43
 
44
44
  ## i18n Configuration
45
45
 
@@ -4,11 +4,15 @@
4
4
 
5
5
  Define one repeatable fullstack pattern for Forgeon add-modules.
6
6
 
7
- Each feature module should be split into:
7
+ Most feature modules should be split into:
8
8
 
9
- 1. `@forgeon/<feature>-contracts`
10
- 2. `@forgeon/<feature>-api`
11
- 3. `@forgeon/<feature>-web`
9
+ 1. `@forgeon/<feature>-contracts`
10
+ 2. `@forgeon/<feature>-api`
11
+ 3. `@forgeon/<feature>-web`
12
+
13
+ Exception:
14
+
15
+ - backend-only infrastructure or security modules may use a single runtime package (`@forgeon/<feature>`) when shared contracts and a dedicated web package add no real value.
12
16
 
13
17
  ## 1) Contracts Package
14
18
 
@@ -63,6 +67,7 @@ Must contain:
63
67
  - Contracts package can be imported from both sides without circular dependencies.
64
68
  - Contracts package exports are stable from `dist/index` entrypoint.
65
69
  - Module has docs under `docs/AI/MODULES/<module-id>.md`.
70
+ - Module docs must explain: why it exists, what it adds, how it works, how to use it, how to configure it, and current operational limits.
66
71
  - If module behavior can be runtime-checked, it also includes API+Web probe hooks (see `docs/AI/MODULE_CHECKS.md`).
67
72
  - If i18n is enabled, module-specific namespaces must be created and wired for both API and web.
68
73
  - If module is added before i18n, namespace templates must still be prepared and applied when i18n is installed later.
@@ -0,0 +1 @@
1
+ # {{MODULE_LABEL}}
@@ -0,0 +1,6 @@
1
+ ## Overview
2
+
3
+ - Id: `{{MODULE_ID}}`
4
+ - Category: `{{MODULE_CATEGORY}}`
5
+ - Status: {{MODULE_STATUS}}
6
+ - Description: {{MODULE_DESCRIPTION}}
@@ -0,0 +1,11 @@
1
+ ## Idea / Why
2
+
3
+ This module adds a simple first-line request throttle to the API.
4
+
5
+ It exists to reduce three common classes of problems:
6
+
7
+ 1. burst traffic from accidental frontend loops
8
+ 2. low-cost abuse against public endpoints
9
+ 3. brute-force style retries against auth endpoints
10
+
11
+ It is intentionally small and predictable. The goal is not to replace a WAF, CDN, or distributed rate limiter. The goal is to give every Forgeon project a safe baseline that can be installed in one step.
@@ -0,0 +1,10 @@
1
+ ## What It Adds
2
+
3
+ - `@forgeon/rate-limit` workspace package
4
+ - env-backed throttle configuration
5
+ - global Nest throttler guard wiring
6
+ - reverse-proxy trust toggle for Caddy / Nginx deployments
7
+ - `GET /api/health/rate-limit` probe endpoint
8
+ - frontend probe button on the generated home page
9
+
10
+ This module is API-first. It does not add shared contracts or a web package in v1 because the runtime value is in backend request throttling, not in reusable client-side types.
@@ -0,0 +1,13 @@
1
+ ## How It Works
2
+
3
+ Implementation details:
4
+
5
+ - `RateLimitConfigService` reads `THROTTLE_*` env values through `@nestjs/config`.
6
+ - `ForgeonRateLimitModule` registers `ThrottlerModule` globally.
7
+ - A global guard applies the throttle rules to incoming HTTP requests.
8
+ - When `THROTTLE_TRUST_PROXY=true`, the module enables Express `trust proxy` through the active HTTP adapter so client IPs are resolved correctly behind reverse proxies.
9
+
10
+ Error behavior:
11
+
12
+ - throttled requests return HTTP `429`
13
+ - the existing Forgeon error envelope wraps the response as `TOO_MANY_REQUESTS`
@@ -0,0 +1,21 @@
1
+ ## How To Use
2
+
3
+ Install:
4
+
5
+ ```bash
6
+ npx create-forgeon@latest add rate-limit
7
+ pnpm install
8
+ ```
9
+
10
+ Verify:
11
+
12
+ 1. start the project
13
+ 2. open the generated frontend
14
+ 3. click `Check rate limit (click repeatedly)` multiple times within the throttle window
15
+ 4. observe the transition from `200` to `429`
16
+
17
+ You can also hit the probe route directly:
18
+
19
+ ```bash
20
+ GET /api/health/rate-limit
21
+ ```
@@ -0,0 +1,15 @@
1
+ ## Configuration
2
+
3
+ Environment keys:
4
+
5
+ - `THROTTLE_ENABLED=true`
6
+ - `THROTTLE_TTL=10`
7
+ - `THROTTLE_LIMIT=3`
8
+ - `THROTTLE_TRUST_PROXY=false`
9
+
10
+ Meaning:
11
+
12
+ - `THROTTLE_ENABLED`: hard on/off switch
13
+ - `THROTTLE_TTL`: throttle window in seconds
14
+ - `THROTTLE_LIMIT`: maximum requests allowed inside that window
15
+ - `THROTTLE_TRUST_PROXY`: use forwarded client IPs when behind Caddy / Nginx
@@ -0,0 +1,10 @@
1
+ ## Operational Notes
2
+
3
+ Current scope:
4
+
5
+ - in-memory throttling only
6
+ - one global baseline policy
7
+ - no Redis / distributed storage
8
+ - no route-specific policy DSL in v1
9
+
10
+ That means this module is appropriate for local development, simple deployments, and as a base preset. If a project later needs distributed throttling or different policies per route or user, this module should be extended rather than replaced blindly.
@@ -0,0 +1,3 @@
1
+ ## Status
2
+
3
+ Implemented in the current scaffold.
@@ -0,0 +1 @@
1
+ # {{MODULE_LABEL}}
@@ -0,0 +1,6 @@
1
+ ## Overview
2
+
3
+ - Id: `{{MODULE_ID}}`
4
+ - Category: `{{MODULE_CATEGORY}}`
5
+ - Status: {{MODULE_STATUS}}
6
+ - Description: {{MODULE_DESCRIPTION}}
@@ -0,0 +1,9 @@
1
+ ## Idea / Why
2
+
3
+ This module adds a minimal authorization layer for backend routes.
4
+
5
+ It exists to answer one simple question consistently:
6
+
7
+ - does the current caller have the required role or permission for this endpoint?
8
+
9
+ The module intentionally stays small. It does not try to become a general policy engine. It provides a stable baseline for backend access checks and leaves more advanced patterns to separate modules if they are ever needed.
@@ -0,0 +1,11 @@
1
+ ## What It Adds
2
+
3
+ - `@forgeon/rbac` runtime package
4
+ - `@Roles(...)` decorator
5
+ - `@Permissions(...)` decorator
6
+ - `ForgeonRbacGuard`
7
+ - helper utilities for role and permission checks
8
+ - a protected probe route: `GET /api/health/rbac`
9
+ - a frontend probe button that sends a valid permission header
10
+
11
+ This module is backend-first. It does not include frontend route guards. If frontend access-control helpers are needed later, they should live in a separate module.
@@ -0,0 +1,20 @@
1
+ ## How It Works
2
+
3
+ Implementation details:
4
+
5
+ - decorators store required roles and permissions as Nest metadata
6
+ - `ForgeonRbacGuard` reads that metadata with `Reflector`
7
+ - the guard checks `request.user` first
8
+ - if no user payload is available, it can also read test headers:
9
+ - `x-forgeon-roles`
10
+ - `x-forgeon-permissions`
11
+
12
+ Decision rules in v1:
13
+
14
+ - roles: any required role is enough
15
+ - permissions: all required permissions must be present
16
+
17
+ Failure path:
18
+
19
+ - denied access throws `403`
20
+ - the existing Forgeon error envelope wraps it as `FORBIDDEN`
@@ -0,0 +1,19 @@
1
+ ## How To Use
2
+
3
+ Install:
4
+
5
+ ```bash
6
+ npx create-forgeon@latest add rbac
7
+ pnpm install
8
+ ```
9
+
10
+ Verify:
11
+
12
+ 1. start the project
13
+ 2. click `Check RBAC access` on the generated frontend
14
+ 3. the request should return `200`
15
+
16
+ Manual forbidden-path check:
17
+
18
+ 1. call `GET /api/health/rbac` without the `x-forgeon-permissions` header
19
+ 2. the request should return `403`
@@ -0,0 +1,9 @@
1
+ ## Configuration
2
+
3
+ This module has no dedicated environment variables in v1.
4
+
5
+ Behavior is controlled by:
6
+
7
+ - route decorators (`@Roles`, `@Permissions`)
8
+ - the active request payload (`request.user`)
9
+ - optional testing headers (`x-forgeon-roles`, `x-forgeon-permissions`)
@@ -0,0 +1,10 @@
1
+ ## Operational Notes
2
+
3
+ Current scope:
4
+
5
+ - no policy engine
6
+ - no database-backed role or permission store
7
+ - no admin UI
8
+ - no frontend route-guard package in this module
9
+
10
+ This is a stable baseline module, not a placeholder for a future “v2”. If access-control needs grow later, this module can be extended carefully or paired with a separate specialized module.
@@ -0,0 +1,3 @@
1
+ ## Status
2
+
3
+ Implemented in the current scaffold.
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@forgeon/rate-limit",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json"
9
+ },
10
+ "dependencies": {
11
+ "@nestjs/common": "^11.0.1",
12
+ "@nestjs/config": "^4.0.2",
13
+ "@nestjs/core": "^11.0.1",
14
+ "@nestjs/throttler": "^6.4.0",
15
+ "rxjs": "^7.8.1",
16
+ "zod": "^3.23.8"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.10.7",
20
+ "typescript": "^5.7.3"
21
+ }
22
+ }
@@ -0,0 +1,50 @@
1
+ import { Module, OnModuleInit } from '@nestjs/common';
2
+ import { APP_GUARD, HttpAdapterHost } from '@nestjs/core';
3
+ import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
4
+ import { RateLimitConfigModule } from './rate-limit-config.module';
5
+ import { RateLimitConfigService } from './rate-limit-config.service';
6
+
7
+ @Module({
8
+ imports: [
9
+ RateLimitConfigModule,
10
+ ThrottlerModule.forRootAsync({
11
+ imports: [RateLimitConfigModule],
12
+ inject: [RateLimitConfigService],
13
+ useFactory: (config: RateLimitConfigService) => ({
14
+ errorMessage: 'Too many requests. Please try again later.',
15
+ skipIf: () => !config.enabled,
16
+ throttlers: [
17
+ {
18
+ ttl: config.ttlMs,
19
+ limit: config.limit,
20
+ },
21
+ ],
22
+ }),
23
+ }),
24
+ ],
25
+ providers: [
26
+ {
27
+ provide: APP_GUARD,
28
+ useClass: ThrottlerGuard,
29
+ },
30
+ ],
31
+ exports: [RateLimitConfigModule],
32
+ })
33
+ export class ForgeonRateLimitModule implements OnModuleInit {
34
+ constructor(
35
+ private readonly rateLimitConfig: RateLimitConfigService,
36
+ private readonly httpAdapterHost: HttpAdapterHost,
37
+ ) {}
38
+
39
+ onModuleInit(): void {
40
+ if (!this.rateLimitConfig.trustProxy) {
41
+ return;
42
+ }
43
+
44
+ const adapter = this.httpAdapterHost.httpAdapter;
45
+ const instance = adapter?.getInstance?.();
46
+ if (instance && typeof instance.set === 'function') {
47
+ instance.set('trust proxy', true);
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,5 @@
1
+ export * from './forgeon-rate-limit.module';
2
+ export * from './rate-limit-config.loader';
3
+ export * from './rate-limit-config.module';
4
+ export * from './rate-limit-config.service';
5
+ export * from './rate-limit-env.schema';
@@ -0,0 +1,25 @@
1
+ import { registerAs } from '@nestjs/config';
2
+ import { parseRateLimitEnv } from './rate-limit-env.schema';
3
+
4
+ export const RATE_LIMIT_CONFIG_NAMESPACE = 'rateLimit';
5
+
6
+ export interface RateLimitConfigValues {
7
+ enabled: boolean;
8
+ ttlSeconds: number;
9
+ limit: number;
10
+ trustProxy: boolean;
11
+ }
12
+
13
+ export const rateLimitConfig = registerAs(
14
+ RATE_LIMIT_CONFIG_NAMESPACE,
15
+ (): RateLimitConfigValues => {
16
+ const env = parseRateLimitEnv(process.env);
17
+
18
+ return {
19
+ enabled: env.THROTTLE_ENABLED,
20
+ ttlSeconds: env.THROTTLE_TTL,
21
+ limit: env.THROTTLE_LIMIT,
22
+ trustProxy: env.THROTTLE_TRUST_PROXY,
23
+ };
24
+ },
25
+ );
@@ -0,0 +1,8 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { RateLimitConfigService } from './rate-limit-config.service';
3
+
4
+ @Module({
5
+ providers: [RateLimitConfigService],
6
+ exports: [RateLimitConfigService],
7
+ })
8
+ export class RateLimitConfigModule {}
@@ -0,0 +1,35 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import {
4
+ RATE_LIMIT_CONFIG_NAMESPACE,
5
+ RateLimitConfigValues,
6
+ } from './rate-limit-config.loader';
7
+
8
+ @Injectable()
9
+ export class RateLimitConfigService {
10
+ constructor(private readonly configService: ConfigService) {}
11
+
12
+ get enabled(): boolean {
13
+ return this.configService.getOrThrow<boolean>(`${RATE_LIMIT_CONFIG_NAMESPACE}.enabled`);
14
+ }
15
+
16
+ get ttlSeconds(): RateLimitConfigValues['ttlSeconds'] {
17
+ return this.configService.getOrThrow<RateLimitConfigValues['ttlSeconds']>(
18
+ `${RATE_LIMIT_CONFIG_NAMESPACE}.ttlSeconds`,
19
+ );
20
+ }
21
+
22
+ get ttlMs(): number {
23
+ return this.ttlSeconds * 1000;
24
+ }
25
+
26
+ get limit(): RateLimitConfigValues['limit'] {
27
+ return this.configService.getOrThrow<RateLimitConfigValues['limit']>(
28
+ `${RATE_LIMIT_CONFIG_NAMESPACE}.limit`,
29
+ );
30
+ }
31
+
32
+ get trustProxy(): boolean {
33
+ return this.configService.getOrThrow<boolean>(`${RATE_LIMIT_CONFIG_NAMESPACE}.trustProxy`);
34
+ }
35
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+
3
+ export const rateLimitEnvSchema = z
4
+ .object({
5
+ THROTTLE_ENABLED: z.coerce.boolean().default(true),
6
+ THROTTLE_TTL: z.coerce.number().int().positive().default(10),
7
+ THROTTLE_LIMIT: z.coerce.number().int().positive().default(3),
8
+ THROTTLE_TRUST_PROXY: z.coerce.boolean().default(false),
9
+ })
10
+ .passthrough();
11
+
12
+ export type RateLimitEnv = z.infer<typeof rateLimitEnvSchema>;
13
+
14
+ export function parseRateLimitEnv(input: Record<string, unknown>): RateLimitEnv {
15
+ return rateLimitEnvSchema.parse(input);
16
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.node.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*.ts"]
9
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@forgeon/rbac",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json"
9
+ },
10
+ "dependencies": {
11
+ "@nestjs/common": "^11.0.1",
12
+ "@nestjs/core": "^11.0.1",
13
+ "reflect-metadata": "^0.2.2"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^22.10.7",
17
+ "typescript": "^5.7.3"
18
+ }
19
+ }