@lenne.tech/nest-server 11.24.1 → 11.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/configurable-features.md +1 -0
- package/.claude/rules/package-management.md +61 -2
- package/CLAUDE.md +77 -0
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/common/decorators/unified-field.decorator.d.ts +2 -1
- package/dist/core/common/decorators/unified-field.decorator.js +60 -13
- package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
- package/dist/core/common/helpers/register-enum.helper.d.ts +6 -0
- package/dist/core/common/helpers/register-enum.helper.js +23 -1
- package/dist/core/common/helpers/register-enum.helper.js.map +1 -1
- package/dist/core/common/inputs/combined-filter.input.js +1 -1
- package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.js +1 -1
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/inputs/sort.input.js +1 -1
- package/dist/core/common/inputs/sort.input.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +7 -1
- package/dist/core/common/services/email.service.d.ts +2 -2
- package/dist/core/common/services/email.service.js +7 -0
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.24.2-to-11.24.3.md +255 -0
- package/package.json +32 -13
- package/src/core/common/decorators/unified-field.decorator.ts +173 -23
- package/src/core/common/helpers/register-enum.helper.ts +89 -1
- package/src/core/common/inputs/combined-filter.input.ts +1 -1
- package/src/core/common/inputs/single-filter.input.ts +1 -1
- package/src/core/common/inputs/sort.input.ts +1 -1
- package/src/core/common/interfaces/server-options.interface.ts +34 -2
- package/src/core/common/services/email.service.ts +17 -3
|
@@ -2,6 +2,18 @@ import { registerEnumType } from '@nestjs/graphql';
|
|
|
2
2
|
|
|
3
3
|
import { enumNameRegistry } from '../decorators/unified-field.decorator';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Tracks which enum objects have been registered for GraphQL via registerEnum/registerEnums.
|
|
7
|
+
* Used to prevent duplicate registerEnumType() calls which cause schema build errors.
|
|
8
|
+
* Separate from enumNameRegistry (Swagger) because GraphQL registration is a distinct concern.
|
|
9
|
+
*
|
|
10
|
+
* @internal Consumers should use {@link enumNameRegistry} (from unified-field.decorator) to
|
|
11
|
+
* verify Swagger/name registration. This registry is exported for advanced use cases
|
|
12
|
+
* (e.g. checking if an enum is already registered for GraphQL before calling registerEnumType
|
|
13
|
+
* directly), but most projects should not need to interact with it.
|
|
14
|
+
*/
|
|
15
|
+
export const graphqlEnumRegistry = new WeakSet<object>();
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Interface defining options for the registerEnum helper
|
|
7
19
|
*/
|
|
@@ -83,11 +95,87 @@ export function registerEnum<T extends object = any>(enumRef: T, options: Regist
|
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
// Register for GraphQL if enabled
|
|
86
|
-
if (graphql) {
|
|
98
|
+
if (graphql && !graphqlEnumRegistry.has(enumRef)) {
|
|
87
99
|
registerEnumType(enumRef, {
|
|
88
100
|
description,
|
|
89
101
|
name,
|
|
90
102
|
valuesMap,
|
|
91
103
|
});
|
|
104
|
+
graphqlEnumRegistry.add(enumRef);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Options for {@link registerEnums} bulk registration.
|
|
110
|
+
*
|
|
111
|
+
* Controls which API layers the enums are registered for (GraphQL and/or Swagger/REST).
|
|
112
|
+
* When omitted, enums are registered for both layers.
|
|
113
|
+
*/
|
|
114
|
+
export interface RegisterEnumsOptions {
|
|
115
|
+
/**
|
|
116
|
+
* Whether to register enums for GraphQL using registerEnumType.
|
|
117
|
+
* @default true
|
|
118
|
+
*/
|
|
119
|
+
graphql?: boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether to register enums in the enumNameRegistry for Swagger/REST.
|
|
123
|
+
* When enabled, enum names are auto-detected by {@link UnifiedField} decorators
|
|
124
|
+
* for the `enumName` property in Swagger/OpenAPI schemas.
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
swagger?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Bulk-register all enums from a barrel-export namespace object.
|
|
132
|
+
*
|
|
133
|
+
* Uses the **export key names** as enum names — no manual name repetition
|
|
134
|
+
* needed. Call this once per project, typically in your module setup.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // src/server/common/enums/index.ts (barrel file)
|
|
139
|
+
* export { ContactStatusEnum } from './contact-status.enum';
|
|
140
|
+
* export { IndustryEnum } from './industry.enum';
|
|
141
|
+
* export { NotifyViaEnum } from './notify-via.enum';
|
|
142
|
+
*
|
|
143
|
+
* // src/server/server.module.ts (one line)
|
|
144
|
+
* import * as Enums from './common/enums';
|
|
145
|
+
* registerEnums(Enums);
|
|
146
|
+
*
|
|
147
|
+
* // Result: ContactStatusEnum, IndustryEnum, NotifyViaEnum are all
|
|
148
|
+
* // registered for both GraphQL and Swagger auto-detection.
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* Only plain objects with string/number values (enum-like objects) are
|
|
152
|
+
* registered. Non-enum exports (functions, classes, strings) are skipped.
|
|
153
|
+
*
|
|
154
|
+
* @param namespace - The barrel-export namespace object (`import * as X from '...'`)
|
|
155
|
+
* @param options - Optional: control GraphQL/Swagger registration
|
|
156
|
+
*/
|
|
157
|
+
export function registerEnums(namespace: Record<string, any>, options?: RegisterEnumsOptions): void {
|
|
158
|
+
const { graphql = true, swagger = true } = options ?? {};
|
|
159
|
+
|
|
160
|
+
for (const [name, value] of Object.entries(namespace)) {
|
|
161
|
+
// Skip non-objects (functions, strings, numbers, etc.)
|
|
162
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Skip if already registered for all requested targets
|
|
167
|
+
const alreadySwagger = !swagger || enumNameRegistry.has(value);
|
|
168
|
+
const alreadyGraphql = !graphql || graphqlEnumRegistry.has(value);
|
|
169
|
+
if (alreadySwagger && alreadyGraphql) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Verify it looks like an enum: all own values are strings or numbers
|
|
174
|
+
const vals = Object.values(value);
|
|
175
|
+
if (vals.length === 0 || !vals.every((v) => typeof v === 'string' || typeof v === 'number')) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
registerEnum(value, { graphql, name, swagger });
|
|
92
180
|
}
|
|
93
181
|
}
|
|
@@ -18,7 +18,7 @@ export class CombinedFilterInput extends CoreInput {
|
|
|
18
18
|
*/
|
|
19
19
|
@UnifiedField({
|
|
20
20
|
description: 'Logical Operator to combine filters',
|
|
21
|
-
enum:
|
|
21
|
+
enum: LogicalOperatorEnum,
|
|
22
22
|
roles: RoleEnum.S_EVERYONE,
|
|
23
23
|
})
|
|
24
24
|
logicalOperator: LogicalOperatorEnum = undefined;
|
|
@@ -58,7 +58,7 @@ export class SingleFilterInput extends CoreInput {
|
|
|
58
58
|
*/
|
|
59
59
|
@UnifiedField({
|
|
60
60
|
description: '[Comparison operator](https://docs.mongodb.com/manual/reference/operator/query-comparison/)',
|
|
61
|
-
enum:
|
|
61
|
+
enum: ComparisonOperatorEnum,
|
|
62
62
|
roles: RoleEnum.S_EVERYONE,
|
|
63
63
|
})
|
|
64
64
|
operator: ComparisonOperatorEnum = undefined;
|
|
@@ -10,7 +10,12 @@ import { MongoosePingCheckSettings } from '@nestjs/terminus/dist/health-indicato
|
|
|
10
10
|
import { DiskHealthIndicatorOptions } from '@nestjs/terminus/dist/health-indicator/disk/disk-health-options.type';
|
|
11
11
|
import compression from 'compression';
|
|
12
12
|
import { CollationOptions } from 'mongodb';
|
|
13
|
+
import type * as JSONTransport from 'nodemailer/lib/json-transport';
|
|
14
|
+
import type * as SendmailTransport from 'nodemailer/lib/sendmail-transport';
|
|
15
|
+
import type * as SESTransport from 'nodemailer/lib/ses-transport';
|
|
16
|
+
import type * as SMTPPool from 'nodemailer/lib/smtp-pool';
|
|
13
17
|
import * as SMTPTransport from 'nodemailer/lib/smtp-transport';
|
|
18
|
+
import type * as StreamTransport from 'nodemailer/lib/stream-transport';
|
|
14
19
|
|
|
15
20
|
import { Falsy } from '../types/falsy.type';
|
|
16
21
|
import { IPermissions } from '../../modules/permissions/interfaces/permissions.interface';
|
|
@@ -24,6 +29,29 @@ import { MailjetOptions } from './mailjet-options.interface';
|
|
|
24
29
|
*/
|
|
25
30
|
export type BetterAuthFieldType = 'boolean' | 'date' | 'json' | 'number' | 'number[]' | 'string' | 'string[]';
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* All transport configurations accepted by `nodemailer.createTransport()`.
|
|
34
|
+
*
|
|
35
|
+
* Covers every built-in transport nodemailer ships with. `JSONTransport` is
|
|
36
|
+
* particularly useful in CI / e2e tests: `{ jsonTransport: true }` serializes
|
|
37
|
+
* outgoing mail to a JSON string and returns a valid response without any
|
|
38
|
+
* network I/O — no SMTP server, no credentials, no flakiness.
|
|
39
|
+
*/
|
|
40
|
+
export type MailTransportOptions =
|
|
41
|
+
| JSONTransport
|
|
42
|
+
| JSONTransport.Options
|
|
43
|
+
| SendmailTransport
|
|
44
|
+
| SendmailTransport.Options
|
|
45
|
+
| SESTransport
|
|
46
|
+
| SESTransport.Options
|
|
47
|
+
| SMTPPool
|
|
48
|
+
| SMTPPool.Options
|
|
49
|
+
| SMTPTransport
|
|
50
|
+
| SMTPTransport.Options
|
|
51
|
+
| StreamTransport
|
|
52
|
+
| StreamTransport.Options
|
|
53
|
+
| string;
|
|
54
|
+
|
|
27
55
|
/**
|
|
28
56
|
* Interface for Auth configuration
|
|
29
57
|
*
|
|
@@ -1147,9 +1175,13 @@ export interface IServerOptions {
|
|
|
1147
1175
|
passwordResetLink?: string;
|
|
1148
1176
|
|
|
1149
1177
|
/**
|
|
1150
|
-
*
|
|
1178
|
+
* Mail transport configuration for nodemailer.
|
|
1179
|
+
*
|
|
1180
|
+
* Accepts any transport type supported by `nodemailer.createTransport()`.
|
|
1181
|
+
* See {@link MailTransportOptions} for the full list including
|
|
1182
|
+
* `JSONTransport` / `{ jsonTransport: true }` for CI and e2e tests.
|
|
1151
1183
|
*/
|
|
1152
|
-
smtp?:
|
|
1184
|
+
smtp?: MailTransportOptions;
|
|
1153
1185
|
|
|
1154
1186
|
/**
|
|
1155
1187
|
* Verification link for email
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type SMTPPool = require('nodemailer/lib/smtp-pool');
|
|
2
|
-
|
|
3
1
|
import { createHash } from 'crypto';
|
|
4
2
|
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
|
5
3
|
import nodemailer = require('nodemailer');
|
|
6
4
|
import { Attachment } from 'nodemailer/lib/mailer';
|
|
7
5
|
|
|
8
6
|
import { isNonEmptyString, isTrue, returnFalse } from '../helpers/input.helper';
|
|
7
|
+
import { MailTransportOptions } from '../interfaces/server-options.interface';
|
|
9
8
|
import { ConfigService } from './config.service';
|
|
10
9
|
import { TemplateService } from './template.service';
|
|
11
10
|
|
|
@@ -49,7 +48,7 @@ export class EmailService implements OnModuleDestroy {
|
|
|
49
48
|
htmlTemplate?: string;
|
|
50
49
|
senderEmail?: string;
|
|
51
50
|
senderName?: string;
|
|
52
|
-
smtp?:
|
|
51
|
+
smtp?: MailTransportOptions;
|
|
53
52
|
templateData?: { [key: string]: any };
|
|
54
53
|
text?: string;
|
|
55
54
|
textTemplate?: string;
|
|
@@ -90,6 +89,21 @@ export class EmailService implements OnModuleDestroy {
|
|
|
90
89
|
isNonEmptyString(html);
|
|
91
90
|
}
|
|
92
91
|
|
|
92
|
+
// Guard: JSONTransport silently discards all mail — block in production / staging
|
|
93
|
+
// to prevent accidental misconfiguration that causes password-reset, 2FA, and
|
|
94
|
+
// verification emails to vanish without error.
|
|
95
|
+
// Uses truthy check (not strict === true) because nodemailer activates
|
|
96
|
+
// JSONTransport for any truthy value of options.jsonTransport.
|
|
97
|
+
const env = this.configService.getFastButReadOnly<string>('env');
|
|
98
|
+
if (env === 'production' || env === 'staging') {
|
|
99
|
+
if (typeof smtp === 'object' && smtp !== null && !!(smtp as Record<string, unknown>).jsonTransport) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'JSONTransport (jsonTransport: true) is not permitted in production/staging environments. ' +
|
|
102
|
+
'It silently discards all outgoing email. Check email.smtp in your config.',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
// Reuse transporter if SMTP config hasn't changed (avoids creating new connections per email)
|
|
94
108
|
// Use hash instead of raw JSON to avoid keeping credentials as a string in memory
|
|
95
109
|
const smtpKey = createHash('sha256').update(JSON.stringify(smtp)).digest('hex');
|