@omnifyjp/omnify 0.3.7 → 0.4.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.
- package/package.json +6 -6
- package/ts-dist/interface-generator.js +10 -2
- package/ts-dist/php/model-generator.js +3 -2
- package/ts-dist/php/type-mapper.js +92 -1
- package/ts-dist/types.d.ts +31 -2
- package/ts-dist/zod-generator.js +112 -3
- package/types/schema.d.ts +56 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"zod": "^3.24.0"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@omnifyjp/omnify-darwin-arm64": "0.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "0.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "0.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "0.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "0.
|
|
39
|
+
"@omnifyjp/omnify-darwin-arm64": "0.4.1",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "0.4.1",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "0.4.1",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "0.4.1",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "0.4.1"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -40,8 +40,16 @@ const PK_TYPE_MAP = {
|
|
|
40
40
|
Int: 'number',
|
|
41
41
|
BigInt: 'number',
|
|
42
42
|
Uuid: 'string',
|
|
43
|
+
Ulid: 'string',
|
|
43
44
|
String: 'string',
|
|
44
45
|
};
|
|
46
|
+
/** Extract IdType from the unified `id` field. Returns 'BigInt' as default. */
|
|
47
|
+
function getIdType(options) {
|
|
48
|
+
const id = options?.id;
|
|
49
|
+
if (typeof id === 'string')
|
|
50
|
+
return id;
|
|
51
|
+
return 'BigInt';
|
|
52
|
+
}
|
|
45
53
|
/** Gets TypeScript type for a property. */
|
|
46
54
|
export function getPropertyType(property, allSchemas) {
|
|
47
55
|
if (property.type === 'File') {
|
|
@@ -168,7 +176,7 @@ export function propertyToTSProperties(propertyName, property, schema, allSchema
|
|
|
168
176
|
// Determine FK type from target schema
|
|
169
177
|
let fkType = 'number';
|
|
170
178
|
const targetSchema = property.target ? allSchemas[property.target] : undefined;
|
|
171
|
-
const effectiveIdType = targetSchema?.options
|
|
179
|
+
const effectiveIdType = getIdType(targetSchema?.options);
|
|
172
180
|
if (effectiveIdType === 'Uuid' || effectiveIdType === 'String') {
|
|
173
181
|
fkType = 'string';
|
|
174
182
|
}
|
|
@@ -226,7 +234,7 @@ export function schemaToInterface(schema, allSchemas, options) {
|
|
|
226
234
|
const allSchemaNames = new Set(Object.keys(allSchemas).filter(name => allSchemas[name]?.kind !== 'enum'));
|
|
227
235
|
// ID property
|
|
228
236
|
if (schema.options?.id !== false) {
|
|
229
|
-
const pkType = (schema.options
|
|
237
|
+
const pkType = getIdType(schema.options);
|
|
230
238
|
properties.push({
|
|
231
239
|
name: 'id',
|
|
232
240
|
type: PK_TYPE_MAP[pkType] ?? 'number',
|
|
@@ -42,9 +42,10 @@ function generateBaseModel(name, schema, reader, config) {
|
|
|
42
42
|
const relations = buildRelations(properties, propertyOrder, modelNamespace);
|
|
43
43
|
const accessors = buildAccessors(expandedProperties);
|
|
44
44
|
const primaryKey = options['primaryKey'] ?? 'id';
|
|
45
|
-
const
|
|
45
|
+
const rawId = options.id;
|
|
46
|
+
const idType = typeof rawId === 'string' ? rawId : 'BigInt';
|
|
46
47
|
let keyTypeSection = '';
|
|
47
|
-
if (idType === 'Uuid' || idType === 'String') {
|
|
48
|
+
if (idType === 'Uuid' || idType === 'Ulid' || idType === 'String') {
|
|
48
49
|
keyTypeSection = `
|
|
49
50
|
/**
|
|
50
51
|
* The type of the primary key.
|
|
@@ -63,13 +63,25 @@ export function toPhpDocType(type, nullable = false) {
|
|
|
63
63
|
}
|
|
64
64
|
return nullable ? `${phpType}|null` : phpType;
|
|
65
65
|
}
|
|
66
|
+
/** Type categories for rule applicability. */
|
|
67
|
+
const STRING_TYPES = new Set([
|
|
68
|
+
'String', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
69
|
+
'Slug', 'Url', 'Phone', 'Uuid',
|
|
70
|
+
]);
|
|
71
|
+
const NUMERIC_TYPES = new Set(['Int', 'BigInt', 'TinyInt', 'Float', 'Decimal']);
|
|
72
|
+
const ARRAY_TYPES = new Set(['Json']);
|
|
66
73
|
/** Generate validation rules for a property (Store request). */
|
|
67
74
|
export function toStoreRules(property, tableName) {
|
|
68
75
|
const type = property['type'] ?? 'String';
|
|
69
76
|
const nullable = property['nullable'] ?? false;
|
|
70
77
|
const unique = property['unique'] ?? false;
|
|
78
|
+
const vr = property['rules'];
|
|
71
79
|
const rules = [];
|
|
72
|
-
|
|
80
|
+
// required/nullable — rules.required can override
|
|
81
|
+
const isRequired = vr?.['required'] !== undefined
|
|
82
|
+
? vr['required']
|
|
83
|
+
: !nullable;
|
|
84
|
+
rules.push(isRequired ? 'required' : 'nullable');
|
|
73
85
|
switch (type) {
|
|
74
86
|
case 'String':
|
|
75
87
|
case 'Slug':
|
|
@@ -143,8 +155,87 @@ export function toStoreRules(property, tableName) {
|
|
|
143
155
|
if (unique && type !== 'Email') {
|
|
144
156
|
rules.push(`unique:${tableName}`);
|
|
145
157
|
}
|
|
158
|
+
// Merge explicit validation rules (additive/override)
|
|
159
|
+
if (vr) {
|
|
160
|
+
mergeValidationRules(rules, vr, type);
|
|
161
|
+
}
|
|
146
162
|
return rules;
|
|
147
163
|
}
|
|
164
|
+
/** Merge explicit ValidationRules into the inferred rules array. */
|
|
165
|
+
function mergeValidationRules(rules, vr, type) {
|
|
166
|
+
const isString = STRING_TYPES.has(type);
|
|
167
|
+
const isNumeric = NUMERIC_TYPES.has(type);
|
|
168
|
+
const isArray = ARRAY_TYPES.has(type);
|
|
169
|
+
if (isString) {
|
|
170
|
+
if (vr['minLength'] != null)
|
|
171
|
+
rules.push(`min:${vr['minLength']}`);
|
|
172
|
+
if (vr['maxLength'] != null) {
|
|
173
|
+
const idx = rules.findIndex(r => typeof r === 'string' && r.startsWith('max:'));
|
|
174
|
+
if (idx !== -1)
|
|
175
|
+
rules[idx] = `max:${vr['maxLength']}`;
|
|
176
|
+
else
|
|
177
|
+
rules.push(`max:${vr['maxLength']}`);
|
|
178
|
+
}
|
|
179
|
+
if (vr['url'] === true)
|
|
180
|
+
rules.push('url');
|
|
181
|
+
if (vr['uuid'] === true)
|
|
182
|
+
rules.push('uuid');
|
|
183
|
+
if (vr['ip'] === true)
|
|
184
|
+
rules.push('ip');
|
|
185
|
+
if (vr['ipv4'] === true)
|
|
186
|
+
rules.push('ipv4');
|
|
187
|
+
if (vr['ipv6'] === true)
|
|
188
|
+
rules.push('ipv6');
|
|
189
|
+
if (vr['alpha'] === true)
|
|
190
|
+
rules.push('alpha');
|
|
191
|
+
if (vr['alphaNum'] === true)
|
|
192
|
+
rules.push('alpha_num');
|
|
193
|
+
if (vr['alphaDash'] === true)
|
|
194
|
+
rules.push('alpha_dash');
|
|
195
|
+
if (vr['numeric'] === true)
|
|
196
|
+
rules.push('numeric');
|
|
197
|
+
if (vr['lowercase'] === true)
|
|
198
|
+
rules.push('lowercase');
|
|
199
|
+
if (vr['uppercase'] === true)
|
|
200
|
+
rules.push('uppercase');
|
|
201
|
+
if (vr['digits'] != null)
|
|
202
|
+
rules.push(`digits:${vr['digits']}`);
|
|
203
|
+
if (vr['digitsBetween'] != null) {
|
|
204
|
+
const [a, b] = vr['digitsBetween'];
|
|
205
|
+
rules.push(`digits_between:${a},${b}`);
|
|
206
|
+
}
|
|
207
|
+
if (vr['startsWith'] != null) {
|
|
208
|
+
const v = vr['startsWith'];
|
|
209
|
+
rules.push(`starts_with:${Array.isArray(v) ? v.join(',') : v}`);
|
|
210
|
+
}
|
|
211
|
+
if (vr['endsWith'] != null) {
|
|
212
|
+
const v = vr['endsWith'];
|
|
213
|
+
rules.push(`ends_with:${Array.isArray(v) ? v.join(',') : v}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (isNumeric) {
|
|
217
|
+
if (vr['min'] != null)
|
|
218
|
+
rules.push(`min:${vr['min']}`);
|
|
219
|
+
if (vr['max'] != null)
|
|
220
|
+
rules.push(`max:${vr['max']}`);
|
|
221
|
+
if (vr['between'] != null) {
|
|
222
|
+
const [a, b] = vr['between'];
|
|
223
|
+
rules.push(`between:${a},${b}`);
|
|
224
|
+
}
|
|
225
|
+
if (vr['gt'] != null)
|
|
226
|
+
rules.push(`gt:${vr['gt']}`);
|
|
227
|
+
if (vr['lt'] != null)
|
|
228
|
+
rules.push(`lt:${vr['lt']}`);
|
|
229
|
+
if (vr['multipleOf'] != null)
|
|
230
|
+
rules.push(`multiple_of:${vr['multipleOf']}`);
|
|
231
|
+
}
|
|
232
|
+
if (isArray) {
|
|
233
|
+
if (vr['arrayMin'] != null)
|
|
234
|
+
rules.push(`min:${vr['arrayMin']}`);
|
|
235
|
+
if (vr['arrayMax'] != null)
|
|
236
|
+
rules.push(`max:${vr['arrayMax']}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
148
239
|
/** Generate validation rules for a property (Update request). */
|
|
149
240
|
export function toUpdateRules(property, tableName, modelRouteParam) {
|
|
150
241
|
let rules = toStoreRules(property, tableName);
|
package/ts-dist/types.d.ts
CHANGED
|
@@ -57,8 +57,7 @@ export interface ExpandedColumn {
|
|
|
57
57
|
}
|
|
58
58
|
/** Schema options. */
|
|
59
59
|
export interface SchemaOptions {
|
|
60
|
-
readonly id?: boolean;
|
|
61
|
-
readonly idType?: string;
|
|
60
|
+
readonly id?: boolean | string;
|
|
62
61
|
readonly timestamps?: boolean;
|
|
63
62
|
readonly softDelete?: boolean;
|
|
64
63
|
readonly hidden?: boolean;
|
|
@@ -81,6 +80,35 @@ export interface SchemaDefinition {
|
|
|
81
80
|
readonly values?: readonly EnumValueDefinition[];
|
|
82
81
|
readonly pivotFor?: readonly string[];
|
|
83
82
|
}
|
|
83
|
+
/** Application-level validation rules. Single source of truth for both Laravel validation and Zod schemas. */
|
|
84
|
+
export interface ValidationRules {
|
|
85
|
+
readonly required?: boolean;
|
|
86
|
+
readonly minLength?: number;
|
|
87
|
+
readonly maxLength?: number;
|
|
88
|
+
readonly url?: boolean;
|
|
89
|
+
readonly uuid?: boolean;
|
|
90
|
+
readonly ip?: boolean;
|
|
91
|
+
readonly ipv4?: boolean;
|
|
92
|
+
readonly ipv6?: boolean;
|
|
93
|
+
readonly alpha?: boolean;
|
|
94
|
+
readonly alphaNum?: boolean;
|
|
95
|
+
readonly alphaDash?: boolean;
|
|
96
|
+
readonly numeric?: boolean;
|
|
97
|
+
readonly digits?: number;
|
|
98
|
+
readonly digitsBetween?: readonly [number, number];
|
|
99
|
+
readonly startsWith?: string | readonly string[];
|
|
100
|
+
readonly endsWith?: string | readonly string[];
|
|
101
|
+
readonly lowercase?: boolean;
|
|
102
|
+
readonly uppercase?: boolean;
|
|
103
|
+
readonly min?: number;
|
|
104
|
+
readonly max?: number;
|
|
105
|
+
readonly between?: readonly [number, number];
|
|
106
|
+
readonly gt?: number;
|
|
107
|
+
readonly lt?: number;
|
|
108
|
+
readonly multipleOf?: number;
|
|
109
|
+
readonly arrayMin?: number;
|
|
110
|
+
readonly arrayMax?: number;
|
|
111
|
+
}
|
|
84
112
|
/** Property definition within a schema. */
|
|
85
113
|
export interface PropertyDefinition {
|
|
86
114
|
readonly type: string;
|
|
@@ -100,6 +128,7 @@ export interface PropertyDefinition {
|
|
|
100
128
|
readonly precision?: number;
|
|
101
129
|
readonly scale?: number;
|
|
102
130
|
readonly pattern?: string;
|
|
131
|
+
readonly rules?: ValidationRules;
|
|
103
132
|
readonly enum?: string | readonly string[];
|
|
104
133
|
readonly relation?: string;
|
|
105
134
|
readonly target?: string;
|
package/ts-dist/zod-generator.js
CHANGED
|
@@ -26,11 +26,112 @@ function getMultiLocaleDisplayName(value, locales, fallbackLocale, defaultValue)
|
|
|
26
26
|
}
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
|
+
/** Type categories for Zod rule applicability. */
|
|
30
|
+
const ZOD_STRING_TYPES = new Set([
|
|
31
|
+
'String', 'Email', 'Password', 'Text', 'MediumText', 'LongText',
|
|
32
|
+
'Slug', 'Url', 'Phone', 'Uuid',
|
|
33
|
+
]);
|
|
34
|
+
const ZOD_NUMERIC_TYPES = new Set(['Int', 'BigInt', 'TinyInt', 'Float', 'Decimal']);
|
|
35
|
+
/** Replace an existing Zod method call or append it. */
|
|
36
|
+
function replaceZodMethod(schema, methodName, replacement) {
|
|
37
|
+
const regex = new RegExp(`\\.${methodName}\\([^)]*\\)`);
|
|
38
|
+
if (regex.test(schema)) {
|
|
39
|
+
return schema.replace(regex, replacement);
|
|
40
|
+
}
|
|
41
|
+
return schema + replacement;
|
|
42
|
+
}
|
|
43
|
+
/** Apply validation rules to a Zod schema string (additive/override). */
|
|
44
|
+
function applyZodRules(schema, rules, type) {
|
|
45
|
+
const isString = ZOD_STRING_TYPES.has(type);
|
|
46
|
+
const isNumeric = ZOD_NUMERIC_TYPES.has(type);
|
|
47
|
+
if (isString) {
|
|
48
|
+
if (rules.minLength != null) {
|
|
49
|
+
schema = replaceZodMethod(schema, 'min', `.min(${rules.minLength})`);
|
|
50
|
+
}
|
|
51
|
+
if (rules.maxLength != null) {
|
|
52
|
+
schema = replaceZodMethod(schema, 'max', `.max(${rules.maxLength})`);
|
|
53
|
+
}
|
|
54
|
+
if (rules.url)
|
|
55
|
+
schema += '.url()';
|
|
56
|
+
if (rules.uuid)
|
|
57
|
+
schema += '.uuid()';
|
|
58
|
+
if (rules.ip)
|
|
59
|
+
schema += '.ip()';
|
|
60
|
+
if (rules.ipv4)
|
|
61
|
+
schema += `.ip({ version: "v4" })`;
|
|
62
|
+
if (rules.ipv6)
|
|
63
|
+
schema += `.ip({ version: "v6" })`;
|
|
64
|
+
if (rules.alpha)
|
|
65
|
+
schema += `.regex(/^[a-zA-Z]+$/)`;
|
|
66
|
+
if (rules.alphaNum)
|
|
67
|
+
schema += `.regex(/^[a-zA-Z0-9]+$/)`;
|
|
68
|
+
if (rules.alphaDash)
|
|
69
|
+
schema += `.regex(/^[a-zA-Z0-9_-]+$/)`;
|
|
70
|
+
if (rules.numeric)
|
|
71
|
+
schema += `.regex(/^[0-9]+$/)`;
|
|
72
|
+
if (rules.lowercase)
|
|
73
|
+
schema += `.regex(/^[^A-Z]*$/)`;
|
|
74
|
+
if (rules.uppercase)
|
|
75
|
+
schema += `.regex(/^[^a-z]*$/)`;
|
|
76
|
+
if (rules.digits != null) {
|
|
77
|
+
schema += `.length(${rules.digits}).regex(/^[0-9]+$/)`;
|
|
78
|
+
}
|
|
79
|
+
if (rules.digitsBetween != null) {
|
|
80
|
+
const [a, b] = rules.digitsBetween;
|
|
81
|
+
schema = replaceZodMethod(schema, 'min', `.min(${a})`);
|
|
82
|
+
schema = replaceZodMethod(schema, 'max', `.max(${b})`);
|
|
83
|
+
schema += `.regex(/^[0-9]+$/)`;
|
|
84
|
+
}
|
|
85
|
+
if (rules.startsWith != null) {
|
|
86
|
+
const v = rules.startsWith;
|
|
87
|
+
if (Array.isArray(v)) {
|
|
88
|
+
const escaped = v.map(s => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'));
|
|
89
|
+
schema += `.regex(/^(${escaped.join('|')})/)`;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
schema += `.startsWith('${v}')`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (rules.endsWith != null) {
|
|
96
|
+
const v = rules.endsWith;
|
|
97
|
+
if (Array.isArray(v)) {
|
|
98
|
+
const escaped = v.map(s => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'));
|
|
99
|
+
schema += `.regex(/(${escaped.join('|')})$/)`;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
schema += `.endsWith('${v}')`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (isNumeric) {
|
|
107
|
+
if (rules.min != null) {
|
|
108
|
+
schema = replaceZodMethod(schema, 'gte', `.gte(${rules.min})`);
|
|
109
|
+
}
|
|
110
|
+
if (rules.max != null) {
|
|
111
|
+
schema = replaceZodMethod(schema, 'lte', `.lte(${rules.max})`);
|
|
112
|
+
}
|
|
113
|
+
if (rules.between != null) {
|
|
114
|
+
const [a, b] = rules.between;
|
|
115
|
+
schema = replaceZodMethod(schema, 'gte', `.gte(${a})`);
|
|
116
|
+
schema = replaceZodMethod(schema, 'lte', `.lte(${b})`);
|
|
117
|
+
}
|
|
118
|
+
if (rules.gt != null)
|
|
119
|
+
schema += `.gt(${rules.gt})`;
|
|
120
|
+
if (rules.lt != null)
|
|
121
|
+
schema += `.lt(${rules.lt})`;
|
|
122
|
+
if (rules.multipleOf != null)
|
|
123
|
+
schema += `.multipleOf(${rules.multipleOf})`;
|
|
124
|
+
}
|
|
125
|
+
return schema;
|
|
126
|
+
}
|
|
29
127
|
/**
|
|
30
128
|
* Generate Zod schema string for a property type.
|
|
31
129
|
*/
|
|
32
130
|
function getZodSchemaForType(propDef, _fieldName, options) {
|
|
33
|
-
|
|
131
|
+
// rules.required overrides nullable inference
|
|
132
|
+
const isNullable = propDef.rules?.required !== undefined
|
|
133
|
+
? !propDef.rules.required
|
|
134
|
+
: (propDef.nullable ?? false);
|
|
34
135
|
let schema = '';
|
|
35
136
|
// Check for simple custom types
|
|
36
137
|
const simpleType = options.customTypes.simple[propDef.type];
|
|
@@ -39,6 +140,9 @@ function getZodSchemaForType(propDef, _fieldName, options) {
|
|
|
39
140
|
if (simpleType.length) {
|
|
40
141
|
schema += `.max(${simpleType.length})`;
|
|
41
142
|
}
|
|
143
|
+
if (propDef.rules) {
|
|
144
|
+
schema = applyZodRules(schema, propDef.rules, propDef.type);
|
|
145
|
+
}
|
|
42
146
|
if (isNullable) {
|
|
43
147
|
schema += '.optional().nullable()';
|
|
44
148
|
}
|
|
@@ -128,12 +232,17 @@ function getZodSchemaForType(propDef, _fieldName, options) {
|
|
|
128
232
|
default:
|
|
129
233
|
schema = 'z.string()';
|
|
130
234
|
}
|
|
131
|
-
|
|
132
|
-
|
|
235
|
+
// Apply validation rules (additive/override)
|
|
236
|
+
if (propDef.rules && schema) {
|
|
237
|
+
schema = applyZodRules(schema, propDef.rules, propDef.type);
|
|
133
238
|
}
|
|
239
|
+
// Pattern (before nullable so validators chain correctly)
|
|
134
240
|
if (propDef.pattern && schema) {
|
|
135
241
|
schema += `.regex(/${propDef.pattern}/)`;
|
|
136
242
|
}
|
|
243
|
+
if (isNullable && schema) {
|
|
244
|
+
schema += '.optional().nullable()';
|
|
245
|
+
}
|
|
137
246
|
return schema;
|
|
138
247
|
}
|
|
139
248
|
/**
|
package/types/schema.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export type ReferentialAction =
|
|
|
55
55
|
export type SchemaKind = "object" | "enum" | "partial" | "extend" | "pivot";
|
|
56
56
|
|
|
57
57
|
/** Primary key column type. */
|
|
58
|
-
export type IdType = "BigInt" | "Int" | "Uuid" | "String";
|
|
58
|
+
export type IdType = "BigInt" | "Int" | "Uuid" | "Ulid" | "String";
|
|
59
59
|
|
|
60
60
|
/** Database index type. */
|
|
61
61
|
export type IndexType = "btree" | "hash" | "fulltext" | "spatial" | "gin" | "gist";
|
|
@@ -97,36 +97,85 @@ export interface PivotFieldDefinition {
|
|
|
97
97
|
displayName?: LocalizedString;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Application-level validation rules. Single source of truth for both Laravel validation and Zod schemas.
|
|
102
|
+
* Rules are **additive** — generators infer base rules from type, `rules:` adds/overrides on top.
|
|
103
|
+
*
|
|
104
|
+
* **Mutually exclusive groups** (using more than one triggers a validation error):
|
|
105
|
+
* - `lowercase` + `uppercase`
|
|
106
|
+
* - `alpha` / `alphaNum` / `alphaDash` / `numeric` (pick one)
|
|
107
|
+
* - `url` + `uuid`
|
|
108
|
+
* - `min` + `gt` (both define lower bound)
|
|
109
|
+
* - `max` + `lt` (both define upper bound)
|
|
110
|
+
* - `between` + any of `min`/`max`/`gt`/`lt`
|
|
111
|
+
* - `digits` + `digitsBetween`
|
|
112
|
+
*
|
|
113
|
+
* **Redundancy warnings**:
|
|
114
|
+
* - `ip` + `ipv4` or `ip` + `ipv6` → ipv4/ipv6 is redundant
|
|
115
|
+
*
|
|
116
|
+
* **Cross-field**: `maxLength` vs property `length` — if both set and differ, omnify warns.
|
|
117
|
+
*/
|
|
101
118
|
export interface ValidationRules {
|
|
119
|
+
/** [All types] Override required/nullable inference. */
|
|
102
120
|
required?: boolean;
|
|
103
|
-
|
|
121
|
+
|
|
122
|
+
// ── String rules (apply to: String, Email, Password, Text, MediumText, LongText, Uuid) ──
|
|
123
|
+
|
|
124
|
+
/** Minimum string length. Must be >= 0. Must be <= maxLength when both set. */
|
|
104
125
|
minLength?: number;
|
|
126
|
+
/** Maximum string length. Must be >= 1. Overrides length-based max. */
|
|
105
127
|
maxLength?: number;
|
|
128
|
+
/** Must be valid URL. Mutually exclusive with uuid. */
|
|
106
129
|
url?: boolean;
|
|
130
|
+
/** Must be valid UUID. Mutually exclusive with url. */
|
|
107
131
|
uuid?: boolean;
|
|
132
|
+
/** Must be valid IP (v4 or v6). Makes ipv4/ipv6 redundant. */
|
|
108
133
|
ip?: boolean;
|
|
134
|
+
/** Must be valid IPv4. Redundant when ip is set. */
|
|
109
135
|
ipv4?: boolean;
|
|
136
|
+
/** Must be valid IPv6. Redundant when ip is set. */
|
|
110
137
|
ipv6?: boolean;
|
|
138
|
+
/** Only letters (a-z, A-Z). Mutually exclusive with alphaNum, alphaDash, numeric. */
|
|
111
139
|
alpha?: boolean;
|
|
140
|
+
/** Only letters and numbers. Mutually exclusive with alpha, alphaDash, numeric. */
|
|
112
141
|
alphaNum?: boolean;
|
|
142
|
+
/** Letters, numbers, dash, underscore. Mutually exclusive with alpha, alphaNum, numeric. */
|
|
113
143
|
alphaDash?: boolean;
|
|
144
|
+
/** Only digits (0-9) as string. Mutually exclusive with alpha, alphaNum, alphaDash. */
|
|
114
145
|
numeric?: boolean;
|
|
146
|
+
/** Exactly N digits. Must be >= 1. Mutually exclusive with digitsBetween. */
|
|
115
147
|
digits?: number;
|
|
148
|
+
/** Digit count between [min, max]. Both >= 1, min <= max. Mutually exclusive with digits. */
|
|
116
149
|
digitsBetween?: [number, number];
|
|
150
|
+
/** Must start with prefix(es). */
|
|
117
151
|
startsWith?: string | string[];
|
|
152
|
+
/** Must end with suffix(es). */
|
|
118
153
|
endsWith?: string | string[];
|
|
154
|
+
/** Must be entirely lowercase. Mutually exclusive with uppercase. */
|
|
119
155
|
lowercase?: boolean;
|
|
156
|
+
/** Must be entirely uppercase. Mutually exclusive with lowercase. */
|
|
120
157
|
uppercase?: boolean;
|
|
121
|
-
|
|
158
|
+
|
|
159
|
+
// ── Numeric rules (apply to: TinyInt, Int, BigInt, Float, Decimal) ──
|
|
160
|
+
|
|
161
|
+
/** Minimum value (inclusive). Must be <= max. Mutually exclusive with gt. Redundant with between. */
|
|
122
162
|
min?: number;
|
|
163
|
+
/** Maximum value (inclusive). Must be >= min. Mutually exclusive with lt. Redundant with between. */
|
|
123
164
|
max?: number;
|
|
165
|
+
/** Value between [min, max] inclusive. min <= max. Do not combine with min/max/gt/lt. */
|
|
124
166
|
between?: [number, number];
|
|
167
|
+
/** Greater than (exclusive). Must be < lt. Mutually exclusive with min. Redundant with between. */
|
|
125
168
|
gt?: number;
|
|
169
|
+
/** Less than (exclusive). Must be > gt. Mutually exclusive with max. Redundant with between. */
|
|
126
170
|
lt?: number;
|
|
171
|
+
/** Must be a multiple of value. Must be > 0. */
|
|
127
172
|
multipleOf?: number;
|
|
128
|
-
|
|
173
|
+
|
|
174
|
+
// ── Array rules (apply to: Json) ──
|
|
175
|
+
|
|
176
|
+
/** Minimum items. Must be >= 0. Must be <= arrayMax when both set. */
|
|
129
177
|
arrayMin?: number;
|
|
178
|
+
/** Maximum items. Must be >= 1. Must be >= arrayMin when both set. */
|
|
130
179
|
arrayMax?: number;
|
|
131
180
|
}
|
|
132
181
|
|
|
@@ -194,9 +243,8 @@ export interface IndexDefinition {
|
|
|
194
243
|
|
|
195
244
|
/** Schema-level configuration options. */
|
|
196
245
|
export interface SchemaOptions {
|
|
197
|
-
/**
|
|
198
|
-
id?: boolean;
|
|
199
|
-
idType?: IdType;
|
|
246
|
+
/** ID column: true (BigInt), false (no ID), or type string (BigInt, Int, Uuid, String). */
|
|
247
|
+
id?: boolean | IdType;
|
|
200
248
|
/** Custom primary key — single column or composite. */
|
|
201
249
|
primaryKey?: string | string[];
|
|
202
250
|
timestamps?: boolean;
|
|
@@ -204,7 +252,6 @@ export interface SchemaOptions {
|
|
|
204
252
|
/** Unique constraints — column list or array of column lists. */
|
|
205
253
|
unique?: string[] | string[][];
|
|
206
254
|
indexes?: IndexDefinition[];
|
|
207
|
-
translations?: boolean;
|
|
208
255
|
tableName?: string;
|
|
209
256
|
authenticatable?: boolean;
|
|
210
257
|
hidden?: boolean;
|