metal-orm 1.0.93 → 1.0.95
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/dist/index.cjs +757 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -1
- package/dist/index.d.ts +229 -1
- package/dist/index.js +739 -23
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/decorators/bootstrap.ts +11 -1
- package/src/decorators/decorator-metadata.ts +3 -1
- package/src/decorators/index.ts +27 -0
- package/src/decorators/transformers/built-in/string-transformers.ts +231 -0
- package/src/decorators/transformers/transformer-decorators.ts +137 -0
- package/src/decorators/transformers/transformer-executor.ts +172 -0
- package/src/decorators/transformers/transformer-metadata.ts +73 -0
- package/src/decorators/validators/built-in/br-cep-validator.ts +55 -0
- package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -0
- package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -0
- package/src/decorators/validators/country-validator-registry.ts +64 -0
- package/src/decorators/validators/country-validators-decorators.ts +209 -0
- package/src/decorators/validators/country-validators.ts +105 -0
- package/src/decorators/validators/validator-metadata.ts +59 -0
- package/src/dto/openapi/utilities.ts +5 -0
- package/src/index.ts +4 -3
- package/src/orm/column-introspection.ts +43 -0
- package/src/orm/entity-metadata.ts +64 -45
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ColumnType } from '../../schema/column-types.js';
|
|
2
|
+
|
|
3
|
+
// Transform context provides metadata about the transformation
|
|
4
|
+
export interface TransformContext {
|
|
5
|
+
entityName: string;
|
|
6
|
+
propertyName: string;
|
|
7
|
+
columnType: ColumnType;
|
|
8
|
+
isUpdate: boolean;
|
|
9
|
+
originalValue?: unknown;
|
|
10
|
+
autoTransform: boolean; // Whether auto-correction is enabled
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Auto-transform result for validators that can fix data
|
|
14
|
+
export interface AutoTransformResult<T = unknown> {
|
|
15
|
+
success: boolean;
|
|
16
|
+
correctedValue?: T;
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Base transformer interface
|
|
21
|
+
export interface PropertyTransformer<TInput = unknown, TOutput = unknown> {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
transform(value: TInput, context: TransformContext): TOutput;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validator interface (read-only, throws on failure)
|
|
27
|
+
export interface PropertyValidator<T = unknown> {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
validate(value: T, context: TransformContext): ValidationResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Sanitizer interface (mutates, never throws)
|
|
33
|
+
export interface PropertySanitizer<T = unknown> {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
sanitize(value: T, context: TransformContext): T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Combined transformer that can validate and transform
|
|
39
|
+
export interface CompositeTransformer<TInput = unknown, TOutput = unknown>
|
|
40
|
+
extends PropertyTransformer<TInput, TOutput>,
|
|
41
|
+
PropertyValidator<TInput> {}
|
|
42
|
+
|
|
43
|
+
// Extended validator interface with auto-transform capability
|
|
44
|
+
export interface AutoTransformableValidator<T = unknown> extends PropertyValidator<T> {
|
|
45
|
+
/**
|
|
46
|
+
* Attempts to automatically correct an invalid value.
|
|
47
|
+
* Returns undefined if auto-correction is not possible.
|
|
48
|
+
*/
|
|
49
|
+
autoTransform?(value: T, context: TransformContext): AutoTransformResult<T>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validation result
|
|
53
|
+
export interface ValidationResult {
|
|
54
|
+
isValid: boolean;
|
|
55
|
+
error?: string;
|
|
56
|
+
message?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Transformer metadata stored during decoration phase
|
|
60
|
+
export interface TransformerMetadata {
|
|
61
|
+
propertyName: string;
|
|
62
|
+
transformers: PropertyTransformer[];
|
|
63
|
+
validators: PropertyValidator[];
|
|
64
|
+
sanitizers: PropertySanitizer[];
|
|
65
|
+
executionOrder: 'before-save' | 'after-load' | 'both';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Transformer configuration options
|
|
69
|
+
export interface TransformerConfig {
|
|
70
|
+
auto?: boolean; // Enable auto-transform mode (default: false)
|
|
71
|
+
execute?: 'before-save' | 'after-load' | 'both'; // When to execute (default: 'both')
|
|
72
|
+
stopOnFirstError?: boolean; // Stop validation on first error (default: false)
|
|
73
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CountryValidator, ValidationOptions, ValidationResult, AutoCorrectionResult } from '../country-validators.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brazilian CEP validator
|
|
5
|
+
* Format: XXXXX-XXX (8 digits)
|
|
6
|
+
*/
|
|
7
|
+
export class CEPValidator implements CountryValidator<string> {
|
|
8
|
+
readonly countryCode = 'BR';
|
|
9
|
+
readonly identifierType = 'cep';
|
|
10
|
+
readonly name = 'br-cep';
|
|
11
|
+
|
|
12
|
+
validate(value: string, options: ValidationOptions = {}): ValidationResult {
|
|
13
|
+
const normalized = this.normalize(value);
|
|
14
|
+
|
|
15
|
+
// Validate format
|
|
16
|
+
if (!/^\d{8}$/.test(normalized)) {
|
|
17
|
+
return {
|
|
18
|
+
isValid: false,
|
|
19
|
+
error: options.errorMessage || 'CEP must contain exactly 8 numeric digits'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
isValid: true,
|
|
25
|
+
normalizedValue: normalized,
|
|
26
|
+
formattedValue: this.format(value)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
normalize(value: string): string {
|
|
31
|
+
return value.replace(/[^0-9]/g, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
format(value: string): string {
|
|
35
|
+
const normalized = this.normalize(value);
|
|
36
|
+
if (normalized.length !== 8) return value;
|
|
37
|
+
return normalized.replace(/(\d{5})(\d{3})/, '$1-$2');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
autoCorrect(value: string): AutoCorrectionResult<string> {
|
|
41
|
+
const normalized = this.normalize(value);
|
|
42
|
+
|
|
43
|
+
if (normalized.length === 8) {
|
|
44
|
+
return { success: true, correctedValue: this.format(normalized) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (normalized.length < 8) {
|
|
48
|
+
const padded = normalized.padEnd(8, '0');
|
|
49
|
+
return { success: true, correctedValue: this.format(padded) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const truncated = normalized.slice(0, 8);
|
|
53
|
+
return { success: true, correctedValue: this.format(truncated) };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { CountryValidator, ValidationOptions, ValidationResult, AutoCorrectionResult } from '../country-validators.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brazilian CNPJ validator
|
|
5
|
+
* Format: XX.XXX.XXX/XXXX-XX (14 digits)
|
|
6
|
+
* Checksum: Mod 11 algorithm
|
|
7
|
+
*/
|
|
8
|
+
export class CNPJValidator implements CountryValidator<string> {
|
|
9
|
+
readonly countryCode = 'BR';
|
|
10
|
+
readonly identifierType = 'cnpj';
|
|
11
|
+
readonly name = 'br-cnpj';
|
|
12
|
+
|
|
13
|
+
validate(value: string, options: ValidationOptions = {}): ValidationResult {
|
|
14
|
+
const normalized = this.normalize(value);
|
|
15
|
+
|
|
16
|
+
// Validate format
|
|
17
|
+
if (!/^\d{14}$/.test(normalized)) {
|
|
18
|
+
return {
|
|
19
|
+
isValid: false,
|
|
20
|
+
error: options.errorMessage || 'CNPJ must contain exactly 14 numeric digits'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for known invalid CNPJs
|
|
25
|
+
if (this.isKnownInvalid(normalized) && options.strict !== false) {
|
|
26
|
+
return {
|
|
27
|
+
isValid: false,
|
|
28
|
+
error: options.errorMessage || 'Invalid CNPJ number'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate checksum
|
|
33
|
+
if (!this.validateChecksum(normalized)) {
|
|
34
|
+
return {
|
|
35
|
+
isValid: false,
|
|
36
|
+
error: options.errorMessage || 'Invalid CNPJ checksum'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
isValid: true,
|
|
42
|
+
normalizedValue: normalized,
|
|
43
|
+
formattedValue: this.format(value)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
normalize(value: string): string {
|
|
48
|
+
return value.replace(/[^0-9]/g, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
format(value: string): string {
|
|
52
|
+
const normalized = this.normalize(value);
|
|
53
|
+
if (normalized.length !== 14) return value;
|
|
54
|
+
return normalized.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
autoCorrect(value: string): AutoCorrectionResult<string> {
|
|
58
|
+
const normalized = this.normalize(value);
|
|
59
|
+
|
|
60
|
+
if (normalized.length === 14) {
|
|
61
|
+
return { success: true, correctedValue: this.format(normalized) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (normalized.length < 14) {
|
|
65
|
+
const padded = normalized.padEnd(14, '0');
|
|
66
|
+
return { success: true, correctedValue: this.format(padded) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const truncated = normalized.slice(0, 14);
|
|
70
|
+
return { success: true, correctedValue: this.format(truncated) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private isKnownInvalid(cnpj: string): boolean {
|
|
74
|
+
// Check for CNPJs with all digits the same (e.g., 00.000.000/0000-00)
|
|
75
|
+
return /^(\d)\1{13}$/.test(cnpj);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private validateChecksum(cnpj: string): boolean {
|
|
79
|
+
const digits = cnpj.split('').map(Number);
|
|
80
|
+
const weights1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
|
|
81
|
+
const weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
|
|
82
|
+
|
|
83
|
+
// Calculate first check digit
|
|
84
|
+
let sum = 0;
|
|
85
|
+
for (let i = 0; i < 12; i++) {
|
|
86
|
+
sum += digits[i] * weights1[i];
|
|
87
|
+
}
|
|
88
|
+
let check1 = sum % 11;
|
|
89
|
+
check1 = check1 < 2 ? 0 : 11 - check1;
|
|
90
|
+
|
|
91
|
+
if (check1 !== digits[12]) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Calculate second check digit
|
|
96
|
+
sum = 0;
|
|
97
|
+
for (let i = 0; i < 13; i++) {
|
|
98
|
+
sum += digits[i] * weights2[i];
|
|
99
|
+
}
|
|
100
|
+
let check2 = sum % 11;
|
|
101
|
+
check2 = check2 < 2 ? 0 : 11 - check2;
|
|
102
|
+
|
|
103
|
+
return check2 === digits[13];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { CountryValidator, ValidationOptions, ValidationResult, AutoCorrectionResult } from '../country-validators.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brazilian CPF validator
|
|
5
|
+
* Format: XXX.XXX.XXX-XX (11 digits)
|
|
6
|
+
* Checksum: Mod 11 algorithm
|
|
7
|
+
*/
|
|
8
|
+
export class CPFValidator implements CountryValidator<string> {
|
|
9
|
+
readonly countryCode = 'BR';
|
|
10
|
+
readonly identifierType = 'cpf';
|
|
11
|
+
readonly name = 'br-cpf';
|
|
12
|
+
|
|
13
|
+
validate(value: string, options: ValidationOptions = {}): ValidationResult {
|
|
14
|
+
const normalized = this.normalize(value);
|
|
15
|
+
|
|
16
|
+
// Validate format
|
|
17
|
+
if (!/^\d{11}$/.test(normalized)) {
|
|
18
|
+
return {
|
|
19
|
+
isValid: false,
|
|
20
|
+
error: options.errorMessage || 'CPF must contain exactly 11 numeric digits'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for known invalid CPFs
|
|
25
|
+
if (this.isKnownInvalid(normalized) && options.strict !== false) {
|
|
26
|
+
return {
|
|
27
|
+
isValid: false,
|
|
28
|
+
error: options.errorMessage || 'Invalid CPF number'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate checksum
|
|
33
|
+
if (!this.validateChecksum(normalized)) {
|
|
34
|
+
return {
|
|
35
|
+
isValid: false,
|
|
36
|
+
error: options.errorMessage || 'Invalid CPF checksum'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
isValid: true,
|
|
42
|
+
normalizedValue: normalized,
|
|
43
|
+
formattedValue: this.format(value)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
normalize(value: string): string {
|
|
48
|
+
return value.replace(/[^0-9]/g, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
format(value: string): string {
|
|
52
|
+
const normalized = this.normalize(value);
|
|
53
|
+
if (normalized.length !== 11) return value;
|
|
54
|
+
return normalized.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
autoCorrect(value: string): AutoCorrectionResult<string> {
|
|
58
|
+
const normalized = this.normalize(value);
|
|
59
|
+
|
|
60
|
+
if (normalized.length === 11) {
|
|
61
|
+
return { success: true, correctedValue: this.format(normalized) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (normalized.length < 11) {
|
|
65
|
+
const padded = normalized.padEnd(11, '0');
|
|
66
|
+
return { success: true, correctedValue: this.format(padded) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const truncated = normalized.slice(0, 11);
|
|
70
|
+
return { success: true, correctedValue: this.format(truncated) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private isKnownInvalid(cpf: string): boolean {
|
|
74
|
+
// Check for CPFs with all digits the same (e.g., 111.111.111-11)
|
|
75
|
+
return /^(\d)\1{10}$/.test(cpf);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private validateChecksum(cpf: string): boolean {
|
|
79
|
+
const digits = cpf.split('').map(Number);
|
|
80
|
+
|
|
81
|
+
// Calculate first check digit
|
|
82
|
+
let sum = 0;
|
|
83
|
+
for (let i = 0; i < 9; i++) {
|
|
84
|
+
sum += digits[i] * (10 - i);
|
|
85
|
+
}
|
|
86
|
+
let check1 = sum % 11;
|
|
87
|
+
check1 = check1 < 2 ? 0 : 11 - check1;
|
|
88
|
+
|
|
89
|
+
if (check1 !== digits[9]) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Calculate second check digit
|
|
94
|
+
sum = 0;
|
|
95
|
+
for (let i = 0; i < 10; i++) {
|
|
96
|
+
sum += digits[i] * (11 - i);
|
|
97
|
+
}
|
|
98
|
+
let check2 = sum % 11;
|
|
99
|
+
check2 = check2 < 2 ? 0 : 11 - check2;
|
|
100
|
+
|
|
101
|
+
return check2 === digits[10];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CountryValidator, CountryValidatorFactory } from './country-validators.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registry for country-specific identifier validators
|
|
5
|
+
* Mirrors the INFLECTOR_FACTORIES pattern from scripts/inflection/index.mjs
|
|
6
|
+
*/
|
|
7
|
+
const VALIDATOR_FACTORIES = new Map<string, CountryValidatorFactory>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Registers a country validator factory
|
|
11
|
+
* @param countryCode - ISO 3166-1 alpha-2 country code (e.g., 'BR', 'US')
|
|
12
|
+
* @param identifierType - Identifier type (e.g., 'cpf', 'ssn')
|
|
13
|
+
* @param factory - Factory function that creates a validator instance
|
|
14
|
+
*/
|
|
15
|
+
export const registerValidator = (
|
|
16
|
+
countryCode: string,
|
|
17
|
+
identifierType: string,
|
|
18
|
+
factory: CountryValidatorFactory
|
|
19
|
+
): void => {
|
|
20
|
+
const key = `${countryCode.toLowerCase()}-${identifierType.toLowerCase()}`;
|
|
21
|
+
if (!countryCode) throw new Error('countryCode is required');
|
|
22
|
+
if (!identifierType) throw new Error('identifierType is required');
|
|
23
|
+
if (typeof factory !== 'function') {
|
|
24
|
+
throw new Error('factory must be a function that returns a validator');
|
|
25
|
+
}
|
|
26
|
+
VALIDATOR_FACTORIES.set(key, factory);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolves a validator for a given country and identifier type
|
|
31
|
+
* @param countryCode - ISO 3166-1 alpha-2 country code
|
|
32
|
+
* @param identifierType - Identifier type
|
|
33
|
+
* @returns Validator instance or undefined if not found
|
|
34
|
+
*/
|
|
35
|
+
export const resolveValidator = (
|
|
36
|
+
countryCode: string,
|
|
37
|
+
identifierType: string
|
|
38
|
+
): CountryValidator | undefined => {
|
|
39
|
+
const key = `${countryCode.toLowerCase()}-${identifierType.toLowerCase()}`;
|
|
40
|
+
const factory = VALIDATOR_FACTORIES.get(key);
|
|
41
|
+
return factory ? factory() : undefined;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets all registered validator keys
|
|
46
|
+
* @returns Array of validator keys (e.g., ['br-cpf', 'us-ssn'])
|
|
47
|
+
*/
|
|
48
|
+
export const getRegisteredValidators = (): string[] => {
|
|
49
|
+
return Array.from(VALIDATOR_FACTORIES.keys());
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a validator is registered
|
|
54
|
+
* @param countryCode - ISO 3166-1 alpha-2 country code
|
|
55
|
+
* @param identifierType - Identifier type
|
|
56
|
+
* @returns True if validator is registered
|
|
57
|
+
*/
|
|
58
|
+
export const hasValidator = (
|
|
59
|
+
countryCode: string,
|
|
60
|
+
identifierType: string
|
|
61
|
+
): boolean => {
|
|
62
|
+
const key = `${countryCode.toLowerCase()}-${identifierType.toLowerCase()}`;
|
|
63
|
+
return VALIDATOR_FACTORIES.has(key);
|
|
64
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { getOrCreateMetadataBag } from '../decorator-metadata.js';
|
|
2
|
+
import { registerValidator } from './country-validator-registry.js';
|
|
3
|
+
import { CPFValidator } from './built-in/br-cpf-validator.js';
|
|
4
|
+
import { CNPJValidator } from './built-in/br-cnpj-validator.js';
|
|
5
|
+
import { CEPValidator } from './built-in/br-cep-validator.js';
|
|
6
|
+
|
|
7
|
+
// Register built-in validators
|
|
8
|
+
registerValidator('BR', 'cpf', () => new CPFValidator());
|
|
9
|
+
registerValidator('BR', 'cnpj', () => new CNPJValidator());
|
|
10
|
+
registerValidator('BR', 'cep', () => new CEPValidator());
|
|
11
|
+
|
|
12
|
+
const normalizePropertyName = (name: string | symbol): string => {
|
|
13
|
+
if (typeof name === 'symbol') {
|
|
14
|
+
return name.description ?? name.toString();
|
|
15
|
+
}
|
|
16
|
+
return name;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decorator to validate a Brazilian CPF number
|
|
21
|
+
* @param options - Validation options
|
|
22
|
+
* @returns Property decorator for CPF validation
|
|
23
|
+
*/
|
|
24
|
+
export function CPF(options?: { strict?: boolean; errorMessage?: string }) {
|
|
25
|
+
return function (_value: unknown, context: ClassFieldDecoratorContext) {
|
|
26
|
+
const propertyName = normalizePropertyName(context.name);
|
|
27
|
+
const bag = getOrCreateMetadataBag(context);
|
|
28
|
+
|
|
29
|
+
// Find or create transformer metadata for this property
|
|
30
|
+
let existing = bag.transformers.find(t => t.propertyName === propertyName);
|
|
31
|
+
if (!existing) {
|
|
32
|
+
existing = {
|
|
33
|
+
propertyName,
|
|
34
|
+
metadata: {
|
|
35
|
+
propertyName,
|
|
36
|
+
transformers: [],
|
|
37
|
+
validators: [],
|
|
38
|
+
sanitizers: [],
|
|
39
|
+
executionOrder: 'both'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
bag.transformers.push(existing);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create validator instance
|
|
46
|
+
const validator = new CPFValidator();
|
|
47
|
+
|
|
48
|
+
// Add validator to metadata
|
|
49
|
+
existing.metadata.validators.push({
|
|
50
|
+
name: validator.name,
|
|
51
|
+
validate: (value: string) => {
|
|
52
|
+
const result = validator.validate(value, {
|
|
53
|
+
strict: options?.strict ?? true,
|
|
54
|
+
errorMessage: options?.errorMessage
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
isValid: result.isValid,
|
|
58
|
+
error: result.error,
|
|
59
|
+
message: result.error
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
autoTransform: (value: string) => {
|
|
63
|
+
const correction = validator.autoCorrect(value);
|
|
64
|
+
if (correction?.success) {
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
correctedValue: correction.correctedValue,
|
|
68
|
+
message: correction.message
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { success: false };
|
|
72
|
+
}
|
|
73
|
+
} as unknown as never);
|
|
74
|
+
|
|
75
|
+
// Add sanitizer to normalize and format
|
|
76
|
+
existing.metadata.sanitizers.push({
|
|
77
|
+
name: 'cpf-formatter',
|
|
78
|
+
sanitize: (value: string) => validator.format(value)
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Decorator to validate a Brazilian CNPJ number
|
|
85
|
+
* @param options - Validation options
|
|
86
|
+
* @returns Property decorator for CNPJ validation
|
|
87
|
+
*/
|
|
88
|
+
export function CNPJ(options?: { strict?: boolean; errorMessage?: string }) {
|
|
89
|
+
return function (_value: unknown, context: ClassFieldDecoratorContext) {
|
|
90
|
+
const propertyName = normalizePropertyName(context.name);
|
|
91
|
+
const bag = getOrCreateMetadataBag(context);
|
|
92
|
+
|
|
93
|
+
// Find or create transformer metadata for this property
|
|
94
|
+
let existing = bag.transformers.find(t => t.propertyName === propertyName);
|
|
95
|
+
if (!existing) {
|
|
96
|
+
existing = {
|
|
97
|
+
propertyName,
|
|
98
|
+
metadata: {
|
|
99
|
+
propertyName,
|
|
100
|
+
transformers: [],
|
|
101
|
+
validators: [],
|
|
102
|
+
sanitizers: [],
|
|
103
|
+
executionOrder: 'both'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
bag.transformers.push(existing);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create validator instance
|
|
110
|
+
const validator = new CNPJValidator();
|
|
111
|
+
|
|
112
|
+
// Add validator to metadata
|
|
113
|
+
existing.metadata.validators.push({
|
|
114
|
+
name: validator.name,
|
|
115
|
+
validate: (value: string) => {
|
|
116
|
+
const result = validator.validate(value, {
|
|
117
|
+
strict: options?.strict ?? true,
|
|
118
|
+
errorMessage: options?.errorMessage
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
isValid: result.isValid,
|
|
122
|
+
error: result.error,
|
|
123
|
+
message: result.error
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
autoTransform: (value: string) => {
|
|
127
|
+
const correction = validator.autoCorrect(value);
|
|
128
|
+
if (correction?.success) {
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
correctedValue: correction.correctedValue,
|
|
132
|
+
message: correction.message
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { success: false };
|
|
136
|
+
}
|
|
137
|
+
} as unknown as never);
|
|
138
|
+
|
|
139
|
+
// Add sanitizer to normalize and format
|
|
140
|
+
existing.metadata.sanitizers.push({
|
|
141
|
+
name: 'cnpj-formatter',
|
|
142
|
+
sanitize: (value: string) => validator.format(value)
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Decorator to validate a Brazilian CEP number
|
|
149
|
+
* @param options - Validation options
|
|
150
|
+
* @returns Property decorator for CEP validation
|
|
151
|
+
*/
|
|
152
|
+
export function CEP(options?: { strict?: boolean; errorMessage?: string }) {
|
|
153
|
+
return function (_value: unknown, context: ClassFieldDecoratorContext) {
|
|
154
|
+
const propertyName = normalizePropertyName(context.name);
|
|
155
|
+
const bag = getOrCreateMetadataBag(context);
|
|
156
|
+
|
|
157
|
+
// Find or create transformer metadata for this property
|
|
158
|
+
let existing = bag.transformers.find(t => t.propertyName === propertyName);
|
|
159
|
+
if (!existing) {
|
|
160
|
+
existing = {
|
|
161
|
+
propertyName,
|
|
162
|
+
metadata: {
|
|
163
|
+
propertyName,
|
|
164
|
+
transformers: [],
|
|
165
|
+
validators: [],
|
|
166
|
+
sanitizers: [],
|
|
167
|
+
executionOrder: 'both'
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
bag.transformers.push(existing);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create validator instance
|
|
174
|
+
const validator = new CEPValidator();
|
|
175
|
+
|
|
176
|
+
// Add validator to metadata
|
|
177
|
+
existing.metadata.validators.push({
|
|
178
|
+
name: validator.name,
|
|
179
|
+
validate: (value: string) => {
|
|
180
|
+
const result = validator.validate(value, {
|
|
181
|
+
strict: options?.strict ?? true,
|
|
182
|
+
errorMessage: options?.errorMessage
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
isValid: result.isValid,
|
|
186
|
+
error: result.error,
|
|
187
|
+
message: result.error
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
autoTransform: (value: string) => {
|
|
191
|
+
const correction = validator.autoCorrect(value);
|
|
192
|
+
if (correction?.success) {
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
correctedValue: correction.correctedValue,
|
|
196
|
+
message: correction.message
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { success: false };
|
|
200
|
+
}
|
|
201
|
+
} as unknown as never);
|
|
202
|
+
|
|
203
|
+
// Add sanitizer to normalize and format
|
|
204
|
+
existing.metadata.sanitizers.push({
|
|
205
|
+
name: 'cep-formatter',
|
|
206
|
+
sanitize: (value: string) => validator.format(value)
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
}
|