Skip to content

Commit 02db939

Browse files
committed
Merge remote-tracking branch 'remotes/origin/main'
2 parents 228339c + 2f8b0aa commit 02db939

File tree

7 files changed

+206
-7
lines changed

7 files changed

+206
-7
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@
101101
"svelte": "3.x || 4.x || >=5.0.0-next.51",
102102
"valibot": "^0.28.1",
103103
"yup": "^1.3.3",
104-
"zod": "^3.22.4"
104+
"zod": "^3.22.4",
105+
"@vinejs/vine": "^1.7.1"
105106
},
106107
"peerDependenciesMeta": {
107108
"@sinclair/typebox": {
@@ -124,6 +125,9 @@
124125
},
125126
"zod": {
126127
"optional": true
128+
},
129+
"@vinejs/vine": {
130+
"optional": true
127131
}
128132
},
129133
"optionalDependencies": {
@@ -136,6 +140,7 @@
136140
"valibot": "^0.28.1",
137141
"yup": "^1.3.3",
138142
"zod": "^3.22.4",
143+
"@vinejs/vine": "^1.7.1",
139144
"zod-to-json-schema": "^3.22.4"
140145
},
141146
"dependencies": {

pnpm-lock.yaml

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/adapters/adapters.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export type ValidationLibrary =
2626
| 'typebox'
2727
| 'valibot'
2828
| 'yup'
29-
| 'zod';
29+
| 'zod'
30+
| 'vine';
3031

3132
export type AdapterOptions<T extends Schema> = {
3233
jsonSchema?: JSONSchema;

src/lib/adapters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { typebox, typeboxClient } from './typebox.js';
77
export { valibot, valibotClient } from './valibot.js';
88
export { yup, yupClient } from './yup.js';
99
export { zod, zodClient } from './zod.js';
10+
export { vine, vineClient } from './vine.js';
1011

1112
/*
1213
// Cannot use due to moduleResolution problem: https://github.com/ianstormtaylor/superstruct/issues/1200

src/lib/adapters/typeSchema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type { AnySchema } from 'joi';
44
import type { BaseSchema, BaseSchemaAsync, Input, Output } from 'valibot';
55
import type { Schema as Schema$2, InferType } from 'yup';
66
import type { ZodSchema, input, output } from 'zod';
7+
import type { SchemaTypes, Infer as VineInfer } from '@vinejs/vine/types';
8+
79
/*
810
import type { SchemaObject } from 'ajv';
911
import type { Type as Type$1 } from '@deepkit/type';
@@ -14,6 +16,13 @@ import type { Runtype, Static } from 'runtypes';
1416
import type { Struct, Infer as Infer$2 } from 'superstruct';
1517
*/
1618

19+
type Replace<T, From, To> =
20+
NonNullable<T> extends From
21+
? To | Exclude<T, From>
22+
: NonNullable<T> extends object
23+
? { [K in keyof T]: Replace<T[K], From, To> }
24+
: T;
25+
1726
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1827
type IfDefined<T> = any extends T ? never : T;
1928
type UnknownIfNever<T> = [T] extends [never] ? unknown : T;
@@ -101,6 +110,14 @@ interface ZodResolver extends Resolver {
101110
output: this['schema'] extends ZodSchema ? output<this['schema']> : never;
102111
}
103112

113+
interface VineResolver extends Resolver {
114+
base: SchemaTypes;
115+
input: this['schema'] extends SchemaTypes
116+
? Replace<VineInfer<this['schema']>, Date, string>
117+
: never;
118+
output: this['schema'] extends SchemaTypes ? VineInfer<this['schema']> : never;
119+
}
120+
104121
/*
105122
interface AjvResolver extends Resolver {
106123
base: SchemaObject;
@@ -148,6 +165,7 @@ type Registry = {
148165
valibot: ValibotResolver;
149166
yup: YupResolver;
150167
zod: ZodResolver;
168+
vine: VineResolver;
151169
/*
152170
ajv: AjvResolver;
153171
deepkit: DeepkitResolver;

src/lib/adapters/vine.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
createAdapter,
3+
createJsonSchema,
4+
type ValidationAdapter,
5+
type ValidationResult,
6+
type ClientValidationAdapter,
7+
type RequiredDefaultsOptions,
8+
type Infer,
9+
type InferIn
10+
} from './adapters.js';
11+
import { memoize } from '$lib/memoize.js';
12+
import type { SchemaTypes } from '@vinejs/vine/types';
13+
14+
async function modules() {
15+
const { Vine, errors } = await import(/* webpackIgnore: true */ '@vinejs/vine');
16+
return { Vine, errors };
17+
}
18+
19+
const fetchModule = /* @__PURE__ */ memoize(modules);
20+
21+
async function validate<T extends SchemaTypes>(
22+
schema: T,
23+
data: unknown
24+
): Promise<ValidationResult<Infer<T>>> {
25+
const { Vine, errors } = await fetchModule();
26+
try {
27+
const output = await new Vine().validate({ schema, data });
28+
return {
29+
success: true,
30+
data: output as Infer<T>
31+
};
32+
} catch (e) {
33+
if (e instanceof errors.E_VALIDATION_ERROR) {
34+
return {
35+
success: false,
36+
issues: e.messages.map((m: { field: string; message: string }) => ({
37+
path: m.field.split('.'),
38+
message: m.message
39+
}))
40+
};
41+
} else {
42+
return { success: false, issues: [] };
43+
}
44+
}
45+
}
46+
47+
function _vine<T extends SchemaTypes>(
48+
schema: T,
49+
options: RequiredDefaultsOptions<T>
50+
): ValidationAdapter<Infer<T>, InferIn<T>> {
51+
return createAdapter({
52+
superFormValidationLibrary: 'vine',
53+
validate: async (data: unknown) => validate(schema, data),
54+
jsonSchema: createJsonSchema(options),
55+
defaults: options.defaults
56+
});
57+
}
58+
59+
function _vineClient<T extends SchemaTypes>(
60+
schema: T
61+
): ClientValidationAdapter<Infer<T>, InferIn<T>> {
62+
return {
63+
superFormValidationLibrary: 'vine',
64+
validate: async (data) => validate(schema, data)
65+
};
66+
}
67+
68+
export const vine = /* @__PURE__ */ memoize(_vine);
69+
export const vineClient = /* @__PURE__ */ memoize(_vineClient);

