@gunubin/vorm-form 0.2.0 → 0.3.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/dist/index.cjs +161 -18
- package/dist/index.d.cts +45 -20
- package/dist/index.d.ts +45 -20
- package/dist/index.js +157 -17
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -21,15 +21,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
buildOutputValues: () => buildOutputValues,
|
|
24
|
+
createArrayField: () => createArrayField,
|
|
24
25
|
createField: () => createField,
|
|
25
26
|
createFormSchema: () => createFormSchema,
|
|
26
|
-
|
|
27
|
+
isArrayFieldSchema: () => isArrayFieldSchema,
|
|
28
|
+
parseFieldPath: () => parseFieldPath,
|
|
27
29
|
resolveMessage: () => resolveMessage,
|
|
30
|
+
validateArrayField: () => validateArrayField,
|
|
28
31
|
validateField: () => validateField,
|
|
29
32
|
validateForm: () => validateForm
|
|
30
33
|
});
|
|
31
34
|
module.exports = __toCommonJS(index_exports);
|
|
32
35
|
|
|
36
|
+
// src/types.ts
|
|
37
|
+
function isArrayFieldSchema(s) {
|
|
38
|
+
return "__array" in s && s.__array === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
// src/create-field.ts
|
|
34
42
|
function createField(voOrConfig, options) {
|
|
35
43
|
if (voOrConfig && "create" in voOrConfig && typeof voOrConfig.create === "function") {
|
|
@@ -76,13 +84,56 @@ function mergeMessages(definition, factory) {
|
|
|
76
84
|
return { ...definition, ...factory };
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
// src/create-
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
// src/create-array-field.ts
|
|
88
|
+
function createArrayField(voOrConfig, options) {
|
|
89
|
+
if (voOrConfig && "create" in voOrConfig && typeof voOrConfig.create === "function") {
|
|
90
|
+
let factory2 = function(config2 = {}) {
|
|
91
|
+
return {
|
|
92
|
+
__array: true,
|
|
93
|
+
item: {
|
|
94
|
+
vo: voDef,
|
|
95
|
+
required: true,
|
|
96
|
+
messages: mergeMessages2(definitionMessages2, config2.messages),
|
|
97
|
+
rules: [...voDef.rules]
|
|
98
|
+
},
|
|
99
|
+
required: config2.required ?? false,
|
|
100
|
+
minLength: config2.minLength,
|
|
101
|
+
maxLength: config2.maxLength,
|
|
102
|
+
messages: mergeMessages2(definitionMessages2, config2.messages)
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
var factory = factory2;
|
|
106
|
+
const voDef = voOrConfig;
|
|
107
|
+
const definitionMessages2 = options?.messages;
|
|
108
|
+
return factory2;
|
|
109
|
+
}
|
|
110
|
+
const config = voOrConfig ?? {};
|
|
111
|
+
const definitionMessages = config.messages;
|
|
112
|
+
const definitionRules = config.rules ?? [];
|
|
113
|
+
function factory(factoryConfig = {}) {
|
|
114
|
+
return {
|
|
115
|
+
__array: true,
|
|
116
|
+
item: {
|
|
117
|
+
vo: null,
|
|
118
|
+
required: true,
|
|
119
|
+
messages: mergeMessages2(definitionMessages, factoryConfig.messages),
|
|
120
|
+
rules: [...definitionRules]
|
|
121
|
+
},
|
|
122
|
+
required: factoryConfig.required ?? false,
|
|
123
|
+
minLength: factoryConfig.minLength,
|
|
124
|
+
maxLength: factoryConfig.maxLength,
|
|
125
|
+
messages: mergeMessages2(definitionMessages, factoryConfig.messages)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return factory;
|
|
129
|
+
}
|
|
130
|
+
function mergeMessages2(definition, factory) {
|
|
131
|
+
if (!definition && !factory) return {};
|
|
132
|
+
if (!definition) return factory;
|
|
133
|
+
if (!factory) return definition;
|
|
134
|
+
if (typeof factory === "function") return factory;
|
|
135
|
+
if (typeof definition === "function") return factory;
|
|
136
|
+
return { ...definition, ...factory };
|
|
86
137
|
}
|
|
87
138
|
|
|
88
139
|
// src/resolve-message.ts
|
|
@@ -125,15 +176,59 @@ function validateField(value, fieldSchema, formMessages) {
|
|
|
125
176
|
return null;
|
|
126
177
|
}
|
|
127
178
|
|
|
179
|
+
// src/validate-array-field.ts
|
|
180
|
+
function validateArrayField(value, schema, formMessages) {
|
|
181
|
+
const errors = {};
|
|
182
|
+
if (schema.required) {
|
|
183
|
+
if (value === void 0 || value === null || Array.isArray(value) && value.length === 0) {
|
|
184
|
+
const code = "REQUIRED";
|
|
185
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
186
|
+
errors[""] = { code, message };
|
|
187
|
+
return errors;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
if (value === void 0 || value === null || Array.isArray(value) && value.length === 0) {
|
|
191
|
+
return errors;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
195
|
+
const code = "MIN_LENGTH";
|
|
196
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
197
|
+
errors[""] = { code, message };
|
|
198
|
+
return errors;
|
|
199
|
+
}
|
|
200
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
201
|
+
const code = "MAX_LENGTH";
|
|
202
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
203
|
+
errors[""] = { code, message };
|
|
204
|
+
return errors;
|
|
205
|
+
}
|
|
206
|
+
for (let i = 0; i < value.length; i++) {
|
|
207
|
+
const itemError = validateField(value[i], schema.item, formMessages);
|
|
208
|
+
if (itemError) {
|
|
209
|
+
errors[`[${i}]`] = itemError;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return errors;
|
|
213
|
+
}
|
|
214
|
+
|
|
128
215
|
// src/validate-form.ts
|
|
129
216
|
function validateForm(values, schema) {
|
|
130
217
|
const errors = {};
|
|
131
218
|
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
132
219
|
const value = values[name];
|
|
133
220
|
const formMessages = schema.messages?.[name];
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
221
|
+
if (isArrayFieldSchema(fieldSchema)) {
|
|
222
|
+
const arrayErrors = validateArrayField(value, fieldSchema, formMessages);
|
|
223
|
+
for (const [suffix, error] of Object.entries(arrayErrors)) {
|
|
224
|
+
const key = suffix === "" ? name : `${name}${suffix}`;
|
|
225
|
+
errors[key] = error;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
const error = validateField(value, fieldSchema, formMessages);
|
|
229
|
+
if (error) {
|
|
230
|
+
errors[name] = error;
|
|
231
|
+
}
|
|
137
232
|
}
|
|
138
233
|
}
|
|
139
234
|
if (Object.keys(errors).length > 0) {
|
|
@@ -153,22 +248,66 @@ function buildOutputValues(values, fields) {
|
|
|
153
248
|
const output = {};
|
|
154
249
|
for (const [name, fieldSchema] of Object.entries(fields)) {
|
|
155
250
|
let value = values[name];
|
|
251
|
+
if (isArrayFieldSchema(fieldSchema)) {
|
|
252
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
253
|
+
output[name] = void 0;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (fieldSchema.item.vo) {
|
|
257
|
+
output[name] = value.map((item) => fieldSchema.item.vo.create(item));
|
|
258
|
+
} else {
|
|
259
|
+
output[name] = [...value];
|
|
260
|
+
}
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const fs = fieldSchema;
|
|
156
264
|
const isEmpty = value === void 0 || value === null || value === "";
|
|
157
265
|
if (isEmpty) {
|
|
158
266
|
output[name] = void 0;
|
|
159
267
|
continue;
|
|
160
268
|
}
|
|
161
|
-
if (
|
|
162
|
-
value =
|
|
269
|
+
if (fs.vo) {
|
|
270
|
+
value = fs.vo.create(value);
|
|
163
271
|
}
|
|
164
272
|
output[name] = value;
|
|
165
273
|
}
|
|
166
274
|
return output;
|
|
167
275
|
}
|
|
168
276
|
|
|
169
|
-
// src/
|
|
170
|
-
function
|
|
171
|
-
|
|
277
|
+
// src/create-form-schema.ts
|
|
278
|
+
function parseFieldPath(key) {
|
|
279
|
+
const result = [];
|
|
280
|
+
let i = 0;
|
|
281
|
+
let current = "";
|
|
282
|
+
while (i < key.length) {
|
|
283
|
+
if (key[i] === "[") {
|
|
284
|
+
if (current) {
|
|
285
|
+
result.push(current);
|
|
286
|
+
current = "";
|
|
287
|
+
}
|
|
288
|
+
i++;
|
|
289
|
+
let num = "";
|
|
290
|
+
while (i < key.length && key[i] !== "]") {
|
|
291
|
+
num += key[i];
|
|
292
|
+
i++;
|
|
293
|
+
}
|
|
294
|
+
result.push(Number(num));
|
|
295
|
+
i++;
|
|
296
|
+
} else {
|
|
297
|
+
current += key[i];
|
|
298
|
+
i++;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (current) {
|
|
302
|
+
result.push(current);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
function createFormSchema(config) {
|
|
307
|
+
const schema = {
|
|
308
|
+
fields: config.fields,
|
|
309
|
+
messages: config.messages,
|
|
310
|
+
resolver: config.resolver,
|
|
172
311
|
"~standard": {
|
|
173
312
|
version: 1,
|
|
174
313
|
vendor: "vorm",
|
|
@@ -180,7 +319,7 @@ function formToStandardSchema(schema) {
|
|
|
180
319
|
const issues = errorEntries.map(
|
|
181
320
|
([field, error]) => ({
|
|
182
321
|
message: error.message || error.code,
|
|
183
|
-
path:
|
|
322
|
+
path: parseFieldPath(field)
|
|
184
323
|
})
|
|
185
324
|
);
|
|
186
325
|
return { issues };
|
|
@@ -194,14 +333,18 @@ function formToStandardSchema(schema) {
|
|
|
194
333
|
types: void 0
|
|
195
334
|
}
|
|
196
335
|
};
|
|
336
|
+
return schema;
|
|
197
337
|
}
|
|
198
338
|
// Annotate the CommonJS export names for ESM import in node:
|
|
199
339
|
0 && (module.exports = {
|
|
200
340
|
buildOutputValues,
|
|
341
|
+
createArrayField,
|
|
201
342
|
createField,
|
|
202
343
|
createFormSchema,
|
|
203
|
-
|
|
344
|
+
isArrayFieldSchema,
|
|
345
|
+
parseFieldPath,
|
|
204
346
|
resolveMessage,
|
|
347
|
+
validateArrayField,
|
|
205
348
|
validateField,
|
|
206
349
|
validateForm
|
|
207
350
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -15,26 +15,36 @@ type FieldSchema<T, TOutput, TRequired extends boolean, TCodes extends string =
|
|
|
15
15
|
parse?: (raw: string) => T;
|
|
16
16
|
format?: (value: T) => string;
|
|
17
17
|
};
|
|
18
|
+
type ArrayFieldSchema<T, TOutput, TRequired extends boolean, TCodes extends string = string> = {
|
|
19
|
+
__array: true;
|
|
20
|
+
item: FieldSchema<T, TOutput, true, TCodes>;
|
|
21
|
+
required: TRequired;
|
|
22
|
+
minLength?: number;
|
|
23
|
+
maxLength?: number;
|
|
24
|
+
messages: ErrorMessages<TCodes | 'REQUIRED' | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
25
|
+
};
|
|
26
|
+
type AnyFieldSchema = FieldSchema<any, any, boolean, any> | ArrayFieldSchema<any, any, boolean, any>;
|
|
27
|
+
declare function isArrayFieldSchema(s: AnyFieldSchema): s is ArrayFieldSchema<any, any, boolean, any>;
|
|
18
28
|
type FieldError = {
|
|
19
29
|
code: string;
|
|
20
30
|
message: string;
|
|
21
31
|
};
|
|
22
32
|
type FormErrors = Record<string, FieldError>;
|
|
23
|
-
type FormSchemaConfig<TFields extends Record<string,
|
|
33
|
+
type FormSchemaConfig<TFields extends Record<string, AnyFieldSchema>> = {
|
|
24
34
|
fields: TFields;
|
|
25
35
|
messages?: Record<string, ErrorMessages>;
|
|
26
36
|
resolver?: (values: FormInputValues<TFields>) => FormErrors | null;
|
|
27
37
|
};
|
|
28
|
-
type FormSchema<TFields extends Record<string,
|
|
38
|
+
type FormSchema<TFields extends Record<string, AnyFieldSchema>> = StandardSchemaV1<FormInputValues<TFields>, FormOutputValues<TFields>> & {
|
|
29
39
|
fields: TFields;
|
|
30
40
|
messages?: Record<string, ErrorMessages>;
|
|
31
41
|
resolver?: (values: FormInputValues<TFields>) => FormErrors | null;
|
|
32
42
|
};
|
|
33
|
-
type FormInputValues<TFields extends Record<string,
|
|
34
|
-
[K in keyof TFields]: TFields[K] extends FieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput : TInput | undefined : never;
|
|
43
|
+
type FormInputValues<TFields extends Record<string, AnyFieldSchema>> = {
|
|
44
|
+
[K in keyof TFields]: TFields[K] extends ArrayFieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput[] : TInput[] | undefined : TFields[K] extends FieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput : TInput | undefined : never;
|
|
35
45
|
};
|
|
36
|
-
type FormOutputValues<TFields extends Record<string,
|
|
37
|
-
[K in keyof TFields]: TFields[K] extends FieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput : TOutput | undefined : never;
|
|
46
|
+
type FormOutputValues<TFields extends Record<string, AnyFieldSchema>> = {
|
|
47
|
+
[K in keyof TFields]: TFields[K] extends ArrayFieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput[] : TOutput[] | undefined : TFields[K] extends FieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput : TOutput | undefined : never;
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
type FieldFactory<TInput, TOutput, TCodes extends string> = {
|
|
@@ -59,24 +69,39 @@ declare function createField<T, TOut = T, TCodes extends string = string>(config
|
|
|
59
69
|
format?: (value: T) => string;
|
|
60
70
|
}): FieldFactory<T, TOut, TCodes>;
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
type ArrayFieldFactory<TInput, TOutput, TCodes extends string> = {
|
|
73
|
+
(config: {
|
|
74
|
+
required: true;
|
|
75
|
+
minLength?: number;
|
|
76
|
+
maxLength?: number;
|
|
77
|
+
messages?: ErrorMessages<TCodes | 'REQUIRED' | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
78
|
+
}): ArrayFieldSchema<TInput, TOutput, true, TCodes>;
|
|
79
|
+
(config?: {
|
|
80
|
+
required?: false;
|
|
81
|
+
minLength?: number;
|
|
82
|
+
maxLength?: number;
|
|
83
|
+
messages?: ErrorMessages<TCodes | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
84
|
+
}): ArrayFieldSchema<TInput, TOutput, false, TCodes>;
|
|
85
|
+
};
|
|
86
|
+
declare function createArrayField<TInput, TOutput, TCodes extends string>(vo: VOLike<TInput, TOutput, TCodes>, options?: {
|
|
87
|
+
messages?: ErrorMessages<TCodes>;
|
|
88
|
+
}): ArrayFieldFactory<TInput, TOutput, TCodes>;
|
|
89
|
+
declare function createArrayField<T, TOut = T, TCodes extends string = string>(config?: {
|
|
90
|
+
rules?: ValidationRule<T, TCodes>[];
|
|
91
|
+
messages?: ErrorMessages<TCodes>;
|
|
92
|
+
}): ArrayFieldFactory<T, TOut, TCodes>;
|
|
93
|
+
|
|
94
|
+
declare function parseFieldPath(key: string): (string | number)[];
|
|
95
|
+
declare function createFormSchema<TFields extends Record<string, AnyFieldSchema>>(config: FormSchemaConfig<TFields>): FormSchema<TFields>;
|
|
63
96
|
|
|
64
97
|
declare function validateField<T>(value: T | undefined | null, fieldSchema: FieldSchema<T, any, boolean, any>, formMessages?: ErrorMessages): FieldError | null;
|
|
65
98
|
|
|
66
|
-
declare function
|
|
99
|
+
declare function validateArrayField(value: unknown[] | undefined | null, schema: ArrayFieldSchema<any, any, boolean, any>, formMessages?: ErrorMessages): Record<string, FieldError>;
|
|
67
100
|
|
|
68
|
-
declare function
|
|
101
|
+
declare function validateForm<TFields extends Record<string, AnyFieldSchema>>(values: FormInputValues<TFields>, schema: FormSchema<TFields>): FormErrors;
|
|
69
102
|
|
|
70
|
-
declare function
|
|
103
|
+
declare function buildOutputValues(values: Record<string, unknown>, fields: Record<string, AnyFieldSchema>): Record<string, unknown>;
|
|
71
104
|
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Wraps a FormSchema as a Standard Schema v1 compliant object.
|
|
75
|
-
*
|
|
76
|
-
* - Input: FormInputValues (plain types, e.g. { email: string; password: string })
|
|
77
|
-
* - Output: FormOutputValues (branded types, e.g. { email: Email; password: Password })
|
|
78
|
-
* - On failure, returns issues with field-level path segments
|
|
79
|
-
*/
|
|
80
|
-
declare function formToStandardSchema<TFields extends AnyFields>(schema: FormSchema<TFields>): StandardSchemaV1<FormInputValues<TFields>, FormOutputValues<TFields>>;
|
|
105
|
+
declare function resolveMessage(code: string, ...messageSources: (ErrorMessages | undefined)[]): string;
|
|
81
106
|
|
|
82
|
-
export { type ErrorMessageFn, type ErrorMessageMap, type ErrorMessages, type FieldError, type FieldSchema, type FormErrors, type FormInputValues, type FormOutputValues, type FormSchema, type FormSchemaConfig, buildOutputValues, createField, createFormSchema,
|
|
107
|
+
export { type AnyFieldSchema, type ArrayFieldSchema, type ErrorMessageFn, type ErrorMessageMap, type ErrorMessages, type FieldError, type FieldSchema, type FormErrors, type FormInputValues, type FormOutputValues, type FormSchema, type FormSchemaConfig, buildOutputValues, createArrayField, createField, createFormSchema, isArrayFieldSchema, parseFieldPath, resolveMessage, validateArrayField, validateField, validateForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,26 +15,36 @@ type FieldSchema<T, TOutput, TRequired extends boolean, TCodes extends string =
|
|
|
15
15
|
parse?: (raw: string) => T;
|
|
16
16
|
format?: (value: T) => string;
|
|
17
17
|
};
|
|
18
|
+
type ArrayFieldSchema<T, TOutput, TRequired extends boolean, TCodes extends string = string> = {
|
|
19
|
+
__array: true;
|
|
20
|
+
item: FieldSchema<T, TOutput, true, TCodes>;
|
|
21
|
+
required: TRequired;
|
|
22
|
+
minLength?: number;
|
|
23
|
+
maxLength?: number;
|
|
24
|
+
messages: ErrorMessages<TCodes | 'REQUIRED' | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
25
|
+
};
|
|
26
|
+
type AnyFieldSchema = FieldSchema<any, any, boolean, any> | ArrayFieldSchema<any, any, boolean, any>;
|
|
27
|
+
declare function isArrayFieldSchema(s: AnyFieldSchema): s is ArrayFieldSchema<any, any, boolean, any>;
|
|
18
28
|
type FieldError = {
|
|
19
29
|
code: string;
|
|
20
30
|
message: string;
|
|
21
31
|
};
|
|
22
32
|
type FormErrors = Record<string, FieldError>;
|
|
23
|
-
type FormSchemaConfig<TFields extends Record<string,
|
|
33
|
+
type FormSchemaConfig<TFields extends Record<string, AnyFieldSchema>> = {
|
|
24
34
|
fields: TFields;
|
|
25
35
|
messages?: Record<string, ErrorMessages>;
|
|
26
36
|
resolver?: (values: FormInputValues<TFields>) => FormErrors | null;
|
|
27
37
|
};
|
|
28
|
-
type FormSchema<TFields extends Record<string,
|
|
38
|
+
type FormSchema<TFields extends Record<string, AnyFieldSchema>> = StandardSchemaV1<FormInputValues<TFields>, FormOutputValues<TFields>> & {
|
|
29
39
|
fields: TFields;
|
|
30
40
|
messages?: Record<string, ErrorMessages>;
|
|
31
41
|
resolver?: (values: FormInputValues<TFields>) => FormErrors | null;
|
|
32
42
|
};
|
|
33
|
-
type FormInputValues<TFields extends Record<string,
|
|
34
|
-
[K in keyof TFields]: TFields[K] extends FieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput : TInput | undefined : never;
|
|
43
|
+
type FormInputValues<TFields extends Record<string, AnyFieldSchema>> = {
|
|
44
|
+
[K in keyof TFields]: TFields[K] extends ArrayFieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput[] : TInput[] | undefined : TFields[K] extends FieldSchema<infer TInput, any, infer TRequired, any> ? TRequired extends true ? TInput : TInput | undefined : never;
|
|
35
45
|
};
|
|
36
|
-
type FormOutputValues<TFields extends Record<string,
|
|
37
|
-
[K in keyof TFields]: TFields[K] extends FieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput : TOutput | undefined : never;
|
|
46
|
+
type FormOutputValues<TFields extends Record<string, AnyFieldSchema>> = {
|
|
47
|
+
[K in keyof TFields]: TFields[K] extends ArrayFieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput[] : TOutput[] | undefined : TFields[K] extends FieldSchema<any, infer TOutput, infer TRequired, any> ? TRequired extends true ? TOutput : TOutput | undefined : never;
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
type FieldFactory<TInput, TOutput, TCodes extends string> = {
|
|
@@ -59,24 +69,39 @@ declare function createField<T, TOut = T, TCodes extends string = string>(config
|
|
|
59
69
|
format?: (value: T) => string;
|
|
60
70
|
}): FieldFactory<T, TOut, TCodes>;
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
type ArrayFieldFactory<TInput, TOutput, TCodes extends string> = {
|
|
73
|
+
(config: {
|
|
74
|
+
required: true;
|
|
75
|
+
minLength?: number;
|
|
76
|
+
maxLength?: number;
|
|
77
|
+
messages?: ErrorMessages<TCodes | 'REQUIRED' | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
78
|
+
}): ArrayFieldSchema<TInput, TOutput, true, TCodes>;
|
|
79
|
+
(config?: {
|
|
80
|
+
required?: false;
|
|
81
|
+
minLength?: number;
|
|
82
|
+
maxLength?: number;
|
|
83
|
+
messages?: ErrorMessages<TCodes | 'MIN_LENGTH' | 'MAX_LENGTH'>;
|
|
84
|
+
}): ArrayFieldSchema<TInput, TOutput, false, TCodes>;
|
|
85
|
+
};
|
|
86
|
+
declare function createArrayField<TInput, TOutput, TCodes extends string>(vo: VOLike<TInput, TOutput, TCodes>, options?: {
|
|
87
|
+
messages?: ErrorMessages<TCodes>;
|
|
88
|
+
}): ArrayFieldFactory<TInput, TOutput, TCodes>;
|
|
89
|
+
declare function createArrayField<T, TOut = T, TCodes extends string = string>(config?: {
|
|
90
|
+
rules?: ValidationRule<T, TCodes>[];
|
|
91
|
+
messages?: ErrorMessages<TCodes>;
|
|
92
|
+
}): ArrayFieldFactory<T, TOut, TCodes>;
|
|
93
|
+
|
|
94
|
+
declare function parseFieldPath(key: string): (string | number)[];
|
|
95
|
+
declare function createFormSchema<TFields extends Record<string, AnyFieldSchema>>(config: FormSchemaConfig<TFields>): FormSchema<TFields>;
|
|
63
96
|
|
|
64
97
|
declare function validateField<T>(value: T | undefined | null, fieldSchema: FieldSchema<T, any, boolean, any>, formMessages?: ErrorMessages): FieldError | null;
|
|
65
98
|
|
|
66
|
-
declare function
|
|
99
|
+
declare function validateArrayField(value: unknown[] | undefined | null, schema: ArrayFieldSchema<any, any, boolean, any>, formMessages?: ErrorMessages): Record<string, FieldError>;
|
|
67
100
|
|
|
68
|
-
declare function
|
|
101
|
+
declare function validateForm<TFields extends Record<string, AnyFieldSchema>>(values: FormInputValues<TFields>, schema: FormSchema<TFields>): FormErrors;
|
|
69
102
|
|
|
70
|
-
declare function
|
|
103
|
+
declare function buildOutputValues(values: Record<string, unknown>, fields: Record<string, AnyFieldSchema>): Record<string, unknown>;
|
|
71
104
|
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Wraps a FormSchema as a Standard Schema v1 compliant object.
|
|
75
|
-
*
|
|
76
|
-
* - Input: FormInputValues (plain types, e.g. { email: string; password: string })
|
|
77
|
-
* - Output: FormOutputValues (branded types, e.g. { email: Email; password: Password })
|
|
78
|
-
* - On failure, returns issues with field-level path segments
|
|
79
|
-
*/
|
|
80
|
-
declare function formToStandardSchema<TFields extends AnyFields>(schema: FormSchema<TFields>): StandardSchemaV1<FormInputValues<TFields>, FormOutputValues<TFields>>;
|
|
105
|
+
declare function resolveMessage(code: string, ...messageSources: (ErrorMessages | undefined)[]): string;
|
|
81
106
|
|
|
82
|
-
export { type ErrorMessageFn, type ErrorMessageMap, type ErrorMessages, type FieldError, type FieldSchema, type FormErrors, type FormInputValues, type FormOutputValues, type FormSchema, type FormSchemaConfig, buildOutputValues, createField, createFormSchema,
|
|
107
|
+
export { type AnyFieldSchema, type ArrayFieldSchema, type ErrorMessageFn, type ErrorMessageMap, type ErrorMessages, type FieldError, type FieldSchema, type FormErrors, type FormInputValues, type FormOutputValues, type FormSchema, type FormSchemaConfig, buildOutputValues, createArrayField, createField, createFormSchema, isArrayFieldSchema, parseFieldPath, resolveMessage, validateArrayField, validateField, validateForm };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
function isArrayFieldSchema(s) {
|
|
3
|
+
return "__array" in s && s.__array === true;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
// src/create-field.ts
|
|
2
7
|
function createField(voOrConfig, options) {
|
|
3
8
|
if (voOrConfig && "create" in voOrConfig && typeof voOrConfig.create === "function") {
|
|
@@ -44,13 +49,56 @@ function mergeMessages(definition, factory) {
|
|
|
44
49
|
return { ...definition, ...factory };
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
// src/create-
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// src/create-array-field.ts
|
|
53
|
+
function createArrayField(voOrConfig, options) {
|
|
54
|
+
if (voOrConfig && "create" in voOrConfig && typeof voOrConfig.create === "function") {
|
|
55
|
+
let factory2 = function(config2 = {}) {
|
|
56
|
+
return {
|
|
57
|
+
__array: true,
|
|
58
|
+
item: {
|
|
59
|
+
vo: voDef,
|
|
60
|
+
required: true,
|
|
61
|
+
messages: mergeMessages2(definitionMessages2, config2.messages),
|
|
62
|
+
rules: [...voDef.rules]
|
|
63
|
+
},
|
|
64
|
+
required: config2.required ?? false,
|
|
65
|
+
minLength: config2.minLength,
|
|
66
|
+
maxLength: config2.maxLength,
|
|
67
|
+
messages: mergeMessages2(definitionMessages2, config2.messages)
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
var factory = factory2;
|
|
71
|
+
const voDef = voOrConfig;
|
|
72
|
+
const definitionMessages2 = options?.messages;
|
|
73
|
+
return factory2;
|
|
74
|
+
}
|
|
75
|
+
const config = voOrConfig ?? {};
|
|
76
|
+
const definitionMessages = config.messages;
|
|
77
|
+
const definitionRules = config.rules ?? [];
|
|
78
|
+
function factory(factoryConfig = {}) {
|
|
79
|
+
return {
|
|
80
|
+
__array: true,
|
|
81
|
+
item: {
|
|
82
|
+
vo: null,
|
|
83
|
+
required: true,
|
|
84
|
+
messages: mergeMessages2(definitionMessages, factoryConfig.messages),
|
|
85
|
+
rules: [...definitionRules]
|
|
86
|
+
},
|
|
87
|
+
required: factoryConfig.required ?? false,
|
|
88
|
+
minLength: factoryConfig.minLength,
|
|
89
|
+
maxLength: factoryConfig.maxLength,
|
|
90
|
+
messages: mergeMessages2(definitionMessages, factoryConfig.messages)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return factory;
|
|
94
|
+
}
|
|
95
|
+
function mergeMessages2(definition, factory) {
|
|
96
|
+
if (!definition && !factory) return {};
|
|
97
|
+
if (!definition) return factory;
|
|
98
|
+
if (!factory) return definition;
|
|
99
|
+
if (typeof factory === "function") return factory;
|
|
100
|
+
if (typeof definition === "function") return factory;
|
|
101
|
+
return { ...definition, ...factory };
|
|
54
102
|
}
|
|
55
103
|
|
|
56
104
|
// src/resolve-message.ts
|
|
@@ -93,15 +141,59 @@ function validateField(value, fieldSchema, formMessages) {
|
|
|
93
141
|
return null;
|
|
94
142
|
}
|
|
95
143
|
|
|
144
|
+
// src/validate-array-field.ts
|
|
145
|
+
function validateArrayField(value, schema, formMessages) {
|
|
146
|
+
const errors = {};
|
|
147
|
+
if (schema.required) {
|
|
148
|
+
if (value === void 0 || value === null || Array.isArray(value) && value.length === 0) {
|
|
149
|
+
const code = "REQUIRED";
|
|
150
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
151
|
+
errors[""] = { code, message };
|
|
152
|
+
return errors;
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
if (value === void 0 || value === null || Array.isArray(value) && value.length === 0) {
|
|
156
|
+
return errors;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
160
|
+
const code = "MIN_LENGTH";
|
|
161
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
162
|
+
errors[""] = { code, message };
|
|
163
|
+
return errors;
|
|
164
|
+
}
|
|
165
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
166
|
+
const code = "MAX_LENGTH";
|
|
167
|
+
const message = resolveMessage(code, formMessages, schema.messages);
|
|
168
|
+
errors[""] = { code, message };
|
|
169
|
+
return errors;
|
|
170
|
+
}
|
|
171
|
+
for (let i = 0; i < value.length; i++) {
|
|
172
|
+
const itemError = validateField(value[i], schema.item, formMessages);
|
|
173
|
+
if (itemError) {
|
|
174
|
+
errors[`[${i}]`] = itemError;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return errors;
|
|
178
|
+
}
|
|
179
|
+
|
|
96
180
|
// src/validate-form.ts
|
|
97
181
|
function validateForm(values, schema) {
|
|
98
182
|
const errors = {};
|
|
99
183
|
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
100
184
|
const value = values[name];
|
|
101
185
|
const formMessages = schema.messages?.[name];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
186
|
+
if (isArrayFieldSchema(fieldSchema)) {
|
|
187
|
+
const arrayErrors = validateArrayField(value, fieldSchema, formMessages);
|
|
188
|
+
for (const [suffix, error] of Object.entries(arrayErrors)) {
|
|
189
|
+
const key = suffix === "" ? name : `${name}${suffix}`;
|
|
190
|
+
errors[key] = error;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
const error = validateField(value, fieldSchema, formMessages);
|
|
194
|
+
if (error) {
|
|
195
|
+
errors[name] = error;
|
|
196
|
+
}
|
|
105
197
|
}
|
|
106
198
|
}
|
|
107
199
|
if (Object.keys(errors).length > 0) {
|
|
@@ -121,22 +213,66 @@ function buildOutputValues(values, fields) {
|
|
|
121
213
|
const output = {};
|
|
122
214
|
for (const [name, fieldSchema] of Object.entries(fields)) {
|
|
123
215
|
let value = values[name];
|
|
216
|
+
if (isArrayFieldSchema(fieldSchema)) {
|
|
217
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
218
|
+
output[name] = void 0;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (fieldSchema.item.vo) {
|
|
222
|
+
output[name] = value.map((item) => fieldSchema.item.vo.create(item));
|
|
223
|
+
} else {
|
|
224
|
+
output[name] = [...value];
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const fs = fieldSchema;
|
|
124
229
|
const isEmpty = value === void 0 || value === null || value === "";
|
|
125
230
|
if (isEmpty) {
|
|
126
231
|
output[name] = void 0;
|
|
127
232
|
continue;
|
|
128
233
|
}
|
|
129
|
-
if (
|
|
130
|
-
value =
|
|
234
|
+
if (fs.vo) {
|
|
235
|
+
value = fs.vo.create(value);
|
|
131
236
|
}
|
|
132
237
|
output[name] = value;
|
|
133
238
|
}
|
|
134
239
|
return output;
|
|
135
240
|
}
|
|
136
241
|
|
|
137
|
-
// src/
|
|
138
|
-
function
|
|
139
|
-
|
|
242
|
+
// src/create-form-schema.ts
|
|
243
|
+
function parseFieldPath(key) {
|
|
244
|
+
const result = [];
|
|
245
|
+
let i = 0;
|
|
246
|
+
let current = "";
|
|
247
|
+
while (i < key.length) {
|
|
248
|
+
if (key[i] === "[") {
|
|
249
|
+
if (current) {
|
|
250
|
+
result.push(current);
|
|
251
|
+
current = "";
|
|
252
|
+
}
|
|
253
|
+
i++;
|
|
254
|
+
let num = "";
|
|
255
|
+
while (i < key.length && key[i] !== "]") {
|
|
256
|
+
num += key[i];
|
|
257
|
+
i++;
|
|
258
|
+
}
|
|
259
|
+
result.push(Number(num));
|
|
260
|
+
i++;
|
|
261
|
+
} else {
|
|
262
|
+
current += key[i];
|
|
263
|
+
i++;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (current) {
|
|
267
|
+
result.push(current);
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
function createFormSchema(config) {
|
|
272
|
+
const schema = {
|
|
273
|
+
fields: config.fields,
|
|
274
|
+
messages: config.messages,
|
|
275
|
+
resolver: config.resolver,
|
|
140
276
|
"~standard": {
|
|
141
277
|
version: 1,
|
|
142
278
|
vendor: "vorm",
|
|
@@ -148,7 +284,7 @@ function formToStandardSchema(schema) {
|
|
|
148
284
|
const issues = errorEntries.map(
|
|
149
285
|
([field, error]) => ({
|
|
150
286
|
message: error.message || error.code,
|
|
151
|
-
path:
|
|
287
|
+
path: parseFieldPath(field)
|
|
152
288
|
})
|
|
153
289
|
);
|
|
154
290
|
return { issues };
|
|
@@ -162,13 +298,17 @@ function formToStandardSchema(schema) {
|
|
|
162
298
|
types: void 0
|
|
163
299
|
}
|
|
164
300
|
};
|
|
301
|
+
return schema;
|
|
165
302
|
}
|
|
166
303
|
export {
|
|
167
304
|
buildOutputValues,
|
|
305
|
+
createArrayField,
|
|
168
306
|
createField,
|
|
169
307
|
createFormSchema,
|
|
170
|
-
|
|
308
|
+
isArrayFieldSchema,
|
|
309
|
+
parseFieldPath,
|
|
171
310
|
resolveMessage,
|
|
311
|
+
validateArrayField,
|
|
172
312
|
validateField,
|
|
173
313
|
validateForm
|
|
174
314
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gunubin/vorm-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Field schemas, form validation, and output building for vorm",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"form",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@gunubin/vorm-core": "0.
|
|
41
|
+
"@gunubin/vorm-core": "0.3.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"typescript": "^5.9.3",
|