Skip to content

Commit 970da6a

Browse files
committed
Superstruct tests passing.
1 parent 1671c35 commit 970da6a

File tree

7 files changed

+120
-14
lines changed

7 files changed

+120
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- New validation library: [Superstruct](https://docs.superstructjs.org/)!
13+
1014
### Fixed
1115

1216
- Type inference for validation errors didn't include `_errors` for objects, only for arrays.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
"@vinejs/vine": "^1.8.0",
109109
"arktype": ">=2.0.0-dev.21",
110110
"joi": "^17.13.1",
111-
"superstruct": "^1.0.4",
111+
"superstruct": "^2.0.2",
112112
"svelte": "3.x || 4.x || >=5.0.0-next.51",
113113
"valibot": ">=0.33.0 <1",
114114
"yup": "^1.4.0",
@@ -152,7 +152,7 @@
152152
"arktype": "2.0.0-dev.21",
153153
"joi": "^17.13.3",
154154
"json-schema-to-ts": "^3.1.0",
155-
"superstruct": "^1.0.4",
155+
"superstruct": "^2.0.2",
156156
"valibot": "^0.35.0",
157157
"yup": "^1.4.0",
158158
"zod": "^3.23.8",

pnpm-lock.yaml

Lines changed: 5 additions & 5 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
@@ -28,7 +28,8 @@ export type ValidationLibrary =
2828
| 'yup'
2929
| 'zod'
3030
| 'vine'
31-
| 'schemasafe';
31+
| 'schemasafe'
32+
| 'superstruct';
3233

3334
export type AdapterOptions<T> = {
3435
jsonSchema?: JSONSchema;

src/lib/adapters/superstruct.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
type ValidationAdapter,
3+
createAdapter,
4+
type ClientValidationAdapter,
5+
createJsonSchema,
6+
type RequiredDefaultsOptions,
7+
type Infer,
8+
type ValidationResult
9+
} from './adapters.js';
10+
import type { Struct } from 'superstruct';
11+
import { memoize } from '$lib/memoize.js';
12+
13+
// async function modules() {
14+
// const { validate } = await import(/* webpackIgnore: true */ 'superstruct');
15+
// return { validate };
16+
// }
17+
18+
// const fetchModule = /* @__PURE__ */ memoize(modules);
19+
20+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21+
async function validate<T extends Struct<any, any>>(
22+
schema: T,
23+
data: unknown
24+
): Promise<ValidationResult<Infer<T>>> {
25+
const result = schema.validate(data, { coerce: true });
26+
if (!result[0]) {
27+
return {
28+
data: result[1] as Infer<T>,
29+
success: true
30+
};
31+
}
32+
const errors = result[0];
33+
return {
34+
success: false,
35+
issues: errors.failures().map((error) => ({
36+
message: error.message,
37+
path: error.path
38+
}))
39+
};
40+
}
41+
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
function _superstruct<T extends Struct<any, any>>(
44+
schema: T,
45+
options: RequiredDefaultsOptions<Infer<T>>
46+
): ValidationAdapter<Infer<T>> {
47+
return createAdapter({
48+
superFormValidationLibrary: 'superstruct',
49+
defaults: options.defaults,
50+
jsonSchema: createJsonSchema(options),
51+
validate: async (data) => validate(schema, data)
52+
});
53+
}
54+
55+
function _superstructClient<T extends Struct>(schema: T): ClientValidationAdapter<Infer<T>> {
56+
return {
57+
superFormValidationLibrary: 'superstruct',
58+
validate: async (data) => validate(schema, data)
59+
};
60+
}
61+
62+
export const superstruct = /* @__PURE__ */ memoize(_superstruct);
63+
export const superstructClient = /* @__PURE__ */ memoize(_superstructClient);

src/lib/adapters/typeSchema.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { Schema as Schema$2, InferType } from 'yup';
1111
import type { ZodSchema, input, output } from 'zod';
1212
import type { SchemaTypes, Infer as VineInfer } from '@vinejs/vine/types';
1313
import type { FromSchema, JSONSchema } from 'json-schema-to-ts';
14+
import type { Struct, Infer as Infer$2 } from 'superstruct';
1415

1516
/*
1617
import type { SchemaObject } from 'ajv';
@@ -19,7 +20,6 @@ import type { Schema as Schema$1 } from '@effect/schema/Schema';
1920
import type { Any, OutputOf, TypeOf } from 'io-ts';
2021
import type { Predicate, Infer as Infer$1 } from 'ow';
2122
import type { Runtype, Static } from 'runtypes';
22-
import type { Struct, Infer as Infer$2 } from 'superstruct';
2323
*/
2424

2525
type Replace<T, From, To> =
@@ -133,6 +133,15 @@ interface SchemasafeResolver<Schema extends JSONSchema, Data = FromSchema<Schema
133133
output: this['schema'] extends Schema ? Data : never;
134134
}
135135

