metal-orm 1.1.11 → 1.1.16
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/README.md +758 -769
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +100 -96
- package/scripts/generate-entities/schema.mjs +122 -122
- package/scripts/generate-entities/tree-detection.mjs +92 -92
- package/scripts/inflection/pt-br.mjs +468 -468
- package/scripts/run-eslint.mjs +33 -34
- package/scripts/show-sql-seed.sql +102 -0
- package/scripts/show-sql.mjs +321 -325
- package/src/codegen/naming-strategy.ts +54 -54
- package/src/codegen/typescript.ts +482 -482
- package/src/core/ast/adapters.ts +1 -1
- package/src/core/ast/builders.ts +123 -123
- package/src/core/ast/expression-builders.ts +745 -745
- package/src/core/ast/expression-nodes.ts +304 -304
- package/src/core/ast/expression-visitor.ts +212 -212
- package/src/core/ast/expression.ts +20 -20
- package/src/core/ast/helpers.ts +14 -14
- package/src/core/ast/join.ts +18 -18
- package/src/core/ast/query.ts +200 -200
- package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/render-reference.test.ts +70 -70
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
- package/src/core/ddl/introspect/catalogs/index.ts +5 -5
- package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
- package/src/core/ddl/introspect/context.ts +15 -15
- package/src/core/ddl/introspect/functions/postgres.ts +35 -35
- package/src/core/ddl/introspect/registry.ts +40 -40
- package/src/core/ddl/introspect/run-select.ts +35 -35
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/ddl/naming-strategy.ts +16 -16
- package/src/core/ddl/schema-dialect.ts +56 -56
- package/src/core/ddl/schema-diff.ts +234 -234
- package/src/core/ddl/schema-generator.ts +1 -1
- package/src/core/ddl/schema-introspect.ts +25 -25
- package/src/core/ddl/sql-writing.ts +171 -171
- package/src/core/dialect/abstract.ts +541 -541
- package/src/core/dialect/base/cte-compiler.ts +33 -33
- package/src/core/dialect/base/function-table-formatter.ts +103 -103
- package/src/core/dialect/base/groupby-compiler.ts +21 -21
- package/src/core/dialect/base/join-compiler.ts +25 -25
- package/src/core/dialect/base/orderby-compiler.ts +35 -35
- package/src/core/dialect/base/pagination-strategy.ts +32 -32
- package/src/core/dialect/base/returning-strategy.ts +57 -57
- package/src/core/dialect/base/sql-dialect.ts +294 -294
- package/src/core/dialect/dialect-factory.ts +91 -91
- package/src/core/dialect/mssql/functions.ts +120 -120
- package/src/core/dialect/mssql/index.ts +317 -317
- package/src/core/dialect/mysql/functions.ts +103 -103
- package/src/core/dialect/mysql/index.ts +105 -105
- package/src/core/dialect/postgres/functions.ts +108 -108
- package/src/core/dialect/postgres/index.ts +91 -91
- package/src/core/dialect/sqlite/functions.ts +129 -129
- package/src/core/dialect/sqlite/index.ts +67 -67
- package/src/core/driver/database-driver.ts +19 -19
- package/src/core/driver/mssql-driver.ts +23 -23
- package/src/core/driver/mysql-driver.ts +23 -23
- package/src/core/driver/postgres-driver.ts +23 -23
- package/src/core/driver/sqlite-driver.ts +23 -23
- package/src/core/execution/db-executor.ts +92 -92
- package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
- package/src/core/execution/executors/mssql-executor.ts +184 -184
- package/src/core/execution/executors/mysql-executor.ts +88 -88
- package/src/core/execution/executors/postgres-executor.ts +26 -26
- package/src/core/execution/executors/sqlite-executor.ts +42 -42
- package/src/core/execution/pooling/pool-types.ts +30 -30
- package/src/core/execution/pooling/pool.ts +275 -275
- package/src/core/functions/array.ts +35 -35
- package/src/core/functions/control-flow.ts +79 -79
- package/src/core/functions/datetime.ts +237 -237
- package/src/core/functions/json.ts +70 -70
- package/src/core/functions/numeric.ts +297 -297
- package/src/core/functions/text.ts +334 -334
- package/src/core/functions/types.ts +33 -33
- package/src/core/hydration/types.ts +51 -51
- package/src/core/sql/sql-operator-config.ts +39 -39
- package/src/core/sql/sql.ts +152 -152
- package/src/decorators/column-decorator.ts +87 -87
- package/src/decorators/decorator-metadata.ts +143 -143
- package/src/decorators/entity.ts +93 -93
- package/src/decorators/index.ts +39 -39
- package/src/decorators/relations.ts +231 -231
- package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
- package/src/decorators/transformers/transformer-decorators.ts +134 -134
- package/src/decorators/transformers/transformer-executor.ts +171 -171
- package/src/decorators/transformers/transformer-metadata.ts +73 -73
- package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
- package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
- package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
- package/src/decorators/validators/country-validator-registry.ts +64 -64
- package/src/decorators/validators/country-validators-decorators.ts +203 -203
- package/src/decorators/validators/country-validators.ts +105 -105
- package/src/decorators/validators/validator-metadata.ts +59 -59
- package/src/dto/dto-types.ts +226 -226
- package/src/dto/filter-types.ts +141 -141
- package/src/dto/index.ts +95 -95
- package/src/dto/openapi/generators/base.ts +29 -29
- package/src/dto/openapi/generators/column.ts +37 -37
- package/src/dto/openapi/generators/dto.ts +91 -91
- package/src/dto/openapi/generators/filter.ts +75 -75
- package/src/dto/openapi/generators/nested-dto.ts +618 -618
- package/src/dto/openapi/generators/pagination.ts +111 -111
- package/src/dto/openapi/generators/relation-filter.ts +228 -228
- package/src/dto/openapi/index.ts +12 -12
- package/src/dto/openapi/types.ts +101 -101
- package/src/dto/openapi/utilities.ts +95 -95
- package/src/dto/pagination-utils.ts +150 -150
- package/src/dto/transform.ts +197 -197
- package/src/index.ts +86 -86
- package/src/orm/als.ts +58 -58
- package/src/orm/domain-event-bus.ts +119 -119
- package/src/orm/entity-context.ts +86 -86
- package/src/orm/entity-materializer.ts +159 -159
- package/src/orm/entity-meta.ts +97 -97
- package/src/orm/entity-registry.ts +39 -39
- package/src/orm/entity-relation-cache.ts +39 -39
- package/src/orm/execute.ts +194 -194
- package/src/orm/execution-context.ts +18 -18
- package/src/orm/hydration-context.ts +26 -26
- package/src/orm/hydration.ts +112 -112
- package/src/orm/identity-map.ts +61 -61
- package/src/orm/interceptor-pipeline.ts +16 -16
- package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
- package/src/orm/lazy-batch/belongs-to.ts +108 -108
- package/src/orm/lazy-batch/has-many.ts +69 -69
- package/src/orm/lazy-batch/has-one.ts +68 -68
- package/src/orm/lazy-batch/shared.ts +125 -125
- package/src/orm/orm-session.ts +620 -620
- package/src/orm/orm.ts +34 -34
- package/src/orm/pooled-executor-factory.ts +121 -121
- package/src/orm/query-logger.ts +40 -40
- package/src/orm/relation-change-processor.ts +420 -420
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -156
- package/src/orm/relations/morph-one.ts +151 -151
- package/src/orm/relations/morph-to.ts +162 -162
- package/src/orm/runtime-types.ts +92 -92
- package/src/orm/save-graph.ts +561 -561
- package/src/orm/transaction-runner.ts +24 -24
- package/src/orm/unit-of-work.ts +426 -426
- package/src/query/index.ts +69 -69
- package/src/query/target.ts +46 -46
- package/src/query-builder/column-selector.ts +81 -81
- package/src/query-builder/delete.ts +141 -141
- package/src/query-builder/insert-query-state.ts +149 -149
- package/src/query-builder/insert.ts +117 -117
- package/src/query-builder/query-ast-service.ts +287 -287
- package/src/query-builder/query-resolution.ts +78 -78
- package/src/query-builder/raw-column-parser.ts +38 -38
- package/src/query-builder/relation-filter-utils.ts +153 -153
- package/src/query-builder/relation-join-planner.ts +10 -10
- package/src/query-builder/relation-manager.ts +87 -87
- package/src/query-builder/relation-projection-helper.ts +102 -102
- package/src/query-builder/relation-types.ts +9 -9
- package/src/query-builder/relation-utils.ts +15 -15
- package/src/query-builder/select/cte-facet.ts +40 -40
- package/src/query-builder/select/from-facet.ts +80 -80
- package/src/query-builder/select/join-facet.ts +62 -62
- package/src/query-builder/select/predicate-facet.ts +104 -104
- package/src/query-builder/select/projection-facet.ts +70 -70
- package/src/query-builder/select/relation-facet.ts +81 -81
- package/src/query-builder/select/setop-facet.ts +36 -36
- package/src/query-builder/select-helpers.ts +1 -1
- package/src/query-builder/select-query-builder-deps.ts +130 -130
- package/src/query-builder/select-query-state.ts +207 -207
- package/src/query-builder/update.ts +151 -151
- package/src/schema/column-types.ts +322 -322
- package/src/schema/table-guards.ts +37 -37
- package/src/schema/table.ts +281 -281
- package/src/tree/index.ts +13 -13
- package/src/tree/nested-set-strategy.ts +545 -545
- package/src/tree/tree-decorator.ts +397 -397
- package/src/tree/tree-manager.ts +754 -754
- package/src/tree/tree-query.ts +427 -427
- package/src/tree/tree-types.ts +299 -299
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
import type { PropertySanitizer, AutoTransformableValidator, AutoTransformResult, ValidationResult } from '../transformer-metadata.js';
|
|
2
|
-
|
|
3
|
-
// TrimTransformer
|
|
4
|
-
export class TrimTransformer implements PropertySanitizer<string> {
|
|
5
|
-
readonly name = 'trim';
|
|
6
|
-
|
|
7
|
-
constructor(private readonly options: { trimStart?: boolean; trimEnd?: boolean; trimAll?: boolean } = {}) {}
|
|
8
|
-
|
|
9
|
-
sanitize(value: string): string {
|
|
10
|
-
if (typeof value !== 'string') return value;
|
|
11
|
-
|
|
12
|
-
if (this.options.trimAll) {
|
|
13
|
-
return value.trim();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let result = value;
|
|
17
|
-
if (this.options.trimStart) {
|
|
18
|
-
result = result.trimStart();
|
|
19
|
-
}
|
|
20
|
-
if (this.options.trimEnd) {
|
|
21
|
-
result = result.trimEnd();
|
|
22
|
-
}
|
|
23
|
-
if (!this.options.trimStart && !this.options.trimEnd && !this.options.trimAll) {
|
|
24
|
-
result = result.trim();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// CaseTransformer
|
|
32
|
-
export class CaseTransformer implements PropertySanitizer<string> {
|
|
33
|
-
readonly name: string;
|
|
34
|
-
|
|
35
|
-
constructor(private readonly caseType: 'lower' | 'upper' | 'capitalize' | 'title') {
|
|
36
|
-
this.name = `case-${caseType}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
sanitize(value: string): string {
|
|
40
|
-
if (typeof value !== 'string') return value;
|
|
41
|
-
|
|
42
|
-
switch (this.caseType) {
|
|
43
|
-
case 'lower':
|
|
44
|
-
return value.toLowerCase();
|
|
45
|
-
case 'upper':
|
|
46
|
-
return value.toUpperCase();
|
|
47
|
-
case 'capitalize':
|
|
48
|
-
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
|
49
|
-
case 'title':
|
|
50
|
-
return value.split(' ').map(word =>
|
|
51
|
-
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
52
|
-
).join(' ');
|
|
53
|
-
default:
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// AlphanumericValidator
|
|
60
|
-
export class AlphanumericValidator implements AutoTransformableValidator<string> {
|
|
61
|
-
readonly name = 'alphanumeric';
|
|
62
|
-
|
|
63
|
-
constructor(private readonly options: { allowSpaces?: boolean; allowUnderscores?: boolean; allowHyphens?: boolean } = {}) {}
|
|
64
|
-
|
|
65
|
-
validate(value: string): ValidationResult {
|
|
66
|
-
if (typeof value !== 'string') {
|
|
67
|
-
return { isValid: false, error: 'Value must be a string' };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const pattern = new RegExp(
|
|
71
|
-
`^[a-zA-Z0-9${this.options.allowSpaces ? ' ' : ''}${this.options.allowUnderscores ? '_' : ''}${this.options.allowHyphens ? '-' : ''}]*$`
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
return pattern.test(value)
|
|
75
|
-
? { isValid: true }
|
|
76
|
-
: { isValid: false, error: 'Value must contain only alphanumeric characters' };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
autoTransform(value: string): AutoTransformResult<string> {
|
|
80
|
-
if (typeof value !== 'string') {
|
|
81
|
-
return { success: false };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let result = value;
|
|
85
|
-
result = result.replace(/[^a-zA-Z0-9]/g, char => {
|
|
86
|
-
if (char === ' ' && this.options.allowSpaces) return ' ';
|
|
87
|
-
if (char === '_' && this.options.allowUnderscores) return '_';
|
|
88
|
-
if (char === '-' && this.options.allowHyphens) return '-';
|
|
89
|
-
return '';
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
success: true,
|
|
94
|
-
correctedValue: result,
|
|
95
|
-
message: 'Removed non-alphanumeric characters'
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// EmailValidator
|
|
101
|
-
export class EmailValidator implements AutoTransformableValidator<string> {
|
|
102
|
-
readonly name = 'email';
|
|
103
|
-
|
|
104
|
-
constructor(private readonly options: { allowPlus?: boolean; requireTLD?: boolean } = {}) {}
|
|
105
|
-
|
|
106
|
-
validate(value: string): ValidationResult {
|
|
107
|
-
if (typeof value !== 'string') {
|
|
108
|
-
return { isValid: false, error: 'Value must be a string' };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Simple email regex - RFC 5322 is very complex, this covers most cases
|
|
112
|
-
const emailPattern = this.options.allowPlus
|
|
113
|
-
? /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
114
|
-
: /^[^\s@+]+@[^\s@]+\.[^\s@]+$/;
|
|
115
|
-
|
|
116
|
-
if (!emailPattern.test(value)) {
|
|
117
|
-
return { isValid: false, error: 'Value must be a valid email address' };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (this.options.requireTLD) {
|
|
121
|
-
const parts = value.split('.');
|
|
122
|
-
if (parts.length < 3 || parts[parts.length - 1].length < 2) {
|
|
123
|
-
return { isValid: false, error: 'Email must have a valid top-level domain' };
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { isValid: true };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
autoTransform(value: string): AutoTransformResult<string> {
|
|
131
|
-
if (typeof value !== 'string') {
|
|
132
|
-
return { success: false };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let result = value.trim().toLowerCase();
|
|
136
|
-
if (!this.options.allowPlus) {
|
|
137
|
-
result = result.replace(/\+.*@/, '@');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
success: true,
|
|
142
|
-
correctedValue: result,
|
|
143
|
-
message: 'Trimmed and lowercased email'
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// LengthValidator
|
|
149
|
-
export class LengthValidator implements AutoTransformableValidator<string> {
|
|
150
|
-
readonly name = 'length';
|
|
151
|
-
|
|
152
|
-
constructor(private readonly options: { min?: number; max?: number; exact?: number } = {}) {}
|
|
153
|
-
|
|
154
|
-
validate(value: string): ValidationResult {
|
|
155
|
-
if (typeof value !== 'string') {
|
|
156
|
-
return { isValid: false, error: 'Value must be a string' };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (this.options.exact !== undefined && value.length !== this.options.exact) {
|
|
160
|
-
return { isValid: false, error: `Value must be exactly ${this.options.exact} characters long` };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (this.options.min !== undefined && value.length < this.options.min) {
|
|
164
|
-
return { isValid: false, error: `Value must be at least ${this.options.min} characters long` };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (this.options.max !== undefined && value.length > this.options.max) {
|
|
168
|
-
return { isValid: false, error: `Value must be at most ${this.options.max} characters long` };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return { isValid: true };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
autoTransform(value: string): AutoTransformResult<string> {
|
|
175
|
-
if (typeof value !== 'string') {
|
|
176
|
-
return { success: false };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
let result = value;
|
|
180
|
-
|
|
181
|
-
if (this.options.max !== undefined && result.length > this.options.max) {
|
|
182
|
-
result = result.slice(0, this.options.max);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (this.options.min !== undefined && result.length < this.options.min) {
|
|
186
|
-
result = result.padEnd(this.options.min);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
success: true,
|
|
191
|
-
correctedValue: result,
|
|
192
|
-
message: 'Adjusted string length'
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// PatternValidator
|
|
198
|
-
export class PatternValidator implements AutoTransformableValidator<string> {
|
|
199
|
-
readonly name = 'pattern';
|
|
200
|
-
|
|
201
|
-
constructor(private readonly options: { pattern: RegExp; flags?: string; errorMessage?: string; replacement?: string } = { pattern: /.*/ }) {}
|
|
202
|
-
|
|
203
|
-
validate(value: string): ValidationResult {
|
|
204
|
-
if (typeof value !== 'string') {
|
|
205
|
-
return { isValid: false, error: 'Value must be a string' };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const pattern = new RegExp(this.options.pattern.source, this.options.flags);
|
|
209
|
-
|
|
210
|
-
if (!pattern.test(value)) {
|
|
211
|
-
return { isValid: false, error: this.options.errorMessage || 'Value does not match required pattern' };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return { isValid: true };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
autoTransform(value: string): AutoTransformResult<string> {
|
|
218
|
-
if (typeof value !== 'string' || !this.options.replacement) {
|
|
219
|
-
return { success: false };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const pattern = new RegExp(this.options.pattern.source, this.options.flags);
|
|
223
|
-
const result = value.replace(pattern, this.options.replacement);
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
success: true,
|
|
227
|
-
correctedValue: result,
|
|
228
|
-
message: 'Replaced pattern matches'
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
1
|
+
import type { PropertySanitizer, AutoTransformableValidator, AutoTransformResult, ValidationResult } from '../transformer-metadata.js';
|
|
2
|
+
|
|
3
|
+
// TrimTransformer
|
|
4
|
+
export class TrimTransformer implements PropertySanitizer<string> {
|
|
5
|
+
readonly name = 'trim';
|
|
6
|
+
|
|
7
|
+
constructor(private readonly options: { trimStart?: boolean; trimEnd?: boolean; trimAll?: boolean } = {}) {}
|
|
8
|
+
|
|
9
|
+
sanitize(value: string): string {
|
|
10
|
+
if (typeof value !== 'string') return value;
|
|
11
|
+
|
|
12
|
+
if (this.options.trimAll) {
|
|
13
|
+
return value.trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let result = value;
|
|
17
|
+
if (this.options.trimStart) {
|
|
18
|
+
result = result.trimStart();
|
|
19
|
+
}
|
|
20
|
+
if (this.options.trimEnd) {
|
|
21
|
+
result = result.trimEnd();
|
|
22
|
+
}
|
|
23
|
+
if (!this.options.trimStart && !this.options.trimEnd && !this.options.trimAll) {
|
|
24
|
+
result = result.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// CaseTransformer
|
|
32
|
+
export class CaseTransformer implements PropertySanitizer<string> {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
|
|
35
|
+
constructor(private readonly caseType: 'lower' | 'upper' | 'capitalize' | 'title') {
|
|
36
|
+
this.name = `case-${caseType}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
sanitize(value: string): string {
|
|
40
|
+
if (typeof value !== 'string') return value;
|
|
41
|
+
|
|
42
|
+
switch (this.caseType) {
|
|
43
|
+
case 'lower':
|
|
44
|
+
return value.toLowerCase();
|
|
45
|
+
case 'upper':
|
|
46
|
+
return value.toUpperCase();
|
|
47
|
+
case 'capitalize':
|
|
48
|
+
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
|
49
|
+
case 'title':
|
|
50
|
+
return value.split(' ').map(word =>
|
|
51
|
+
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
52
|
+
).join(' ');
|
|
53
|
+
default:
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// AlphanumericValidator
|
|
60
|
+
export class AlphanumericValidator implements AutoTransformableValidator<string> {
|
|
61
|
+
readonly name = 'alphanumeric';
|
|
62
|
+
|
|
63
|
+
constructor(private readonly options: { allowSpaces?: boolean; allowUnderscores?: boolean; allowHyphens?: boolean } = {}) {}
|
|
64
|
+
|
|
65
|
+
validate(value: string): ValidationResult {
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
return { isValid: false, error: 'Value must be a string' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pattern = new RegExp(
|
|
71
|
+
`^[a-zA-Z0-9${this.options.allowSpaces ? ' ' : ''}${this.options.allowUnderscores ? '_' : ''}${this.options.allowHyphens ? '-' : ''}]*$`
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return pattern.test(value)
|
|
75
|
+
? { isValid: true }
|
|
76
|
+
: { isValid: false, error: 'Value must contain only alphanumeric characters' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
autoTransform(value: string): AutoTransformResult<string> {
|
|
80
|
+
if (typeof value !== 'string') {
|
|
81
|
+
return { success: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let result = value;
|
|
85
|
+
result = result.replace(/[^a-zA-Z0-9]/g, char => {
|
|
86
|
+
if (char === ' ' && this.options.allowSpaces) return ' ';
|
|
87
|
+
if (char === '_' && this.options.allowUnderscores) return '_';
|
|
88
|
+
if (char === '-' && this.options.allowHyphens) return '-';
|
|
89
|
+
return '';
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
correctedValue: result,
|
|
95
|
+
message: 'Removed non-alphanumeric characters'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// EmailValidator
|
|
101
|
+
export class EmailValidator implements AutoTransformableValidator<string> {
|
|
102
|
+
readonly name = 'email';
|
|
103
|
+
|
|
104
|
+
constructor(private readonly options: { allowPlus?: boolean; requireTLD?: boolean } = {}) {}
|
|
105
|
+
|
|
106
|
+
validate(value: string): ValidationResult {
|
|
107
|
+
if (typeof value !== 'string') {
|
|
108
|
+
return { isValid: false, error: 'Value must be a string' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Simple email regex - RFC 5322 is very complex, this covers most cases
|
|
112
|
+
const emailPattern = this.options.allowPlus
|
|
113
|
+
? /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
114
|
+
: /^[^\s@+]+@[^\s@]+\.[^\s@]+$/;
|
|
115
|
+
|
|
116
|
+
if (!emailPattern.test(value)) {
|
|
117
|
+
return { isValid: false, error: 'Value must be a valid email address' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.options.requireTLD) {
|
|
121
|
+
const parts = value.split('.');
|
|
122
|
+
if (parts.length < 3 || parts[parts.length - 1].length < 2) {
|
|
123
|
+
return { isValid: false, error: 'Email must have a valid top-level domain' };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { isValid: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
autoTransform(value: string): AutoTransformResult<string> {
|
|
131
|
+
if (typeof value !== 'string') {
|
|
132
|
+
return { success: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let result = value.trim().toLowerCase();
|
|
136
|
+
if (!this.options.allowPlus) {
|
|
137
|
+
result = result.replace(/\+.*@/, '@');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
correctedValue: result,
|
|
143
|
+
message: 'Trimmed and lowercased email'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// LengthValidator
|
|
149
|
+
export class LengthValidator implements AutoTransformableValidator<string> {
|
|
150
|
+
readonly name = 'length';
|
|
151
|
+
|
|
152
|
+
constructor(private readonly options: { min?: number; max?: number; exact?: number } = {}) {}
|
|
153
|
+
|
|
154
|
+
validate(value: string): ValidationResult {
|
|
155
|
+
if (typeof value !== 'string') {
|
|
156
|
+
return { isValid: false, error: 'Value must be a string' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.options.exact !== undefined && value.length !== this.options.exact) {
|
|
160
|
+
return { isValid: false, error: `Value must be exactly ${this.options.exact} characters long` };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this.options.min !== undefined && value.length < this.options.min) {
|
|
164
|
+
return { isValid: false, error: `Value must be at least ${this.options.min} characters long` };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.options.max !== undefined && value.length > this.options.max) {
|
|
168
|
+
return { isValid: false, error: `Value must be at most ${this.options.max} characters long` };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { isValid: true };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
autoTransform(value: string): AutoTransformResult<string> {
|
|
175
|
+
if (typeof value !== 'string') {
|
|
176
|
+
return { success: false };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let result = value;
|
|
180
|
+
|
|
181
|
+
if (this.options.max !== undefined && result.length > this.options.max) {
|
|
182
|
+
result = result.slice(0, this.options.max);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.options.min !== undefined && result.length < this.options.min) {
|
|
186
|
+
result = result.padEnd(this.options.min);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
correctedValue: result,
|
|
192
|
+
message: 'Adjusted string length'
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// PatternValidator
|
|
198
|
+
export class PatternValidator implements AutoTransformableValidator<string> {
|
|
199
|
+
readonly name = 'pattern';
|
|
200
|
+
|
|
201
|
+
constructor(private readonly options: { pattern: RegExp; flags?: string; errorMessage?: string; replacement?: string } = { pattern: /.*/ }) {}
|
|
202
|
+
|
|
203
|
+
validate(value: string): ValidationResult {
|
|
204
|
+
if (typeof value !== 'string') {
|
|
205
|
+
return { isValid: false, error: 'Value must be a string' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const pattern = new RegExp(this.options.pattern.source, this.options.flags);
|
|
209
|
+
|
|
210
|
+
if (!pattern.test(value)) {
|
|
211
|
+
return { isValid: false, error: this.options.errorMessage || 'Value does not match required pattern' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { isValid: true };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
autoTransform(value: string): AutoTransformResult<string> {
|
|
218
|
+
if (typeof value !== 'string' || !this.options.replacement) {
|
|
219
|
+
return { success: false };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const pattern = new RegExp(this.options.pattern.source, this.options.flags);
|
|
223
|
+
const result = value.replace(pattern, this.options.replacement);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
correctedValue: result,
|
|
228
|
+
message: 'Replaced pattern matches'
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|