src/tests/superValidate.test.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import {
4343
array as yupArray,
4444
date as yupDate
4545
} from 'yup';
46+
import { vine } from '$lib/adapters/vine.js';
47+
import Vine from '@vinejs/vine';
4648
import { traversePath } from '$lib/traversal.js';
4749
import { splitPath } from '$lib/stringPath.js';
4850

@@ -399,6 +401,31 @@ describe('Zod', () => {
399401
schemaTest(zod(schema));
400402
});
401403

404+
/////////////////////////////////////////////////////////////////////
405+
406+
describe('vine', () => {
407+
const schema = Vine.object({
408+
name: Vine.string().optional(),
409+
email: Vine.string().email(),
410+
tags: Vine.array(Vine.string().minLength(2)).minLength(3),
411+
score: Vine.number().min(0),
412+
date: Vine.date().optional(),
413+
nospace: Vine.string().regex(nospacePattern).optional()
414+
});
415+
416+
const adapter = vine(schema, { defaults });
417+
418+
it('should accept string as input for inferred Date fields', async () => {
419+
const date = '2024-02-14';
420+
const form = await superValidate({ ...validData, date }, adapter);
421+
422+
const realDate: Date | undefined = form.data.date;
423+
expect(realDate).toEqual(new Date(date + 'T00:00:00'));
424+
});
425+
426+
schemaTest(adapter, ['email', 'nospace', 'tags'], 'simple', true);
427+
});
428+
402429
///// Common ////////////////////////////////////////////////////////
403430

404431
describe('Schema In/Out transformations', () => {
@@ -460,11 +487,14 @@ type ErrorFields = ('email' | 'date' | 'nospace' | 'tags' | 'tags[1]')[];
460487
function schemaTest(
461488
adapter: ValidationAdapter<Record<string, unknown>, Record<string, unknown>>,
462489
errors: ErrorFields = ['email', 'nospace', 'tags', 'tags[1]'],
463-
adapterType: 'full' | 'simple' = 'full'
490+
adapterType: 'full' | 'simple' = 'full',
491+
dateAsString: boolean = false
464492
) {
493+
const validD = { ...validData, date: dateAsString ? '2024-01-01' : validData.date };
494+
465495
// eslint-disable-next-line @typescript-eslint/no-explicit-any
466496
function expectErrors(errors: ErrorFields, errorMessages: Record<string, any>) {
467-
//console.log('🚀 ~ expectErrors ~ errorMessages:', errorMessages);
497+
// console.log('🚀 ~ expectErrors ~ errorMessages:', errorMessages);
468498

469499
if (errors.includes('nospace')) expect(errorMessages.nospace).toBeTruthy();
470500
if (errors.includes('email')) expect(errorMessages.email).toBeTruthy();
@@ -530,11 +560,14 @@ function schemaTest(
530560
});
531561

532562
it('with valid test data', async () => {
533-
const output = await superValidate(validData, adapter);
563+
const output = await superValidate(validD, adapter);
534564
expect(output.errors).toEqual({});
535565
expect(output.valid).toEqual(true);
536-
expect(output.data).not.toBe(validData);
537-
expect(output.data).toEqual(validData);
566+
expect(output.data).not.toBe(validD);
567+
expect(output.data).toEqual({
568+
...validD,
569+
date: dateAsString ? new Date(validD.date + 'T00:00:00') : validD.date
570+
});
538571
expect(output.message).toBeUndefined();
539572
expectConstraints(output.constraints);
540573
});

0 commit comments

Comments
 (0)