136+
interface SuperstructResolver extends Resolver {
137+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
138+
base: Struct<any, any>;
139+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
140+
input: this['schema'] extends Struct<any, any> ? Infer$2<this['schema']> : never;
141+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
142+
output: this['schema'] extends Struct<any, any> ? Infer$2<this['schema']> : never;
143+
}
144+
136145
/*
137146
interface AjvResolver extends Resolver {
138147
base: SchemaObject;
@@ -166,10 +175,6 @@ interface RuntypesResolver extends Resolver {
166175
output: this['schema'] extends Runtype ? Static<this['schema']> : never;
167176
}
168177
169-
interface SuperstructResolver extends Resolver {
170-
base: Struct<any, any>;
171-
output: this['schema'] extends Struct<any, any> ? Infer$2<this['schema']> : never;
172-
}
173178
*/
174179

175180
type Registry = {
@@ -182,14 +187,14 @@ type Registry = {
182187
zod: ZodResolver;
183188
vine: VineResolver;
184189
schemasafe: SchemasafeResolver<JSONSchema>;
190+
superstruct: SuperstructResolver;
185191
/*
186192
ajv: AjvResolver;
187193
deepkit: DeepkitResolver;
188194
effect: EffectResolver;
189195
'io-ts': IoTsResolver;
190196
ow: OwResolver;
191197
runtypes: RuntypesResolver;
192-
superstruct: SuperstructResolver;
193198
*/
194199
};
195200

src/tests/superValidate.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ import {
4747
import { vine } from '$lib/adapters/vine.js';
4848
import Vine from '@vinejs/vine';
4949

50+
import { superstruct } from '$lib/adapters/superstruct.js';
51+
import {
52+
object as ssObject,
53+
string as ssString,
54+
number as ssNumber,
55+
array as ssArray,
56+
date as ssDate,
57+
optional as ssOptional,
58+
define as ssDefine,
59+
size as ssSize,
60+
min as ssMin,
61+
pattern as ssPattern,
62+
nullable as ssNullable
63+
} from 'superstruct';
64+
5065
import { schemasafe } from '$lib/adapters/schemasafe.js';
5166

5267
import { traversePath } from '$lib/traversal.js';
@@ -745,6 +760,24 @@ describe('vine', () => {
745760
schemaTest(adapter, ['email', 'nospace', 'tags'], 'simple', 'stringToDate');
746761
});
747762

763+
describe('superstruct', () => {
764+
const email = () => ssDefine('email', (value) => String(value).includes('@'));
765+
766+
const schema = ssObject({
767+
name: ssOptional(ssString()),
768+
email: email(),
769+
tags: ssSize(ssArray(ssSize(ssString(), 2, Infinity)), 3, Infinity),
770+
score: ssMin(ssNumber(), 0),
771+
date: ssOptional(ssDate()),
772+
nospace: ssOptional(ssPattern(ssString(), nospacePattern)),
773+
extra: ssNullable(ssString())
774+
});
775+
776+
const adapter = superstruct(schema, { defaults });
777+
778+
schemaTest(adapter, undefined, 'simple');
779+
});
780+
748781
///// Common ////////////////////////////////////////////////////////
749782

750783
describe('Schema In/Out transformations', () => {

0 commit comments

Comments
 (0)