@flusys/nestjs-core 7.0.2 → 8.0.0-beta.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.
Files changed (70) hide show
  1. package/README.md +2 -2
  2. package/config/index.d.ts +1 -1
  3. package/constants/index.d.ts +1 -1
  4. package/docs/docs.config.d.ts +1 -1
  5. package/docs/index.d.ts +1 -1
  6. package/fesm/229.js +729 -0
  7. package/fesm/config/index.js +126 -1
  8. package/fesm/constants/index.js +29 -1
  9. package/fesm/docs/index.js +288 -1
  10. package/fesm/index.js +124 -5
  11. package/fesm/interfaces/index.js +18 -4
  12. package/fesm/migration/cli.js +19 -2
  13. package/fesm/migration/index.js +45 -3
  14. package/fesm/runtime.js +85 -0
  15. package/fesm/utils/index.js +131 -1
  16. package/index.d.ts +5 -5
  17. package/interfaces/app-config.interfaces.d.ts +2 -1
  18. package/interfaces/database.interface.d.ts +1 -0
  19. package/interfaces/index.d.ts +4 -4
  20. package/interfaces/migration.interface.d.ts +2 -2
  21. package/migration/cli.d.ts +0 -1
  22. package/migration/datasource.factory.d.ts +1 -1
  23. package/migration/index.d.ts +3 -3
  24. package/migration/migration.cli.d.ts +1 -1
  25. package/migration/migration.runner.d.ts +1 -1
  26. package/package.json +25 -33
  27. package/utils/datasource-config.builder.d.ts +2 -2
  28. package/utils/index.d.ts +1 -1
  29. package/cjs/config/env-config.service.js +0 -162
  30. package/cjs/config/env-config.service.spec.js +0 -325
  31. package/cjs/config/index.js +0 -18
  32. package/cjs/constants/database.constants.js +0 -20
  33. package/cjs/constants/index.js +0 -18
  34. package/cjs/docs/docs.config.js +0 -276
  35. package/cjs/docs/docs.config.spec.js +0 -923
  36. package/cjs/docs/index.js +0 -18
  37. package/cjs/index.js +0 -23
  38. package/cjs/interfaces/app-config.interfaces.js +0 -4
  39. package/cjs/interfaces/base-entity.interface.js +0 -9
  40. package/cjs/interfaces/database.interface.js +0 -4
  41. package/cjs/interfaces/index.js +0 -21
  42. package/cjs/interfaces/migration.interface.js +0 -4
  43. package/cjs/migration/cli.js +0 -10
  44. package/cjs/migration/datasource.factory.js +0 -43
  45. package/cjs/migration/datasource.factory.spec.js +0 -113
  46. package/cjs/migration/index.js +0 -45
  47. package/cjs/migration/migration.cli.js +0 -231
  48. package/cjs/migration/migration.cli.spec.js +0 -293
  49. package/cjs/migration/migration.runner.js +0 -276
  50. package/cjs/migration/migration.runner.spec.js +0 -268
  51. package/cjs/utils/datasource-config.builder.js +0 -103
  52. package/cjs/utils/datasource-config.builder.spec.js +0 -232
  53. package/cjs/utils/index.js +0 -18
  54. package/fesm/config/env-config.service.js +0 -112
  55. package/fesm/config/env-config.service.spec.js +0 -321
  56. package/fesm/constants/database.constants.js +0 -2
  57. package/fesm/docs/docs.config.js +0 -258
  58. package/fesm/docs/docs.config.spec.js +0 -919
  59. package/fesm/interfaces/app-config.interfaces.js +0 -1
  60. package/fesm/interfaces/base-entity.interface.js +0 -6
  61. package/fesm/interfaces/database.interface.js +0 -1
  62. package/fesm/interfaces/migration.interface.js +0 -1
  63. package/fesm/migration/datasource.factory.js +0 -25
  64. package/fesm/migration/datasource.factory.spec.js +0 -109
  65. package/fesm/migration/migration.cli.js +0 -180
  66. package/fesm/migration/migration.cli.spec.js +0 -289
  67. package/fesm/migration/migration.runner.js +0 -202
  68. package/fesm/migration/migration.runner.spec.js +0 -264
  69. package/fesm/utils/datasource-config.builder.js +0 -84
  70. package/fesm/utils/datasource-config.builder.spec.js +0 -228
