@simtlix/simfinity-js 2.1.0 → 2.2.0
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 +182 -3
- package/package.json +1 -1
- package/src/index.js +3 -0
- package/src/scalars.js +188 -0
- package/src/validators.js +250 -0
package/README.md
CHANGED
|
@@ -1101,9 +1101,117 @@ mutation {
|
|
|
1101
1101
|
|
|
1102
1102
|
## ✅ Validations
|
|
1103
1103
|
|
|
1104
|
-
###
|
|
1104
|
+
### Declarative Validation Helpers
|
|
1105
1105
|
|
|
1106
|
-
|
|
1106
|
+
Simfinity.js provides built-in validation helpers to simplify common validation patterns, eliminating verbose boilerplate code.
|
|
1107
|
+
|
|
1108
|
+
#### Using Validators
|
|
1109
|
+
|
|
1110
|
+
```javascript
|
|
1111
|
+
const { validators } = require('@simtlix/simfinity-js');
|
|
1112
|
+
|
|
1113
|
+
const PersonType = new GraphQLObjectType({
|
|
1114
|
+
name: 'Person',
|
|
1115
|
+
fields: () => ({
|
|
1116
|
+
id: { type: GraphQLID },
|
|
1117
|
+
name: {
|
|
1118
|
+
type: GraphQLString,
|
|
1119
|
+
extensions: {
|
|
1120
|
+
validations: validators.stringLength('Name', 2, 100)
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
email: {
|
|
1124
|
+
type: GraphQLString,
|
|
1125
|
+
extensions: {
|
|
1126
|
+
validations: validators.email()
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
website: {
|
|
1130
|
+
type: GraphQLString,
|
|
1131
|
+
extensions: {
|
|
1132
|
+
validations: validators.url()
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
age: {
|
|
1136
|
+
type: GraphQLInt,
|
|
1137
|
+
extensions: {
|
|
1138
|
+
validations: validators.numberRange('Age', 0, 120)
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
price: {
|
|
1142
|
+
type: GraphQLFloat,
|
|
1143
|
+
extensions: {
|
|
1144
|
+
validations: validators.positive('Price')
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
})
|
|
1148
|
+
});
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
#### Available Validators
|
|
1152
|
+
|
|
1153
|
+
**String Validators:**
|
|
1154
|
+
- `validators.stringLength(name, min, max)` - Validates string length with min/max bounds (required for CREATE)
|
|
1155
|
+
- `validators.maxLength(name, max)` - Validates maximum string length
|
|
1156
|
+
- `validators.pattern(name, regex, message)` - Validates against a regex pattern
|
|
1157
|
+
- `validators.email()` - Validates email format
|
|
1158
|
+
- `validators.url()` - Validates URL format
|
|
1159
|
+
|
|
1160
|
+
**Number Validators:**
|
|
1161
|
+
- `validators.numberRange(name, min, max)` - Validates number range
|
|
1162
|
+
- `validators.positive(name)` - Ensures number is positive
|
|
1163
|
+
|
|
1164
|
+
**Array Validators:**
|
|
1165
|
+
- `validators.arrayLength(name, maxItems, itemValidator)` - Validates array length and optionally each item
|
|
1166
|
+
|
|
1167
|
+
**Date Validators:**
|
|
1168
|
+
- `validators.dateFormat(name, format)` - Validates date format
|
|
1169
|
+
- `validators.futureDate(name)` - Ensures date is in the future
|
|
1170
|
+
|
|
1171
|
+
#### Validator Features
|
|
1172
|
+
|
|
1173
|
+
- **Automatic Operation Handling**: Validators work for both `CREATE` (save) and `UPDATE` operations
|
|
1174
|
+
- **Smart Validation**: For CREATE operations, values are required. For UPDATE operations, undefined/null values are allowed (field might not be updated)
|
|
1175
|
+
- **Consistent Error Messages**: All validators throw `SimfinityError` with appropriate messages
|
|
1176
|
+
|
|
1177
|
+
#### Example: Multiple Validators
|
|
1178
|
+
|
|
1179
|
+
```javascript
|
|
1180
|
+
const ProductType = new GraphQLObjectType({
|
|
1181
|
+
name: 'Product',
|
|
1182
|
+
fields: () => ({
|
|
1183
|
+
id: { type: GraphQLID },
|
|
1184
|
+
name: {
|
|
1185
|
+
type: GraphQLString,
|
|
1186
|
+
extensions: {
|
|
1187
|
+
validations: validators.stringLength('Product Name', 3, 200)
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
sku: {
|
|
1191
|
+
type: GraphQLString,
|
|
1192
|
+
extensions: {
|
|
1193
|
+
validations: validators.pattern('SKU', /^[A-Z0-9-]+$/, 'SKU must be uppercase alphanumeric with hyphens')
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
price: {
|
|
1197
|
+
type: GraphQLFloat,
|
|
1198
|
+
extensions: {
|
|
1199
|
+
validations: validators.positive('Price')
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
tags: {
|
|
1203
|
+
type: new GraphQLList(GraphQLString),
|
|
1204
|
+
extensions: {
|
|
1205
|
+
validations: validators.arrayLength('Tags', 10)
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
})
|
|
1209
|
+
});
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
### Field-Level Validations (Manual)
|
|
1213
|
+
|
|
1214
|
+
For custom validation logic, you can still write manual validators:
|
|
1107
1215
|
|
|
1108
1216
|
```javascript
|
|
1109
1217
|
const { SimfinityError } = require('@simtlix/simfinity-js');
|
|
@@ -1189,7 +1297,78 @@ const OrderType = new GraphQLObjectType({
|
|
|
1189
1297
|
|
|
1190
1298
|
### Custom Validated Scalar Types
|
|
1191
1299
|
|
|
1192
|
-
Create custom scalar types with built-in validation. The generated type names follow the pattern `{name}_{baseScalarTypeName}
|
|
1300
|
+
Create custom scalar types with built-in validation. The generated type names follow the pattern `{name}_{baseScalarTypeName}`.
|
|
1301
|
+
|
|
1302
|
+
#### Pre-built Scalars
|
|
1303
|
+
|
|
1304
|
+
Simfinity.js provides ready-to-use validated scalars for common patterns:
|
|
1305
|
+
|
|
1306
|
+
```javascript
|
|
1307
|
+
const { scalars } = require('@simtlix/simfinity-js');
|
|
1308
|
+
|
|
1309
|
+
const UserType = new GraphQLObjectType({
|
|
1310
|
+
name: 'User',
|
|
1311
|
+
fields: () => ({
|
|
1312
|
+
id: { type: GraphQLID },
|
|
1313
|
+
email: { type: scalars.EmailScalar }, // Type name: Email_String
|
|
1314
|
+
website: { type: scalars.URLScalar }, // Type name: URL_String
|
|
1315
|
+
age: { type: scalars.PositiveIntScalar }, // Type name: PositiveInt_Int
|
|
1316
|
+
price: { type: scalars.PositiveFloatScalar } // Type name: PositiveFloat_Float
|
|
1317
|
+
}),
|
|
1318
|
+
});
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
**Available Pre-built Scalars:**
|
|
1322
|
+
- `scalars.EmailScalar` - Validates email format (`Email_String`)
|
|
1323
|
+
- `scalars.URLScalar` - Validates URL format (`URL_String`)
|
|
1324
|
+
- `scalars.PositiveIntScalar` - Validates positive integers (`PositiveInt_Int`)
|
|
1325
|
+
- `scalars.PositiveFloatScalar` - Validates positive floats (`PositiveFloat_Float`)
|
|
1326
|
+
|
|
1327
|
+
#### Factory Functions for Custom Scalars
|
|
1328
|
+
|
|
1329
|
+
Create custom validated scalars with parameters:
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
const { scalars } = require('@simtlix/simfinity-js');
|
|
1333
|
+
|
|
1334
|
+
// Create a bounded string scalar (name length between 2-100 characters)
|
|
1335
|
+
const NameScalar = scalars.createBoundedStringScalar('Name', 2, 100);
|
|
1336
|
+
|
|
1337
|
+
// Create a bounded integer scalar (age between 0-120)
|
|
1338
|
+
const AgeScalar = scalars.createBoundedIntScalar('Age', 0, 120);
|
|
1339
|
+
|
|
1340
|
+
// Create a bounded float scalar (rating between 0-10)
|
|
1341
|
+
const RatingScalar = scalars.createBoundedFloatScalar('Rating', 0, 10);
|
|
1342
|
+
|
|
1343
|
+
// Create a pattern-based string scalar (phone number format)
|
|
1344
|
+
const PhoneScalar = scalars.createPatternStringScalar(
|
|
1345
|
+
'Phone',
|
|
1346
|
+
/^\+?[\d\s\-()]+$/,
|
|
1347
|
+
'Invalid phone number format'
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
// Use in your types
|
|
1351
|
+
const PersonType = new GraphQLObjectType({
|
|
1352
|
+
name: 'Person',
|
|
1353
|
+
fields: () => ({
|
|
1354
|
+
id: { type: GraphQLID },
|
|
1355
|
+
name: { type: NameScalar }, // Type name: Name_String
|
|
1356
|
+
age: { type: AgeScalar }, // Type name: Age_Int
|
|
1357
|
+
rating: { type: RatingScalar }, // Type name: Rating_Float
|
|
1358
|
+
phone: { type: PhoneScalar } // Type name: Phone_String
|
|
1359
|
+
}),
|
|
1360
|
+
});
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
**Available Factory Functions:**
|
|
1364
|
+
- `scalars.createBoundedStringScalar(name, min, max)` - String with length bounds
|
|
1365
|
+
- `scalars.createBoundedIntScalar(name, min, max)` - Integer with range validation
|
|
1366
|
+
- `scalars.createBoundedFloatScalar(name, min, max)` - Float with range validation
|
|
1367
|
+
- `scalars.createPatternStringScalar(name, pattern, message)` - String with regex pattern validation
|
|
1368
|
+
|
|
1369
|
+
#### Creating Custom Scalars Manually
|
|
1370
|
+
|
|
1371
|
+
You can also create custom scalars using `createValidatedScalar` directly:
|
|
1193
1372
|
|
|
1194
1373
|
```javascript
|
|
1195
1374
|
const { GraphQLString, GraphQLInt } = require('graphql');
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -2075,6 +2075,9 @@ export const addNoEndpointType = (gqltype) => {
|
|
|
2075
2075
|
|
|
2076
2076
|
export { createValidatedScalar };
|
|
2077
2077
|
|
|
2078
|
+
export { default as validators } from './validators.js';
|
|
2079
|
+
export { default as scalars } from './scalars.js';
|
|
2080
|
+
|
|
2078
2081
|
const createArgsForQuery = (argTypes) => {
|
|
2079
2082
|
const argsObject = {};
|
|
2080
2083
|
|
package/src/scalars.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLString, GraphQLInt, GraphQLFloat,
|
|
3
|
+
} from 'graphql';
|
|
4
|
+
import { createValidatedScalar } from './index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Email scalar - validates email format
|
|
8
|
+
* Type name: Email_String
|
|
9
|
+
*/
|
|
10
|
+
export const EmailScalar = createValidatedScalar(
|
|
11
|
+
'Email',
|
|
12
|
+
'A valid email address',
|
|
13
|
+
GraphQLString,
|
|
14
|
+
(value) => {
|
|
15
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16
|
+
if (!emailRegex.test(value)) {
|
|
17
|
+
throw new Error('Invalid email format');
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* URL scalar - validates URL format
|
|
24
|
+
* Type name: URL_String
|
|
25
|
+
*/
|
|
26
|
+
export const URLScalar = createValidatedScalar(
|
|
27
|
+
'URL',
|
|
28
|
+
'A valid URL',
|
|
29
|
+
GraphQLString,
|
|
30
|
+
(value) => {
|
|
31
|
+
try {
|
|
32
|
+
new URL(value);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error('Invalid URL format');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PositiveInt scalar - validates positive integers
|
|
41
|
+
* Type name: PositiveInt_Int
|
|
42
|
+
*/
|
|
43
|
+
export const PositiveIntScalar = createValidatedScalar(
|
|
44
|
+
'PositiveInt',
|
|
45
|
+
'A positive integer',
|
|
46
|
+
GraphQLInt,
|
|
47
|
+
(value) => {
|
|
48
|
+
if (value <= 0) {
|
|
49
|
+
throw new Error('Value must be positive');
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* PositiveFloat scalar - validates positive floats
|
|
56
|
+
* Type name: PositiveFloat_Float
|
|
57
|
+
*/
|
|
58
|
+
export const PositiveFloatScalar = createValidatedScalar(
|
|
59
|
+
'PositiveFloat',
|
|
60
|
+
'A positive float',
|
|
61
|
+
GraphQLFloat,
|
|
62
|
+
(value) => {
|
|
63
|
+
if (value <= 0) {
|
|
64
|
+
throw new Error('Value must be positive');
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Factory function to create a bounded string scalar
|
|
71
|
+
* @param {string} name - Name for the scalar
|
|
72
|
+
* @param {number} min - Minimum length
|
|
73
|
+
* @param {number} max - Maximum length
|
|
74
|
+
* @returns {GraphQLScalarType} A scalar type with length validation
|
|
75
|
+
*/
|
|
76
|
+
export const createBoundedStringScalar = (name, min, max) => {
|
|
77
|
+
return createValidatedScalar(
|
|
78
|
+
name,
|
|
79
|
+
`A string with length between ${min} and ${max} characters`,
|
|
80
|
+
GraphQLString,
|
|
81
|
+
(value) => {
|
|
82
|
+
if (typeof value !== 'string') {
|
|
83
|
+
throw new Error('Value must be a string');
|
|
84
|
+
}
|
|
85
|
+
if (min !== undefined && value.length < min) {
|
|
86
|
+
throw new Error(`String must be at least ${min} characters`);
|
|
87
|
+
}
|
|
88
|
+
if (max !== undefined && value.length > max) {
|
|
89
|
+
throw new Error(`String must be at most ${max} characters`);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Factory function to create a bounded integer scalar
|
|
97
|
+
* @param {string} name - Name for the scalar
|
|
98
|
+
* @param {number} min - Minimum value
|
|
99
|
+
* @param {number} max - Maximum value
|
|
100
|
+
* @returns {GraphQLScalarType} A scalar type with range validation
|
|
101
|
+
*/
|
|
102
|
+
export const createBoundedIntScalar = (name, min, max) => {
|
|
103
|
+
return createValidatedScalar(
|
|
104
|
+
name,
|
|
105
|
+
`An integer between ${min} and ${max}`,
|
|
106
|
+
GraphQLInt,
|
|
107
|
+
(value) => {
|
|
108
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
109
|
+
throw new Error('Value must be a number');
|
|
110
|
+
}
|
|
111
|
+
if (min !== undefined && value < min) {
|
|
112
|
+
throw new Error(`Value must be at least ${min}`);
|
|
113
|
+
}
|
|
114
|
+
if (max !== undefined && value > max) {
|
|
115
|
+
throw new Error(`Value must be at most ${max}`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Factory function to create a bounded float scalar
|
|
123
|
+
* @param {string} name - Name for the scalar
|
|
124
|
+
* @param {number} min - Minimum value
|
|
125
|
+
* @param {number} max - Maximum value
|
|
126
|
+
* @returns {GraphQLScalarType} A scalar type with range validation
|
|
127
|
+
*/
|
|
128
|
+
export const createBoundedFloatScalar = (name, min, max) => {
|
|
129
|
+
return createValidatedScalar(
|
|
130
|
+
name,
|
|
131
|
+
`A float between ${min} and ${max}`,
|
|
132
|
+
GraphQLFloat,
|
|
133
|
+
(value) => {
|
|
134
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
135
|
+
throw new Error('Value must be a number');
|
|
136
|
+
}
|
|
137
|
+
if (min !== undefined && value < min) {
|
|
138
|
+
throw new Error(`Value must be at least ${min}`);
|
|
139
|
+
}
|
|
140
|
+
if (max !== undefined && value > max) {
|
|
141
|
+
throw new Error(`Value must be at most ${max}`);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Factory function to create a regex pattern string scalar
|
|
149
|
+
* @param {string} name - Name for the scalar
|
|
150
|
+
* @param {RegExp|string} pattern - Regex pattern to validate against
|
|
151
|
+
* @param {string} message - Error message if validation fails
|
|
152
|
+
* @returns {GraphQLScalarType} A scalar type with pattern validation
|
|
153
|
+
*/
|
|
154
|
+
export const createPatternStringScalar = (name, pattern, message) => {
|
|
155
|
+
const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
|
|
156
|
+
const errorMessage = message || 'Value does not match required pattern';
|
|
157
|
+
|
|
158
|
+
return createValidatedScalar(
|
|
159
|
+
name,
|
|
160
|
+
`A string matching the pattern: ${pattern}`,
|
|
161
|
+
GraphQLString,
|
|
162
|
+
(value) => {
|
|
163
|
+
if (typeof value !== 'string') {
|
|
164
|
+
throw new Error('Value must be a string');
|
|
165
|
+
}
|
|
166
|
+
if (!regex.test(value)) {
|
|
167
|
+
throw new Error(errorMessage);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Export all scalars as an object for convenience
|
|
174
|
+
const scalars = {
|
|
175
|
+
// Pre-built scalars
|
|
176
|
+
EmailScalar,
|
|
177
|
+
URLScalar,
|
|
178
|
+
PositiveIntScalar,
|
|
179
|
+
PositiveFloatScalar,
|
|
180
|
+
// Factory functions
|
|
181
|
+
createBoundedStringScalar,
|
|
182
|
+
createBoundedIntScalar,
|
|
183
|
+
createBoundedFloatScalar,
|
|
184
|
+
createPatternStringScalar,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default scalars;
|
|
188
|
+
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import SimfinityError from './errors/simfinity.error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a validation object that works for both 'save' (CREATE) and 'update' (UPDATE) operations.
|
|
5
|
+
* The validators will be applied to both operations.
|
|
6
|
+
* For CREATE operations, the value must be provided and valid.
|
|
7
|
+
* For UPDATE operations, undefined/null values are allowed (field might not be updated),
|
|
8
|
+
* but if a value is provided, it must be valid.
|
|
9
|
+
*/
|
|
10
|
+
const createValidator = (validatorFn, required = false) => {
|
|
11
|
+
// Validator for CREATE operations - value is required if required=true
|
|
12
|
+
const validateCreate = async (typeName, fieldName, value, session) => {
|
|
13
|
+
if (required && (value === null || value === undefined)) {
|
|
14
|
+
throw new SimfinityError(`${fieldName} is required`, 'VALIDATION_ERROR', 400);
|
|
15
|
+
}
|
|
16
|
+
if (value !== null && value !== undefined) {
|
|
17
|
+
await validatorFn(typeName, fieldName, value, session);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Validator for UPDATE operations - value is optional
|
|
22
|
+
const validateUpdate = async (typeName, fieldName, value, session) => {
|
|
23
|
+
// Skip validation if value is not provided (field is not being updated)
|
|
24
|
+
if (value === null || value === undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// If value is provided, validate it
|
|
28
|
+
await validatorFn(typeName, fieldName, value, session);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const validatorCreate = { validate: validateCreate };
|
|
32
|
+
const validatorUpdate = { validate: validateUpdate };
|
|
33
|
+
|
|
34
|
+
// Return validations for both CREATE and UPDATE operations
|
|
35
|
+
// Also support 'save'/'update' for backward compatibility (though code uses CREATE/UPDATE)
|
|
36
|
+
return {
|
|
37
|
+
CREATE: [validatorCreate],
|
|
38
|
+
UPDATE: [validatorUpdate],
|
|
39
|
+
save: [validatorCreate], // For backward compatibility
|
|
40
|
+
update: [validatorUpdate], // For backward compatibility
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* String validators
|
|
46
|
+
*/
|
|
47
|
+
export const stringLength = (name, min, max) => {
|
|
48
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
49
|
+
if (typeof value !== 'string') {
|
|
50
|
+
throw new SimfinityError(`${name} must be a string`, 'VALIDATION_ERROR', 400);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (min !== undefined && value.length < min) {
|
|
54
|
+
throw new SimfinityError(`${name} must be at least ${min} characters`, 'VALIDATION_ERROR', 400);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (max !== undefined && value.length > max) {
|
|
58
|
+
throw new SimfinityError(`${name} must be at most ${max} characters`, 'VALIDATION_ERROR', 400);
|
|
59
|
+
}
|
|
60
|
+
}, true); // Required for CREATE operations
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const maxLength = (name, max) => {
|
|
64
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
65
|
+
if (typeof value !== 'string') {
|
|
66
|
+
throw new SimfinityError(`${name} must be a string`, 'VALIDATION_ERROR', 400);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (value.length > max) {
|
|
70
|
+
throw new SimfinityError(`${name} must be at most ${max} characters`, 'VALIDATION_ERROR', 400);
|
|
71
|
+
}
|
|
72
|
+
}, false); // Optional
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const pattern = (name, regex, message) => {
|
|
76
|
+
const regexObj = typeof regex === 'string' ? new RegExp(regex) : regex;
|
|
77
|
+
const errorMessage = message || `${name} format is invalid`;
|
|
78
|
+
|
|
79
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
80
|
+
if (typeof value !== 'string') {
|
|
81
|
+
throw new SimfinityError(`${name} must be a string`, 'VALIDATION_ERROR', 400);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!regexObj.test(value)) {
|
|
85
|
+
throw new SimfinityError(errorMessage, 'VALIDATION_ERROR', 400);
|
|
86
|
+
}
|
|
87
|
+
}, false); // Optional
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const email = () => {
|
|
91
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
92
|
+
|
|
93
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
94
|
+
if (typeof value !== 'string') {
|
|
95
|
+
throw new SimfinityError('Email must be a string', 'VALIDATION_ERROR', 400);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!emailRegex.test(value)) {
|
|
99
|
+
throw new SimfinityError('Invalid email format', 'VALIDATION_ERROR', 400);
|
|
100
|
+
}
|
|
101
|
+
}, false); // Optional
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const url = () => {
|
|
105
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
106
|
+
if (typeof value !== 'string') {
|
|
107
|
+
throw new SimfinityError('URL must be a string', 'VALIDATION_ERROR', 400);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Use URL constructor for better validation
|
|
112
|
+
new URL(value);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.log('Invalid URL format', e);
|
|
115
|
+
throw new SimfinityError('Invalid URL format', 'VALIDATION_ERROR', 400);
|
|
116
|
+
}
|
|
117
|
+
}, false); // Optional
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Number validators
|
|
122
|
+
*/
|
|
123
|
+
export const numberRange = (name, min, max) => {
|
|
124
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
125
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
126
|
+
throw new SimfinityError(`${name} must be a number`, 'VALIDATION_ERROR', 400);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (min !== undefined && value < min) {
|
|
130
|
+
throw new SimfinityError(`${name} must be at least ${min}`, 'VALIDATION_ERROR', 400);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (max !== undefined && value > max) {
|
|
134
|
+
throw new SimfinityError(`${name} must be at most ${max}`, 'VALIDATION_ERROR', 400);
|
|
135
|
+
}
|
|
136
|
+
}, false); // Optional
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const positive = (name) => {
|
|
140
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
141
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
142
|
+
throw new SimfinityError(`${name} must be a number`, 'VALIDATION_ERROR', 400);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (value <= 0) {
|
|
146
|
+
throw new SimfinityError(`${name} must be positive`, 'VALIDATION_ERROR', 400);
|
|
147
|
+
}
|
|
148
|
+
}, false); // Optional
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Array validators
|
|
153
|
+
*/
|
|
154
|
+
export const arrayLength = (name, maxItems, itemValidator) => {
|
|
155
|
+
return createValidator(async (typeName, fieldName, value, session) => {
|
|
156
|
+
if (!Array.isArray(value)) {
|
|
157
|
+
throw new SimfinityError(`${name} must be an array`, 'VALIDATION_ERROR', 400);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (maxItems !== undefined && value.length > maxItems) {
|
|
161
|
+
throw new SimfinityError(`${name} must have at most ${maxItems} items`, 'VALIDATION_ERROR', 400);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If itemValidator is provided, validate each item
|
|
165
|
+
if (itemValidator && Array.isArray(itemValidator)) {
|
|
166
|
+
for (let i = 0; i < value.length; i++) {
|
|
167
|
+
for (const validator of itemValidator) {
|
|
168
|
+
await validator.validate(typeName, fieldName, value[i], session);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}, false); // Optional
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Date validators
|
|
177
|
+
*/
|
|
178
|
+
export const dateFormat = (name, format) => {
|
|
179
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
180
|
+
// Handle Date objects, ISO strings, and timestamps
|
|
181
|
+
let date;
|
|
182
|
+
if (value instanceof Date) {
|
|
183
|
+
date = value;
|
|
184
|
+
} else if (typeof value === 'string') {
|
|
185
|
+
date = new Date(value);
|
|
186
|
+
} else if (typeof value === 'number') {
|
|
187
|
+
date = new Date(value);
|
|
188
|
+
} else {
|
|
189
|
+
throw new SimfinityError(`${name} must be a valid date`, 'VALIDATION_ERROR', 400);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isNaN(date.getTime())) {
|
|
193
|
+
throw new SimfinityError(`${name} must be a valid date`, 'VALIDATION_ERROR', 400);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// If format is provided, validate format
|
|
197
|
+
if (format && typeof value === 'string') {
|
|
198
|
+
// Simple format validation - can be enhanced
|
|
199
|
+
const formatRegex = /^\d{4}-\d{2}-\d{2}$/; // YYYY-MM-DD
|
|
200
|
+
if (format === 'YYYY-MM-DD' && !formatRegex.test(value)) {
|
|
201
|
+
throw new SimfinityError(`${name} must be in format ${format}`, 'VALIDATION_ERROR', 400);
|
|
202
|
+
}
|
|
203
|
+
// Add more format patterns as needed
|
|
204
|
+
}
|
|
205
|
+
}, false); // Optional
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const futureDate = (name) => {
|
|
209
|
+
return createValidator(async (typeName, fieldName, value) => {
|
|
210
|
+
let date;
|
|
211
|
+
if (value instanceof Date) {
|
|
212
|
+
date = value;
|
|
213
|
+
} else if (typeof value === 'string') {
|
|
214
|
+
date = new Date(value);
|
|
215
|
+
} else if (typeof value === 'number') {
|
|
216
|
+
date = new Date(value);
|
|
217
|
+
} else {
|
|
218
|
+
throw new SimfinityError(`${name} must be a valid date`, 'VALIDATION_ERROR', 400);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (isNaN(date.getTime())) {
|
|
222
|
+
throw new SimfinityError(`${name} must be a valid date`, 'VALIDATION_ERROR', 400);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (date <= new Date()) {
|
|
226
|
+
throw new SimfinityError(`${name} must be a future date`, 'VALIDATION_ERROR', 400);
|
|
227
|
+
}
|
|
228
|
+
}, false); // Optional
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Export all validators as an object
|
|
232
|
+
const validators = {
|
|
233
|
+
// String validators
|
|
234
|
+
stringLength,
|
|
235
|
+
maxLength,
|
|
236
|
+
pattern,
|
|
237
|
+
email,
|
|
238
|
+
url,
|
|
239
|
+
// Number validators
|
|
240
|
+
numberRange,
|
|
241
|
+
positive,
|
|
242
|
+
// Array validators
|
|
243
|
+
arrayLength,
|
|
244
|
+
// Date validators
|
|
245
|
+
dateFormat,
|
|
246
|
+
futureDate,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export default validators;
|
|
250
|
+
|