@@ -1,112 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- import * as dotenv from 'dotenv';
15
- dotenv.config();
16
- let EnvConfigService = class EnvConfigService {
17
- getValue(key, throwOnMissing = true) {
18
- const value = this.env[key];
19
- if (!value && throwOnMissing) {
20
- throw new Error(`Config error - missing env.${key}`);
21
- }
22
- return value ?? '';
23
- }
24
- tryGetValue(key, throwOnMissing = false) {
25
- if (this.restrictedKeys[key]) {
26
- throw new Error(`Access denied for env key "${key}". Use the method \`${this.restrictedKeys[key]}()\` instead.`);
27
- }
28
- return this.getValue(key, throwOnMissing);
29
- }
30
- getNumber(key, throwOnMissing = true) {
31
- return Number(this.getValue(key, throwOnMissing));
32
- }
33
- getBoolean(key, throwOnMissing = true) {
34
- return this.getValue(key, throwOnMissing).toLowerCase() === 'true';
35
- }
36
- getPort() {
37
- return this.getNumber('PORT', false) || 3000;
38
- }
39
- isProduction() {
40
- return this.getValue('MODE', false).toUpperCase() !== 'DEV';
41
- }
42
- getOrigins() {
43
- const origins = this.getValue('ALLOW_ORIGINS');
44
- return origins ? origins.split(',').map((o)=>o.trim()) : [];
45
- }
46
- getTypeOrmConfig() {
47
- const type = this.getValue('DB_TYPE', false) || 'mysql';
48
- const defaultPort = type === 'postgres' ? 5432 : 3306;
49
- return {
50
- type,
51
- host: this.getValue('DB_HOST', false) || 'localhost',
52
- port: this.getNumber('DB_PORT', false) || defaultPort,
53
- username: this.getValue('DB_USER', false) || 'root',
54
- password: this.getValue('DB_PASSWORD', false) || '',
55
- database: this.getValue('DB_NAME', false) || 'flusys_main'
56
- };
57
- }
58
- getJwtConfig() {
59
- return {
60
- secret: this.getValue('JWT_SECRET'),
61
- expiration: this.getValue('JWT_EXPIRATION', false) || '1h',
62
- refreshSecret: this.getValue('REFRESH_TOKEN_SECRET'),
63
- refreshExpiration: this.getValue('REFRESH_TOKEN_EXPIRATION', false) || '7d'
64
- };
65
- }
66
- getRedisUrl() {
67
- return this.getValue('REDIS_URL', false) || 'redis://localhost:6379';
68
- }
69
- getMailConfig() {
70
- return {
71
- MAIL_FROM: this.getValue('MAIL_FROM', false) || '',
72
- MAIL_APP_PASSWORD: this.getValue('MAIL_APP_PASSWORD', false) || ''
73
- };
74
- }
75
- getTenantId() {
76
- return this.getValue('TENANT_ID', false) || null;
77
- }
78
- useTenantMode() {
79
- return this.getValue('USE_TENANT_MODE', false).toLowerCase() === 'true';
80
- }
81
- getLogConfig() {
82
- return {
83
- dir: this.getValue('LOG_DIR', false) || 'logs',
84
- level: this.getValue('LOG_LEVEL', false) || (this.isProduction() ? 'info' : 'debug'),
85
- maxSize: this.getValue('LOG_MAX_SIZE', false) || '20m',
86
- maxFiles: this.getValue('LOG_MAX_FILES', false) || '14d',
87
- disableHttpLogging: this.getValue('DISABLE_HTTP_LOGGING', false).toLowerCase() === 'true'
88
- };
89
- }
90
- getEnv() {
91
- return process.env;
92
- }
93
- constructor(env){
94
- _define_property(this, "env", void 0);
95
- _define_property(this, "restrictedKeys", void 0);
96
- this.env = env;
97
- this.restrictedKeys = {
98
- FRONTEND_URL: 'getFrontendUrl',
99
- ALLOW_ORIGINS: 'getOrigins',
100
- PORT: 'getPort',
101
- MODE: 'isProduction',
102
- DB_TYPE: 'getTypeOrmConfig',
103
- DB_HOST: 'getTypeOrmConfig',
104
- DB_PORT: 'getTypeOrmConfig',
105
- DB_USER: 'getTypeOrmConfig',
106
- DB_PASSWORD: 'getTypeOrmConfig',
107
- DB_NAME: 'getTypeOrmConfig'
108
- };
109
- }
110
- };
111
- const envConfig = new EnvConfigService(process.env);
112
- export { envConfig };
@@ -1,321 +0,0 @@
1
- import { envConfig } from './env-config.service';
2
- describe('EnvConfigService', ()=>{
3
- const ORIGINAL_ENV = {
4
- ...process.env
5
- };
6
- afterEach(()=>{
7
- for (const key of Object.keys(process.env)){
8
- if (!(key in ORIGINAL_ENV)) delete process.env[key];
9
- }
10
- Object.assign(process.env, ORIGINAL_ENV);
11
- });
12
- describe('tryGetValue', ()=>{
13
- it('should return env value for an unrestricted key', ()=>{
14
- process.env.CUSTOM_KEY = 'custom-value';
15
- expect(envConfig.tryGetValue('CUSTOM_KEY')).toBe('custom-value');
16
- });
17
- it('should return empty string when missing and throwOnMissing is false', ()=>{
18
- delete process.env.MISSING_KEY;
19
- expect(envConfig.tryGetValue('MISSING_KEY', false)).toBe('');
20
- });
21
- it('should throw when missing and throwOnMissing is true', ()=>{
22
- delete process.env.MISSING_KEY;
23
- expect(()=>envConfig.tryGetValue('MISSING_KEY', true)).toThrow('Config error - missing env.MISSING_KEY');
24
- });
25
- it.each([
26
- [
27
- 'FRONTEND_URL',
28
- 'getFrontendUrl'
29
- ],
30
- [
31
- 'ALLOW_ORIGINS',
32
- 'getOrigins'
33
- ],
34
- [
35
- 'PORT',
36
- 'getPort'
37
- ],
38
- [
39
- 'MODE',
40
- 'isProduction'
41
- ],
42
- [
43
- 'DB_TYPE',
44
- 'getTypeOrmConfig'
45
- ],
46
- [
47
- 'DB_HOST',
48
- 'getTypeOrmConfig'
49
- ],
50
- [
51
- 'DB_PORT',
52
- 'getTypeOrmConfig'
53
- ],
54
- [
55
- 'DB_USER',
56
- 'getTypeOrmConfig'
57
- ],
58
- [
59
- 'DB_PASSWORD',
60
- 'getTypeOrmConfig'
61
- ],
62
- [
63
- 'DB_NAME',
64
- 'getTypeOrmConfig'
65
- ]
66
- ])('should deny direct access to restricted key %s and point to %s()', (key, method)=>{
67
- expect(()=>envConfig.tryGetValue(key)).toThrow(`Access denied for env key "${key}". Use the method \`${method}()\` instead.`);
68
- });
69
- });
70
- describe('getNumber', ()=>{
71
- it('should coerce a numeric env value', ()=>{
72
- process.env.SOME_NUMBER = '42';
73
- expect(envConfig.getNumber('SOME_NUMBER')).toBe(42);
74
- });
75
- it('should return NaN for a non-numeric value', ()=>{
76
- process.env.SOME_NUMBER = 'not-a-number';
77
- expect(envConfig.getNumber('SOME_NUMBER')).toBeNaN();
78
- });
79
- });
80
- describe('getBoolean', ()=>{
81
- it.each([
82
- [
83
- 'true',
84
- true
85
- ],
86
- [
87
- 'TRUE',
88
- true
89
- ],
90
- [
91
- 'True',
92
- true
93
- ],
94
- [
95
- 'false',
96
- false
97
- ],
98
- [
99
- 'yes',
100
- false
101
- ],
102
- [
103
- '',
104
- false
105
- ]
106
- ])('should parse %s as %s', (value, expected)=>{
107
- process.env.SOME_BOOL = value;
108
- expect(envConfig.getBoolean('SOME_BOOL', false)).toBe(expected);
109
- });
110
- });
111
- describe('getPort', ()=>{
112
- it('should default to 3000 when PORT is unset', ()=>{
113
- delete process.env.PORT;
114
- expect(envConfig.getPort()).toBe(3000);
115
- });
116
- it('should return the configured PORT', ()=>{
117
- process.env.PORT = '2002';
118
- expect(envConfig.getPort()).toBe(2002);
119
- });
120
- });
121
- describe('isProduction', ()=>{
122
- it('should be true when MODE is unset', ()=>{
123
- delete process.env.MODE;
124
- expect(envConfig.isProduction()).toBe(true);
125
- });
126
- it('should be false when MODE is "dev" (case-insensitive)', ()=>{
127
- process.env.MODE = 'dev';
128
- expect(envConfig.isProduction()).toBe(false);
129
- process.env.MODE = 'DEV';
130
- expect(envConfig.isProduction()).toBe(false);
131
- });
132
- it('should be true for any non-DEV mode', ()=>{
133
- process.env.MODE = 'production';
134
- expect(envConfig.isProduction()).toBe(true);
135
- });
136
- });
137
- describe('getOrigins', ()=>{
138
- it('should split and trim a comma-separated origin list', ()=>{
139
- process.env.ALLOW_ORIGINS = 'http://localhost:2001, http://localhost:3000 ,http://example.com';
140
- expect(envConfig.getOrigins()).toEqual([
141
- 'http://localhost:2001',
142
- 'http://localhost:3000',
143
- 'http://example.com'
144
- ]);
145
- });
146
- it('should throw when unset since getOrigins requires the value', ()=>{
147
- delete process.env.ALLOW_ORIGINS;
148
- expect(()=>envConfig.getOrigins()).toThrow('Config error - missing env.ALLOW_ORIGINS');
149
- });
150
- });
151
- describe('getTypeOrmConfig', ()=>{
152
- it('should apply mysql defaults when DB_TYPE is unset', ()=>{
153
- delete process.env.DB_TYPE;
154
- delete process.env.DB_HOST;
155
- delete process.env.DB_PORT;
156
- delete process.env.DB_USER;
157
- delete process.env.DB_PASSWORD;
158
- delete process.env.DB_NAME;
159
- expect(envConfig.getTypeOrmConfig()).toEqual({
160
- type: 'mysql',
161
- host: 'localhost',
162
- port: 3306,
163
- username: 'root',
164
- password: '',
165
- database: 'flusys_main'
166
- });
167
- });
168
- it('should default the postgres port to 5432', ()=>{
169
- process.env.DB_TYPE = 'postgres';
170
- delete process.env.DB_PORT;
171
- expect(envConfig.getTypeOrmConfig().port).toBe(5432);
172
- });
173
- it('should use explicit connection values when provided', ()=>{
174
- process.env.DB_TYPE = 'postgres';
175
- process.env.DB_HOST = 'db.internal';
176
- process.env.DB_PORT = '5555';
177
- process.env.DB_USER = 'flusys';
178
- process.env.DB_PASSWORD = 'secret';
179
- process.env.DB_NAME = 'flusys_prod';
180
- expect(envConfig.getTypeOrmConfig()).toEqual({
181
- type: 'postgres',
182
- host: 'db.internal',
183
- port: 5555,
184
- username: 'flusys',
185
- password: 'secret',
186
- database: 'flusys_prod'
187
- });
188
- });
189
- });
190
- describe('getJwtConfig', ()=>{
191
- it('should throw when JWT_SECRET is missing', ()=>{
192
- delete process.env.JWT_SECRET;
193
- process.env.REFRESH_TOKEN_SECRET = 'refresh';
194
- expect(()=>envConfig.getJwtConfig()).toThrow('Config error - missing env.JWT_SECRET');
195
- });
196
- it('should throw when REFRESH_TOKEN_SECRET is missing', ()=>{
197
- process.env.JWT_SECRET = 'secret';
198
- delete process.env.REFRESH_TOKEN_SECRET;
199
- expect(()=>envConfig.getJwtConfig()).toThrow('Config error - missing env.REFRESH_TOKEN_SECRET');
200
- });
201
- it('should apply default expirations when unset', ()=>{
202
- process.env.JWT_SECRET = 'secret';
203
- process.env.REFRESH_TOKEN_SECRET = 'refresh-secret';
204
- delete process.env.JWT_EXPIRATION;
205
- delete process.env.REFRESH_TOKEN_EXPIRATION;
206
- expect(envConfig.getJwtConfig()).toEqual({
207
- secret: 'secret',
208
- expiration: '1h',
209
- refreshSecret: 'refresh-secret',
210
- refreshExpiration: '7d'
211
- });
212
- });
213
- it('should use explicit expirations when set', ()=>{
214
- process.env.JWT_SECRET = 'secret';
215
- process.env.JWT_EXPIRATION = '15m';
216
- process.env.REFRESH_TOKEN_SECRET = 'refresh-secret';
217
- process.env.REFRESH_TOKEN_EXPIRATION = '30d';
218
- expect(envConfig.getJwtConfig()).toEqual({
219
- secret: 'secret',
220
- expiration: '15m',
221
- refreshSecret: 'refresh-secret',
222
- refreshExpiration: '30d'
223
- });
224
- });
225
- });
226
- describe('getRedisUrl', ()=>{
227
- it('should default to localhost redis', ()=>{
228
- delete process.env.REDIS_URL;
229
- expect(envConfig.getRedisUrl()).toBe('redis://localhost:6379');
230
- });
231
- it('should return the configured redis url', ()=>{
232
- process.env.REDIS_URL = 'redis://redis-host:6380';
233
- expect(envConfig.getRedisUrl()).toBe('redis://redis-host:6380');
234
- });
235
- });
236
- describe('getMailConfig', ()=>{
237
- it('should default to empty strings when unset', ()=>{
238
- delete process.env.MAIL_FROM;
239
- delete process.env.MAIL_APP_PASSWORD;
240
- expect(envConfig.getMailConfig()).toEqual({
241
- MAIL_FROM: '',
242
- MAIL_APP_PASSWORD: ''
243
- });
244
- });
245
- it('should return configured mail settings', ()=>{
246
- process.env.MAIL_FROM = 'noreply@flusys.io';
247
- process.env.MAIL_APP_PASSWORD = 'app-pass';
248
- expect(envConfig.getMailConfig()).toEqual({
249
- MAIL_FROM: 'noreply@flusys.io',
250
- MAIL_APP_PASSWORD: 'app-pass'
251
- });
252
- });
253
- });
254
- describe('getTenantId', ()=>{
255
- it('should return null when unset', ()=>{
256
- delete process.env.TENANT_ID;
257
- expect(envConfig.getTenantId()).toBeNull();
258
- });
259
- it('should return the configured tenant id', ()=>{
260
- process.env.TENANT_ID = 'tenant-a';
261
- expect(envConfig.getTenantId()).toBe('tenant-a');
262
- });
263
- });
264
- describe('useTenantMode', ()=>{
265
- it('should be false by default', ()=>{
266
- delete process.env.USE_TENANT_MODE;
267
- expect(envConfig.useTenantMode()).toBe(false);
268
- });
269
- it('should be true when explicitly set', ()=>{
270
- process.env.USE_TENANT_MODE = 'true';
271
- expect(envConfig.useTenantMode()).toBe(true);
272
- });
273
- });
274
- describe('getLogConfig', ()=>{
275
- it('should default log level to info in production', ()=>{
276
- delete process.env.MODE;
277
- delete process.env.LOG_LEVEL;
278
- expect(envConfig.getLogConfig().level).toBe('info');
279
- });
280
- it('should default log level to debug outside production', ()=>{
281
- process.env.MODE = 'dev';
282
- delete process.env.LOG_LEVEL;
283
- expect(envConfig.getLogConfig().level).toBe('debug');
284
- });
285
- it('should apply all defaults when unset', ()=>{
286
- process.env.MODE = 'dev';
287
- delete process.env.LOG_DIR;
288
- delete process.env.LOG_LEVEL;
289
- delete process.env.LOG_MAX_SIZE;
290
- delete process.env.LOG_MAX_FILES;
291
- delete process.env.DISABLE_HTTP_LOGGING;
292
- expect(envConfig.getLogConfig()).toEqual({
293
- dir: 'logs',
294
- level: 'debug',
295
- maxSize: '20m',
296
- maxFiles: '14d',
297
- disableHttpLogging: false
298
- });
299
- });
300
- it('should honor explicit overrides', ()=>{
301
- process.env.LOG_DIR = '/var/log/flusys';
302
- process.env.LOG_LEVEL = 'warn';
303
- process.env.LOG_MAX_SIZE = '50m';
304
- process.env.LOG_MAX_FILES = '30d';
305
- process.env.DISABLE_HTTP_LOGGING = 'true';
306
- expect(envConfig.getLogConfig()).toEqual({
307
- dir: '/var/log/flusys',
308
- level: 'warn',
309
- maxSize: '50m',
310
- maxFiles: '30d',
311
- disableHttpLogging: true
312
- });
313
- });
314
- });
315
- describe('getEnv', ()=>{
316
- it('should return the full process.env map', ()=>{
317
- process.env.SOME_FLAG = 'x';
318
- expect(envConfig.getEnv()).toBe(process.env);
319
- });
320
- });
321
- });
@@ -1,2 +0,0 @@
1
- export const MODULE_OPTIONS = Symbol('MODULE_OPTIONS');
2
- export const DEFAULT_TENANT_HEADER = 'x-tenant-id';
@@ -1,258 +0,0 @@
1
- import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
2
- function filterSchemaProperties(document, exclusions) {
3
- if (!exclusions?.length || !document.components?.schemas) {
4
- return document;
5
- }
6
- const schemas = {
7
- ...document.components.schemas
8
- };
9
- for (const exclusion of exclusions){
10
- const schema = schemas[exclusion.schemaName];
11
- if (schema && typeof schema === 'object' && 'properties' in schema) {
12
- const filteredProperties = {
13
- ...schema.properties
14
- };
15
- for (const prop of exclusion.properties){
16
- delete filteredProperties[prop];
17
- }
18
- // Also remove from required array if present
19
- const filteredRequired = schema.required?.filter((r)=>!exclusion.properties.includes(r));
20
- schemas[exclusion.schemaName] = {
21
- ...schema,
22
- properties: filteredProperties,
23
- ...filteredRequired?.length ? {
24
- required: filteredRequired
25
- } : {}
26
- };
27
- }
28
- }
29
- return {
30
- ...document,
31
- components: {
32
- ...document.components,
33
- schemas
34
- }
35
- };
36
- }
37
- function filterQueryParameters(document, exclusions) {
38
- if (!exclusions?.length || !document.paths) {
39
- return document;
40
- }
41
- const filteredPaths = {};
42
- for (const [path, pathItem] of Object.entries(document.paths)){
43
- const filteredPathItem = {};
44
- for (const [method, operation] of Object.entries(pathItem)){
45
- // Skip non-operation properties
46
- if (!operation || typeof operation !== 'object') {
47
- filteredPathItem[method] = operation;
48
- continue;
49
- }
50
- // Check if this endpoint matches any exclusion pattern
51
- const matchingExclusions = exclusions.filter((exclusion)=>{
52
- const pathMatches = pathMatchesPattern(path, exclusion.pathPattern);
53
- const methodMatches = !exclusion.method || exclusion.method === method;
54
- return pathMatches && methodMatches;
55
- });
56
- if (matchingExclusions.length > 0) {
57
- // Collect all parameters to exclude
58
- const paramsToExclude = new Set();
59
- matchingExclusions.forEach((exclusion)=>{
60
- exclusion.parameters.forEach((param)=>paramsToExclude.add(param));
61
- });
62
- // Filter out excluded query parameters
63
- const filteredOperation = {
64
- ...operation
65
- };
66
- if (Array.isArray(filteredOperation.parameters)) {
67
- filteredOperation.parameters = filteredOperation.parameters.filter((param)=>{
68
- return !(param.in === 'query' && paramsToExclude.has(param.name));
69
- });
70
- }
71
- filteredPathItem[method] = filteredOperation;
72
- } else {
73
- filteredPathItem[method] = operation;
74
- }
75
- }
76
- filteredPaths[path] = filteredPathItem;
77
- }
78
- return {
79
- ...document,
80
- paths: filteredPaths
81
- };
82
- }
83
- function filterExamples(document, exclusions) {
84
- if (!exclusions?.length || !document.paths) {
85
- return document;
86
- }
87
- const filteredPaths = {};
88
- for (const [path, pathItem] of Object.entries(document.paths)){
89
- const filteredPathItem = {};
90
- for (const [method, operation] of Object.entries(pathItem)){
91
- // Skip non-operation properties
92
- if (!operation || typeof operation !== 'object') {
93
- filteredPathItem[method] = operation;
94
- continue;
95
- }
96
- // Check if this endpoint matches any exclusion pattern
97
- const matchingExclusions = exclusions.filter((exclusion)=>{
98
- const pathMatches = pathMatchesPattern(path, exclusion.pathPattern);
99
- const methodMatches = !exclusion.method || exclusion.method === method;
100
- return pathMatches && methodMatches;
101
- });
102
- if (matchingExclusions.length > 0) {
103
- // Collect all examples to exclude
104
- const examplesToExclude = new Set();
105
- matchingExclusions.forEach((exclusion)=>{
106
- exclusion.examples.forEach((example)=>examplesToExclude.add(example));
107
- });
108
- // Deep clone the operation to avoid mutating original
109
- const filteredOperation = JSON.parse(JSON.stringify(operation));
110
- // Filter examples from responses
111
- if (filteredOperation.responses) {
112
- for (const [statusCode, response] of Object.entries(filteredOperation.responses)){
113
- const responseObj = response;
114
- if (responseObj?.content) {
115
- const content = responseObj.content;
116
- for (const [mediaType, mediaTypeObj] of Object.entries(content)){
117
- const media = mediaTypeObj;
118
- if (media?.examples && typeof media.examples === 'object') {
119
- const examples = media.examples;
120
- for (const exampleName of examplesToExclude){
121
- delete examples[exampleName];
122
- }
123
- }
124
- }
125
- }
126
- }
127
- }
128
- filteredPathItem[method] = filteredOperation;
129
- } else {
130
- filteredPathItem[method] = operation;
131
- }
132
- }
133
- filteredPaths[path] = filteredPathItem;
134
- }
135
- return {
136
- ...document,
137
- paths: filteredPaths
138
- };
139
- }
140
- function pathMatchesPattern(path, pattern) {
141
- // Convert wildcard pattern to regex
142
- const regexPattern = pattern.replace(/\*/g, '[^/]+') // * matches any characters except /
143
- .replace(/\*\*/g, '.*'); // ** matches any characters including /
144
- const regex = new RegExp(`^${regexPattern}$`);
145
- return regex.test(path);
146
- }
147
- function filterPaths(document, excludePaths) {
148
- if (!excludePaths?.length || !document.paths) {
149
- return document;
150
- }
151
- const filteredPaths = {};
152
- for (const [path, pathItem] of Object.entries(document.paths)){
153
- const isExcluded = excludePaths.some((pattern)=>pathMatchesPattern(path, pattern));
154
- if (!isExcluded) {
155
- filteredPaths[path] = pathItem;
156
- }
157
- }
158
- return {
159
- ...document,
160
- paths: filteredPaths
161
- };
162
- }
163
- function filterDocumentByTags(document, excludeTags) {
164
- if (!excludeTags?.length) {
165
- return document;
166
- }
167
- const excludeTagsSet = new Set(excludeTags.map((tag)=>tag.toLowerCase()));
168
- // Filter out paths that have any of the excluded tags
169
- const filteredPaths = {};
170
- for (const [path, pathItem] of Object.entries(document.paths)){
171
- const filteredPathItem = {};
172
- for (const [method, operation] of Object.entries(pathItem)){
173
- // Skip non-operation properties like 'parameters'
174
- if (!operation || typeof operation !== 'object' || !('tags' in operation)) {
175
- filteredPathItem[method] = operation;
176
- continue;
177
- }
178
- const operationTags = operation.tags || [];
179
- const hasExcludedTag = operationTags.some((tag)=>excludeTagsSet.has(tag.toLowerCase()));
180
- if (!hasExcludedTag) {
181
- filteredPathItem[method] = operation;
182
- }
183
- }
184
- // Only include path if it has at least one operation
185
- const hasOperations = Object.keys(filteredPathItem).some((key)=>[
186
- 'get',
187
- 'post',
188
- 'put',
189
- 'patch',
190
- 'delete',
191
- 'options',
192
- 'head'
193
- ].includes(key));
194
- if (hasOperations) {
195
- filteredPaths[path] = filteredPathItem;
196
- }
197
- }
198
- // Filter out excluded tags from the tags array
199
- const filteredTags = document.tags?.filter((tag)=>!excludeTagsSet.has(tag.name.toLowerCase()));
200
- return {
201
- ...document,
202
- paths: filteredPaths,
203
- tags: filteredTags
204
- };
205
- }
206
- export function setupModuleSwaggerDocs(app, configs) {
207
- configs.forEach((config)=>{
208
- const builder = new DocumentBuilder().setTitle(config.title).setDescription(config.description).setVersion(config.version || '1.0');
209
- // Add bearer auth
210
- if (config.bearerAuth) {
211
- builder.addBearerAuth();
212
- }
213
- // Add custom global headers
214
- if (config.globalHeaders?.length) {
215
- config.globalHeaders.forEach((header)=>{
216
- builder.addGlobalParameters({
217
- name: header.name,
218
- in: 'header',
219
- description: header.description,
220
- required: header.required ?? false,
221
- schema: {
222
- type: 'string',
223
- example: header.example || ''
224
- }
225
- });
226
- });
227
- }
228
- // Only filter by modules if specified
229
- const createDocOptions = config.modules?.length ? {
230
- include: config.modules
231
- } : {};
232
- let document = SwaggerModule.createDocument(app, builder.build(), createDocOptions);
233
- // Filter out excluded paths
234
- if (config.excludePaths?.length) {
235
- document = filterPaths(document, config.excludePaths);
236
- }
237
- // Filter out excluded tags
238
- if (config.excludeTags?.length) {
239
- document = filterDocumentByTags(document, config.excludeTags);
240
- }
241
- // Filter out excluded schema properties
242
- if (config.excludeSchemaProperties?.length) {
243
- document = filterSchemaProperties(document, config.excludeSchemaProperties);
244
- }
245
- // Filter out excluded query parameters
246
- if (config.excludeQueryParameters?.length) {
247
- document = filterQueryParameters(document, config.excludeQueryParameters);
248
- }
249
- // Filter out excluded examples
250
- if (config.excludeExamples?.length) {
251
- document = filterExamples(document, config.excludeExamples);
252
- }
253
- SwaggerModule.setup(config.path, app, document);
254
- });
255
- }
256
- export function setupSwaggerDocs(app, ...modules) {
257
- setupModuleSwaggerDocs(app, modules);
258
- }