0.0.2
Signed-off-by: Avior <git@avior.me>
This commit is contained in:
parent
4ab90d8476
commit
817b7d6774
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
node_modules/
|
||||
dist
|
||||
|
165
Schema.ts
165
Schema.ts
@ -1,165 +0,0 @@
|
||||
import SchemaArray from "./items/array"
|
||||
import SchemaBoolean from "./items/boolean"
|
||||
import SchemaEnum, { EnumLike } from "./items/enum"
|
||||
import SchemaLiteral from "./items/literal"
|
||||
import SchemaNullable from "./items/nullable"
|
||||
import SchemaNumber from "./items/number"
|
||||
import SchemaObject from "./items/object"
|
||||
import SchemaString from "./items/string"
|
||||
import { SchemaUnion } from "./items/union"
|
||||
import SchemaItem from "./SchemaItem"
|
||||
import { SchemaJSON } from "./types"
|
||||
|
||||
export function parceable() {
|
||||
|
||||
return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
|
||||
if (!(target instanceof SchemaItem)) {
|
||||
throw new Error('the decorator is only usable on Schema')
|
||||
}
|
||||
|
||||
|
||||
const original = target[propertyKey]
|
||||
|
||||
const t = function () { }
|
||||
|
||||
descriptor.value = function (this: SchemaItem, ...args: Array<any>) {
|
||||
this.savedCalls.push({ name: propertyKey as string, args: args })
|
||||
const res = original.call(this, ...args)
|
||||
return res
|
||||
}
|
||||
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
|
||||
interface SchemaItemStatic {
|
||||
new(...args: Array<any>): SchemaItem
|
||||
}
|
||||
|
||||
type ExtractGeneric<T> = T extends SchemaItem<infer U> ? U : never
|
||||
|
||||
export default class Schema {
|
||||
private static registeredModules: Array<SchemaItemStatic> = [
|
||||
SchemaArray,
|
||||
SchemaBoolean,
|
||||
SchemaEnum,
|
||||
SchemaLiteral,
|
||||
SchemaNullable,
|
||||
SchemaObject,
|
||||
SchemaString,
|
||||
SchemaUnion
|
||||
]
|
||||
|
||||
public static register(module: SchemaItemStatic) {
|
||||
this.registeredModules.push(module)
|
||||
}
|
||||
|
||||
public static getModule(name: string) {
|
||||
return this.registeredModules.find((it) => it.name === name)
|
||||
}
|
||||
|
||||
public static array<Type extends SchemaItem>(
|
||||
...inputs: ConstructorParameters<typeof SchemaArray<Type>>
|
||||
): SchemaArray<Type> {
|
||||
return new SchemaArray<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static boolean(
|
||||
...inputs: ConstructorParameters<typeof SchemaBoolean>
|
||||
): SchemaBoolean {
|
||||
return new SchemaBoolean(...inputs)
|
||||
}
|
||||
|
||||
public static enum<Type extends EnumLike>(
|
||||
...inputs: ConstructorParameters<typeof SchemaEnum<Type>>
|
||||
): SchemaEnum<Type> {
|
||||
return new SchemaEnum<Type>(...inputs)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param input the literal value (note: append `as const` else the typing won't work correctly)
|
||||
* @returns
|
||||
*/
|
||||
public static literal<Type>(
|
||||
input: Type
|
||||
): SchemaLiteral<Type> {
|
||||
return new SchemaLiteral<Type>(input)
|
||||
}
|
||||
|
||||
public static nullable<Type extends SchemaItem>(
|
||||
...inputs: ConstructorParameters<typeof SchemaNullable<Type>>
|
||||
): SchemaNullable<Type> {
|
||||
return new SchemaNullable<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static number(
|
||||
...inputs: ConstructorParameters<typeof SchemaNumber>
|
||||
): SchemaNumber {
|
||||
return new SchemaNumber(...inputs)
|
||||
}
|
||||
|
||||
public static object<Type extends Record<string, SchemaItem<any>>>(
|
||||
...inputs: ConstructorParameters<typeof SchemaObject<Type>>
|
||||
): SchemaObject<Type> {
|
||||
return new SchemaObject<Type>(...inputs)
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link SchemaString}
|
||||
*/
|
||||
public static string(
|
||||
...inputs: ConstructorParameters<typeof SchemaString>
|
||||
): SchemaString {
|
||||
return new SchemaString(...inputs)
|
||||
}
|
||||
|
||||
public static union<Type extends Array<SchemaItem<any>>>(
|
||||
...inputs: ConstructorParameters<typeof SchemaUnion<Type>>
|
||||
): SchemaUnion<Type> {
|
||||
return new SchemaUnion<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static fromJSON(json: SchemaJSON): SchemaItem {
|
||||
|
||||
// get the module
|
||||
const fn = this.getModule(json.i)
|
||||
|
||||
// handle module not detected
|
||||
if (!fn) {
|
||||
throw new Error(`Schema cannot parse ${json.i}`)
|
||||
}
|
||||
|
||||
// init the module
|
||||
const item = new fn(...(json.c?.map((it) => this.isSchemaJSON(it) ? Schema.fromJSON(it) : it) ?? []))
|
||||
|
||||
// handle validations
|
||||
for (const validation of (json.f ?? [])) {
|
||||
// validation not found in item :(
|
||||
if (!(validation.n in item)) {
|
||||
throw new Error('validation not available in Schema Item')
|
||||
}
|
||||
|
||||
// init the validation
|
||||
(item[validation.n] as (...params: Array<unknown>) => void)(...validation.a.map((it) => Schema.isSchemaJSON(it) ? Schema.fromJSON(it) : it))
|
||||
}
|
||||
|
||||
// add the attributes
|
||||
item.attrs(...json.a ?? [])
|
||||
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
public static isSchemaJSON(data: unknown): data is SchemaJSON {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!('i' in data)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -8,11 +8,24 @@ export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
{
|
||||
ignores: [
|
||||
'tests',
|
||||
'eslint.config.mjs',
|
||||
'dist',
|
||||
'index.ts'
|
||||
]
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['tsconfig.json']
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.browser,
|
||||
@ -23,16 +36,6 @@ export default [
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
'@stylistic': stylistic
|
||||
},
|
||||
ignores: [
|
||||
'node_modules/',
|
||||
'out/',
|
||||
'*.js',
|
||||
'__tests__/',
|
||||
'src/route.ts',
|
||||
'dist/',
|
||||
'.astro/',
|
||||
'.diaz/'
|
||||
],
|
||||
|
||||
rules: {
|
||||
'@stylistic/arrow-parens': [
|
||||
|
18
index.ts
18
index.ts
@ -1,10 +1,12 @@
|
||||
import s from "./Schema"
|
||||
import { Infer, s } from '.'
|
||||
|
||||
s.string()
|
||||
const res = new s({
|
||||
a: s.object({
|
||||
p: s.string()
|
||||
})
|
||||
})
|
||||
type Infered = Infer<typeof res>
|
||||
|
||||
/*
|
||||
buffer
|
||||
date
|
||||
file
|
||||
record
|
||||
*/
|
||||
type Test = Infered['a']['p']
|
||||
|
||||
console.log(res.parse("{ hello: 'world' }"))
|
||||
|
2805
package-lock.json
generated
Normal file
2805
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
package.json
54
package.json
@ -1,13 +1,43 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@dzeio/object-util": "^1.8.3",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@stylistic/eslint-plugin": "^2.13.0",
|
||||
"@types/bun": "^1.1.18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
||||
"globals": "^15.14.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.21.0",
|
||||
"vitest": "^3.0.4"
|
||||
}
|
||||
}
|
||||
"name": "@dzeio/schema",
|
||||
"version": "0.0.2",
|
||||
"dependencies": {
|
||||
"@dzeio/object-util": "^1.8.3"
|
||||
},
|
||||
"main": "./dist/Schema.js",
|
||||
"module": "./dist/Schema.mjs",
|
||||
"types": "./dist/Schema.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"require": {
|
||||
"types": "./dist/Schema.d.ts",
|
||||
"default": "./dist/Schema.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./dist/Schema.d.mts",
|
||||
"default": "./dist/Schema.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@stylistic/eslint-plugin": "^2.13.0",
|
||||
"@types/bun": "^1.1.18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
||||
"globals": "^15.14.0",
|
||||
"tsup": "^8.3.6",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.21.0",
|
||||
"vitest": "^3.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "bun test",
|
||||
"lint": "eslint",
|
||||
"build": "rm -rf dist && tsup ./src/Schema.ts --format cjs,esm --dts --clean",
|
||||
"prepublishOnly": "rm -rf dist && tsup ./src/Schema.ts --format cjs,esm --dts --clean"
|
||||
}
|
||||
}
|
||||
|
241
src/Schema.ts
Normal file
241
src/Schema.ts
Normal file
@ -0,0 +1,241 @@
|
||||
/* eslint-disable id-blacklist */
|
||||
import { parseForm, parseFormData, parseQuery } from 'helpers'
|
||||
import SchemaDate from 'items/date'
|
||||
import SchemaRecord from 'items/record'
|
||||
import SchemaArray from './items/array'
|
||||
import SchemaBoolean from './items/boolean'
|
||||
import SchemaEnum, { EnumLike } from './items/enum'
|
||||
import SchemaLiteral from './items/literal'
|
||||
import SchemaNullable from './items/nullable'
|
||||
import SchemaNumber from './items/number'
|
||||
import SchemaObject from './items/object'
|
||||
import SchemaString from './items/string'
|
||||
import SchemaUnion from './items/union'
|
||||
import SchemaItem from './SchemaItem'
|
||||
import { SchemaJSON } from './types'
|
||||
|
||||
export function parceable() {
|
||||
|
||||
return (target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
|
||||
// make sure the target is of SchemaItem
|
||||
if (!(target instanceof SchemaItem)) {
|
||||
throw new Error('the decorator is only usable on Schema')
|
||||
}
|
||||
|
||||
// make sur the property exists in the target
|
||||
if (!(propertyKey in target)) {
|
||||
throw new Error('property not set in object')
|
||||
}
|
||||
|
||||
// @ts-expect-error call a function defined from calls of collable
|
||||
const original = target[propertyKey] as (...args: Array<unknown>) => unknown
|
||||
|
||||
// replace original function with modified one
|
||||
descriptor.value = function(this: SchemaItem, ...args: Array<unknown>) {
|
||||
this.savedCalls.push({ name: propertyKey as string, args: args as Array<string> })
|
||||
const res: unknown = original.call(this, ...args)
|
||||
return res
|
||||
}
|
||||
|
||||
// return the modified descriptor
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
|
||||
type SchemaItemStatic = new (...args: Array<any>) => SchemaItem
|
||||
|
||||
export const Types = {
|
||||
Array: SchemaArray,
|
||||
Boolean: SchemaBoolean,
|
||||
Date: SchemaDate,
|
||||
Enum: SchemaEnum,
|
||||
Literal: SchemaLiteral,
|
||||
Nullable: SchemaNullable,
|
||||
Object: SchemaObject,
|
||||
Record: SchemaRecord,
|
||||
String: SchemaString,
|
||||
Union: SchemaUnion
|
||||
} as const
|
||||
|
||||
export default class Schema<T extends Record<string, SchemaItem> = Record<string, SchemaItem>> extends SchemaObject<T> {
|
||||
private static registeredModules: Array<SchemaItemStatic> = [
|
||||
SchemaArray,
|
||||
SchemaBoolean,
|
||||
SchemaDate,
|
||||
SchemaEnum,
|
||||
SchemaLiteral,
|
||||
SchemaNullable,
|
||||
SchemaObject,
|
||||
SchemaRecord,
|
||||
SchemaString,
|
||||
SchemaUnion
|
||||
]
|
||||
|
||||
public static register(module: SchemaItemStatic) {
|
||||
this.registeredModules.push(module)
|
||||
}
|
||||
|
||||
public static getModule(name: string) {
|
||||
return this.registeredModules.find((it) => it.name === name)
|
||||
}
|
||||
|
||||
public static array<Type extends SchemaItem>(
|
||||
...inputs: ConstructorParameters<typeof SchemaArray<Type>>
|
||||
): SchemaArray<Type> {
|
||||
return new SchemaArray<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static boolean(
|
||||
...inputs: ConstructorParameters<typeof SchemaBoolean>
|
||||
): SchemaBoolean {
|
||||
return new SchemaBoolean(...inputs)
|
||||
}
|
||||
|
||||
public static date(
|
||||
...inputs: ConstructorParameters<typeof SchemaDate>
|
||||
): SchemaDate {
|
||||
return new SchemaDate(...inputs)
|
||||
}
|
||||
|
||||
public static enum<Type extends EnumLike>(
|
||||
...inputs: ConstructorParameters<typeof SchemaEnum<Type>>
|
||||
): SchemaEnum<Type> {
|
||||
return new SchemaEnum<Type>(...inputs)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param input the literal value (note: append `as const` else the typing won't work correctly)
|
||||
* @returns
|
||||
*/
|
||||
public static literal<Type>(
|
||||
input: Type
|
||||
): SchemaLiteral<Type> {
|
||||
return new SchemaLiteral<Type>(input)
|
||||
}
|
||||
|
||||
public static nullable<Type extends SchemaItem>(
|
||||
...inputs: ConstructorParameters<typeof SchemaNullable<Type>>
|
||||
): SchemaNullable<Type> {
|
||||
return new SchemaNullable<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static number(
|
||||
...inputs: ConstructorParameters<typeof SchemaNumber>
|
||||
): SchemaNumber {
|
||||
return new SchemaNumber(...inputs)
|
||||
}
|
||||
|
||||
public static object<Type extends Record<string, SchemaItem>>(
|
||||
...inputs: ConstructorParameters<typeof SchemaObject<Type>>
|
||||
): SchemaObject<Type> {
|
||||
return new SchemaObject<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static record<Keys extends SchemaItem, Values extends SchemaItem>(
|
||||
...inputs: ConstructorParameters<typeof SchemaRecord<Keys, Values>>
|
||||
): SchemaRecord<Keys, Values> {
|
||||
return new SchemaRecord<Keys, Values>(...inputs)
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link SchemaString}
|
||||
*/
|
||||
public static string(
|
||||
...inputs: ConstructorParameters<typeof SchemaString>
|
||||
): SchemaString {
|
||||
return new SchemaString(...inputs)
|
||||
}
|
||||
|
||||
public static union<Type extends Array<SchemaItem>>(
|
||||
...inputs: ConstructorParameters<typeof SchemaUnion<Type>>
|
||||
): SchemaUnion<Type> {
|
||||
return new SchemaUnion<Type>(...inputs)
|
||||
}
|
||||
|
||||
public static fromJSON(json: SchemaJSON): SchemaItem {
|
||||
|
||||
// get the module
|
||||
const fn = this.getModule(json.i)
|
||||
|
||||
// handle module not detected
|
||||
if (!fn) {
|
||||
throw new Error(`Schema cannot parse ${json.i}`)
|
||||
}
|
||||
|
||||
// init the module
|
||||
const item = new fn(...(json.c?.map((it) => this.isSchemaJSON(it) ? Schema.fromJSON(it) : it) ?? []))
|
||||
|
||||
// handle validations
|
||||
for (const validation of (json.f ?? [])) {
|
||||
// validation not found in item :(
|
||||
if (!(validation.n in item)) {
|
||||
throw new Error('validation not available in Schema Item')
|
||||
}
|
||||
|
||||
// init the validation
|
||||
// @ts-expect-error call a function defined from calls of collable
|
||||
(item[validation.n] as (...params: Array<unknown>) => void)(...validation.a?.map((it) => Schema.isSchemaJSON(it) ? Schema.fromJSON(it) : it) ?? [])
|
||||
}
|
||||
|
||||
// add the attributes
|
||||
item.attrs(...json.a ?? [])
|
||||
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
public static isSchemaJSON(data: unknown): data is SchemaJSON {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!('i' in data)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use helper `parseQuery`
|
||||
*/
|
||||
public validateQuery(query: URLSearchParams, fast = false) {
|
||||
return parseQuery(this, query, { fast })
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `parse`
|
||||
*/
|
||||
public validate(input: unknown, fast = false) {
|
||||
return this.parse(input, { fast })
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use helper `parseForm`
|
||||
*/
|
||||
public validateForm(form: HTMLFormElement, fast = false) {
|
||||
return parseForm(this, form, { fast })
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use helper `parseFormData`
|
||||
*/
|
||||
public validateFormData(data: FormData, fast = false) {
|
||||
return parseFormData(this, data, { fast })
|
||||
}
|
||||
}
|
||||
|
||||
export const s = Schema
|
||||
|
||||
export * from './helpers'
|
||||
export type * from './types.d.ts'
|
||||
|
||||
export {
|
||||
SchemaArray,
|
||||
SchemaBoolean, SchemaDate, SchemaEnum, SchemaItem, SchemaLiteral,
|
||||
SchemaNullable,
|
||||
SchemaNumber,
|
||||
SchemaObject, SchemaRecord, SchemaString,
|
||||
SchemaUnion
|
||||
}
|
@ -1,37 +1,41 @@
|
||||
import Schema from "./Schema"
|
||||
import { SchemaJSON, ValidationError, ValidationResult } from "./types"
|
||||
/* eslint-disable id-length */
|
||||
import { objectClean } from '@dzeio/object-util'
|
||||
import { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
import Schema, { SchemaNullable } from './Schema'
|
||||
import { SchemaJSON, ValidationError, ValidationResult } from './types'
|
||||
export default abstract class SchemaItem<Type = any> implements StandardSchemaV1<Type> {
|
||||
|
||||
export default abstract class SchemaItem<Type = any> {
|
||||
|
||||
private invalidError = 'the field is invalid'
|
||||
|
||||
/**
|
||||
* list of attributes for custom works
|
||||
*/
|
||||
public readonly attributes: Array<string> = []
|
||||
|
||||
public attrs(...attributes: Array<string>) {
|
||||
this.attributes.concat(attributes)
|
||||
return this
|
||||
}
|
||||
|
||||
public setInvalidError(err: string): this {
|
||||
this.invalidError = err
|
||||
return this
|
||||
}
|
||||
|
||||
public clone(): this {
|
||||
return Schema.fromJSON(this.toJSON()) as this
|
||||
// standard Schema V1 spec
|
||||
public '~standard': StandardSchemaV1.Props<Type> = {
|
||||
vendor: 'aptatio',
|
||||
version: 1,
|
||||
validate: (value: unknown) => {
|
||||
const res = this.parse(value)
|
||||
if (!res.valid) {
|
||||
return {
|
||||
issues: res.errors
|
||||
}
|
||||
}
|
||||
return {
|
||||
value: res.object,
|
||||
issues: res.errors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* schemas implementing unwrap can return their child component (mostly the value)
|
||||
*
|
||||
* ex: s.record(s.number(), s.string()) returns s.string()
|
||||
*
|
||||
* es2: s.nullable(s.string()) returns s.string()
|
||||
* keep public ?
|
||||
*/
|
||||
public unwrap?(): SchemaItem
|
||||
public validations: Array<{
|
||||
fn: (input: Type) => boolean
|
||||
error?: string | undefined
|
||||
}> = []
|
||||
|
||||
/**
|
||||
* Function calls saved for serialization
|
||||
*/
|
||||
public savedCalls: Array<{ name: string, args: Array<string> | IArguments | undefined }> = []
|
||||
|
||||
|
||||
/**
|
||||
* Pre process the variable for various reasons
|
||||
@ -53,23 +57,51 @@ export default abstract class SchemaItem<Type = any> {
|
||||
public postProcess: Array<(input: Type) => Type> = []
|
||||
|
||||
/**
|
||||
* keep public ?
|
||||
* list of attributes for custom works
|
||||
*/
|
||||
public validations: Array<{
|
||||
fn: (input: Type) => boolean
|
||||
error?: string | undefined
|
||||
}> = []
|
||||
|
||||
public savedCalls: Array<{ name: string, args: Array<string> | IArguments | undefined }> = []
|
||||
public readonly attributes: Array<string> = []
|
||||
|
||||
private readonly items?: Array<unknown>
|
||||
|
||||
private invalidError = 'the field is invalid'
|
||||
|
||||
public constructor(items?: Array<unknown> | IArguments) {
|
||||
if (items && items.length > 0) {
|
||||
this.items = Array.isArray(items) ? items : Array.from(items)
|
||||
}
|
||||
}
|
||||
|
||||
public attrs(...attributes: Array<string>) {
|
||||
this.attributes.concat(attributes)
|
||||
return this
|
||||
}
|
||||
|
||||
public attr(...attributes: Array<string>) {
|
||||
return this.attrs(...attributes)
|
||||
}
|
||||
|
||||
public setInvalidError(err: string): this {
|
||||
this.invalidError = err
|
||||
return this
|
||||
}
|
||||
|
||||
public clone(): this {
|
||||
return Schema.fromJSON(this.toJSON()) as this
|
||||
}
|
||||
|
||||
public nullable(): SchemaNullable<this> {
|
||||
return new SchemaNullable(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* schemas implementing unwrap can return their child component (mostly the value)
|
||||
*
|
||||
* ex: s.record(s.number(), s.string()) returns s.string()
|
||||
*
|
||||
* es2: s.nullable(s.string()) returns s.string()
|
||||
*/
|
||||
public unwrap?(): SchemaItem
|
||||
|
||||
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<Type> {
|
||||
// pre process the variable
|
||||
for (const preProcess of this.preProcess) {
|
||||
@ -95,6 +127,7 @@ export default abstract class SchemaItem<Type = any> {
|
||||
})
|
||||
|
||||
// if the system should be fast, stop checking for other errors
|
||||
// eslint-disable-next-line max-depth
|
||||
if (options?.fast) {
|
||||
return {
|
||||
valid: false,
|
||||
@ -119,26 +152,27 @@ export default abstract class SchemaItem<Type = any> {
|
||||
input = postProcess(input as Type)
|
||||
}
|
||||
|
||||
// validate that the pre process handled correctly the variable
|
||||
if (!this.isOfType(input)) {
|
||||
throw new Error('Post process error occured :(')
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
object: input
|
||||
object: input as Type
|
||||
}
|
||||
}
|
||||
|
||||
public toJSON(): SchemaJSON {
|
||||
|
||||
return {
|
||||
// build the JSON
|
||||
const res = {
|
||||
i: this.constructor.name,
|
||||
a: this.attributes.length > 0 ? this.attributes : undefined,
|
||||
c: this.items?.map((it) => it instanceof SchemaItem ? it.toJSON() : it),
|
||||
f: this.savedCalls
|
||||
.map((it) => (it.args ? { n: it.name, a: Array.from(it.args) } : { n: it.name }))
|
||||
}
|
||||
|
||||
// cleanup the object from undefined to make it smaller
|
||||
objectClean(res, { deep: false })
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
protected addValidation(fn: ((input: Type) => boolean) | { fn: (input: Type) => boolean, error?: string }, error?: string) {
|
||||
@ -148,6 +182,7 @@ export default abstract class SchemaItem<Type = any> {
|
||||
return this
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
protected addPreProcess(fn: (input: unknown) => Type | unknown) {
|
||||
this.preProcess.push(fn)
|
||||
|
60
src/helpers.ts
Normal file
60
src/helpers.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { objectGet, objectLoop, objectSet } from '@dzeio/object-util'
|
||||
import SchemaArray from 'items/array'
|
||||
import SchemaBoolean from 'items/boolean'
|
||||
import SchemaNullable from 'items/nullable'
|
||||
import SchemaObject from 'items/object'
|
||||
import type SchemaItem from 'SchemaItem'
|
||||
|
||||
export function parseQuery<T extends SchemaItem>(model: T, query: URLSearchParams, opts?: Parameters<T['parse']>[1]): ReturnType<T['parse']> {
|
||||
const record: Record<string, unknown> = {}
|
||||
for (const [key, value] of query) {
|
||||
record[key] = value
|
||||
}
|
||||
|
||||
return model.parse(record, opts) as ReturnType<T['parse']>
|
||||
}
|
||||
|
||||
export function parseFormData<T extends SchemaObject>(model: T, data: FormData, opts?: Parameters<T['parse']>[1]): ReturnType<T['parse']> {
|
||||
const record: Record<string, unknown> = {}
|
||||
// console.log('VALIDATE FORM DATA data', data)
|
||||
for (const [key, value] of data) {
|
||||
// console.log('parse', key, value)
|
||||
const isArray = model.model[key].isOfType([]) ?? false
|
||||
// record[key] = isArray ? data.getAll(key) : value
|
||||
objectSet(record, key.split('.').map((it) => /^\d+$/g.test(it) ? parseInt(it, 10) : it), isArray ? data.getAll(key) : value)
|
||||
}
|
||||
|
||||
// quick hack to handle FormData not returning Checkboxes
|
||||
const handleBoolean = (value: SchemaItem, keys: Array<string | number>) => {
|
||||
if (value instanceof SchemaNullable) {
|
||||
handleBoolean(value.unwrap(), keys)
|
||||
}
|
||||
|
||||
if (value instanceof SchemaArray) {
|
||||
const elements: Array<unknown> | undefined = objectGet(record, keys)
|
||||
for (let it = 0; it < (elements?.length ?? 0); it++) {
|
||||
handleBoolean(value.unwrap(), [...keys, it])
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof SchemaObject) {
|
||||
handleSchemaForBoolean(value.model as Record<string, SchemaItem>, keys)
|
||||
}
|
||||
|
||||
if (value instanceof SchemaBoolean) {
|
||||
objectSet(record, keys, !!data.get(keys.join('.')))
|
||||
}
|
||||
}
|
||||
const handleSchemaForBoolean = (modl: Record<string, SchemaItem>, keys: Array<string | number> = []) => {
|
||||
objectLoop(modl, (value, key) => {
|
||||
handleBoolean(value as unknown as SchemaItem, [...keys, key])
|
||||
})
|
||||
}
|
||||
handleSchemaForBoolean(model.model)
|
||||
// console.log(JSON.stringify(record, undefined, 2))
|
||||
return model.parse(record, opts) as ReturnType<T['parse']>
|
||||
}
|
||||
|
||||
export function parseForm<T extends SchemaObject>(model: T, form: HTMLFormElement, opts?: Parameters<T['parse']>[1]): ReturnType<T['parse']> {
|
||||
return parseFormData(model, new FormData(form), opts)
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
import { parceable } from "../Schema"
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import { SchemaInfer, ValidationError, ValidationResult } from "../types"
|
||||
import { parceable } from '../Schema'
|
||||
import SchemaItem from '../SchemaItem'
|
||||
import { SchemaInfer, ValidationError, ValidationResult } from '../types'
|
||||
|
||||
export default class SchemaArray<Type extends SchemaItem> extends SchemaItem<Array<SchemaInfer<Type>>> {
|
||||
public constructor(public readonly values: Type) { super(arguments) }
|
||||
|
||||
public unwrap() {
|
||||
return this.values
|
||||
}
|
||||
public constructor(public readonly values: Type) { super([values]) }
|
||||
|
||||
/**
|
||||
* transform the array so it only contains one of each elements
|
||||
@ -22,7 +18,7 @@ export default class SchemaArray<Type extends SchemaItem> extends SchemaItem<Arr
|
||||
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<Array<SchemaInfer<Type>>> {
|
||||
|
||||
// check errors from itself
|
||||
let { valid, object, errors = [] } = super.parse(input, options)
|
||||
const { valid, object, errors = [] } = super.parse(input, options)
|
||||
|
||||
// skip checking childs if self is not valid (maybe still try to check childs whan fast is false ?)
|
||||
if (!valid) {
|
||||
@ -37,39 +33,49 @@ export default class SchemaArray<Type extends SchemaItem> extends SchemaItem<Arr
|
||||
const errs: Array<ValidationError> = []
|
||||
for (let idx = 0; idx < (object as Array<Type>).length; idx++) {
|
||||
const item = (object as Array<Type>)[idx]
|
||||
const res = this.values.parse(item)
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
const errors = res.errors.map((it) => ({
|
||||
...it,
|
||||
field: it.field ? `${idx}.${it.field}` : idx.toString()
|
||||
}))
|
||||
if (options?.fast) {
|
||||
return {
|
||||
valid: false,
|
||||
object: clone as any,
|
||||
errors: errors
|
||||
}
|
||||
}
|
||||
errs.push(...errors)
|
||||
} else {
|
||||
const res: ValidationResult<SchemaInfer<Type>> = this.values.parse(item)
|
||||
|
||||
// handle valid child schema
|
||||
if (res.valid) {
|
||||
clone.push(res.object)
|
||||
continue
|
||||
}
|
||||
|
||||
// handle child errors
|
||||
const errss = res.errors.map((it) => ({
|
||||
...it,
|
||||
field: it.field ? `${idx}.${it.field}` : idx.toString()
|
||||
}))
|
||||
|
||||
if (options?.fast) {
|
||||
return {
|
||||
valid: false,
|
||||
object: clone,
|
||||
errors: errss
|
||||
}
|
||||
}
|
||||
errs.push(...errss)
|
||||
}
|
||||
|
||||
if (errs.length > 0) {
|
||||
return {
|
||||
valid: false,
|
||||
object: clone as any,
|
||||
object: clone,
|
||||
errors: errs
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
object: clone as any
|
||||
object: clone
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override unwrap(): Type {
|
||||
return this.values
|
||||
}
|
||||
|
||||
public override isOfType(input: unknown): input is Array<SchemaInfer<Type>> {
|
||||
return Array.isArray(input)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { parceable } from "../Schema"
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import { parceable } from '../Schema'
|
||||
import SchemaItem from '../SchemaItem'
|
||||
|
||||
export default class SchemaBoolean extends SchemaItem<boolean> {
|
||||
|
8
src/items/date.ts
Normal file
8
src/items/date.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import SchemaItem from '../SchemaItem'
|
||||
|
||||
export default class SchemaDate extends SchemaItem<Date> {
|
||||
|
||||
public override isOfType(input: unknown): input is Date {
|
||||
return input instanceof Date
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import SchemaItem from '../SchemaItem'
|
||||
|
||||
export type EnumLike = {
|
||||
export interface EnumLike {
|
||||
[k: string]: string | number
|
||||
[n: number]: string
|
||||
}
|
||||
@ -21,7 +21,7 @@ export default class SchemaEnum<E extends EnumLike> extends SchemaItem<E[keyof E
|
||||
// test above === numebr
|
||||
this.validations.push({
|
||||
fn: (input) => Object.values(this.templateEnum).includes(input),
|
||||
message: `Input is not part of ${templateEnum.constructor.name}`
|
||||
error: `Input is not part of ${templateEnum.constructor.name}`
|
||||
})
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import SchemaItem from '../SchemaItem'
|
||||
|
||||
export default class SchemaLiteral<Type> extends SchemaItem<Type> {
|
||||
public constructor(private readonly value: Type) {
|
||||
super(arguments)
|
||||
super([value])
|
||||
|
||||
this.validations.push({ fn: (value) => value === this.value })
|
||||
this.validations.push({ fn: (it) => it === this.value })
|
||||
}
|
||||
|
||||
public override isOfType(input: unknown): input is Type {
|
@ -1,14 +1,14 @@
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import { SchemaInfer, ValidationResult } from "../types"
|
||||
import SchemaItem from '../SchemaItem'
|
||||
import { SchemaInfer, ValidationResult } from '../types'
|
||||
|
||||
export default class SchemaNullable<Type extends SchemaItem> extends SchemaItem<SchemaInfer<Type> | undefined> {
|
||||
public constructor(public readonly child: Type) { super(arguments) }
|
||||
public constructor(public readonly child: Type) { super([child]) }
|
||||
|
||||
public unwrap() {
|
||||
public unwrap(): Type {
|
||||
return this.child
|
||||
}
|
||||
|
||||
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<SchemaInfer<Type>> {
|
||||
public override parse(input: unknown, options?: { fast?: boolean }): ValidationResult<SchemaInfer<Type> | undefined> {
|
||||
if (this.isNull(input)) {
|
||||
return {
|
||||
valid: true,
|
||||
@ -16,7 +16,7 @@ export default class SchemaNullable<Type extends SchemaItem> extends SchemaItem<
|
||||
}
|
||||
}
|
||||
|
||||
return this.child.parse(input)
|
||||
return this.child.parse(input, options)
|
||||
}
|
||||
|
||||
public override isOfType(input: unknown): input is SchemaInfer<Type> | undefined {
|
@ -3,14 +3,6 @@ import SchemaItem from '../SchemaItem'
|
||||
|
||||
export default class SchemaNumber extends SchemaItem<number> {
|
||||
|
||||
public min(...params: Parameters<SchemaNumber['gte']>): this {
|
||||
return this.gte(...params)
|
||||
}
|
||||
|
||||
public max(...params: Parameters<SchemaNumber['lte']>): this {
|
||||
return this.lte(...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* validate that the number is less or equal than {@link value}
|
||||
* @param value the maxumum value (inclusive)
|
||||
@ -92,4 +84,12 @@ export default class SchemaNumber extends SchemaItem<number> {
|
||||
public override isOfType(input: unknown): input is number {
|
||||
return typeof input === 'number' && !Number.isNaN(input)
|
||||
}
|
||||
|
||||
public min(...params: Parameters<SchemaNumber['gte']>): this {
|
||||
return this.gte(...params)
|
||||
}
|
||||
|
||||
public max(...params: Parameters<SchemaNumber['lte']>): this {
|
||||
return this.lte(...params)
|
||||
}
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
import { isObject, objectLoop, objectClone } from '@dzeio/object-util'
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import { SchemaInfer, ValidationResult } from "../types"
|
||||
import { isObject, objectClone, objectLoop } from '@dzeio/object-util'
|
||||
import SchemaItem from '../SchemaItem'
|
||||
import { SchemaInfer, ValidationResult } from '../types'
|
||||
|
||||
type ModelInfer<M extends Record<string, SchemaItem<any>>> = {
|
||||
type ModelInfer<M extends Record<string, SchemaItem>> = {
|
||||
[key in keyof M]: SchemaInfer<M[key]>
|
||||
}
|
||||
|
||||
export default class SchemaObject<T extends Record<string, SchemaItem<any>>> extends SchemaItem<ModelInfer<T>> {
|
||||
public id: string = 'object'
|
||||
export default class SchemaObject<T extends Record<string, SchemaItem> = Record<string, SchemaItem>> extends SchemaItem<ModelInfer<T>> {
|
||||
public id = 'object'
|
||||
|
||||
public constructor(public readonly model: T) {
|
||||
super(arguments)
|
||||
super([model])
|
||||
}
|
||||
|
||||
public override parse(input: unknown, options?: { fast?: boolean }): ValidationResult<ModelInfer<T>> {
|
||||
// check errors from itself
|
||||
let { valid, object, errors = [] } = super.parse(objectClone(input), options)
|
||||
const { valid, object, errors = [] } = super.parse(input, options)
|
||||
|
||||
// skip checking childs if self is not valid (maybe still try to check childs whan fast is false ?)
|
||||
if (!valid) {
|
||||
@ -26,20 +26,26 @@ export default class SchemaObject<T extends Record<string, SchemaItem<any>>> ext
|
||||
} as ValidationResult<ModelInfer<T>>
|
||||
}
|
||||
|
||||
const clone = objectClone(object)
|
||||
|
||||
// loop through the childs
|
||||
objectLoop(this.model, (childSchema, key) => {
|
||||
const childValue = object![key]
|
||||
const childValue = clone[key]
|
||||
|
||||
// parse the child
|
||||
const child = childSchema.parse(childValue)
|
||||
|
||||
// add errors
|
||||
if ((child.errors?.length ?? 0) > 0) {
|
||||
errors.push(...child.errors!)
|
||||
if (!child.valid) {
|
||||
errors.push(...child.errors.map((it) => ({
|
||||
...it,
|
||||
field: it.field ? `${key}.${it.field}` : key
|
||||
})))
|
||||
}
|
||||
|
||||
// @ts-expect-error while it's a generic we know by proof above that it's valid !
|
||||
object![key] = child.object
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
clone[key] = child.object
|
||||
|
||||
// skip rest of items if current one is invalid
|
||||
return child.valid || !options?.fast
|
||||
@ -49,7 +55,7 @@ export default class SchemaObject<T extends Record<string, SchemaItem<any>>> ext
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors: errors,
|
||||
object: object
|
||||
object: clone
|
||||
} as ValidationResult<ModelInfer<T>>
|
||||
}
|
||||
|
55
src/items/record.ts
Normal file
55
src/items/record.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { isObject, objectLoop } from '@dzeio/object-util'
|
||||
import SchemaItem from '../SchemaItem'
|
||||
import { SchemaInfer, ValidationResult } from '../types'
|
||||
|
||||
export default class SchemaRecord<Keys extends SchemaItem, Values extends SchemaItem> extends SchemaItem<Record<SchemaInfer<Keys>, SchemaInfer<Values>>> {
|
||||
|
||||
public constructor(private readonly keys: Keys, private readonly values: Values) {
|
||||
super([keys, values])
|
||||
}
|
||||
|
||||
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<Record<SchemaInfer<Keys>, SchemaInfer<Values>>> {
|
||||
|
||||
// check errors from itself
|
||||
const { valid, object, errors = [] } = super.parse(input, options)
|
||||
|
||||
// skip checking childs if self is not valid (maybe still try to check childs whan fast is false ?)
|
||||
if (!valid) {
|
||||
return {
|
||||
valid,
|
||||
object,
|
||||
errors
|
||||
} as ValidationResult<SchemaInfer<this>>
|
||||
}
|
||||
|
||||
const clone: Partial<SchemaInfer<this>> = {}
|
||||
objectLoop(object, (value, key: string | number) => {
|
||||
const res1 = this.keys.parse(key)
|
||||
const res2 = this.values.parse(value)
|
||||
if (!res1.valid || !res2.valid) {
|
||||
errors.push(...((res1.errors ?? []).concat(...(res2.errors ?? []))).map((it) => ({
|
||||
message: it.message,
|
||||
field: it.field ? `${key as string}.${it.field}` : key.toString()
|
||||
})))
|
||||
} else {
|
||||
// @ts-expect-error normal behavior
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
clone[res1.object] = res2.object
|
||||
}
|
||||
|
||||
// skip completion if fast is enabled
|
||||
return errors.length === 0 || !options?.fast
|
||||
})
|
||||
|
||||
// answer !
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors: errors,
|
||||
object: clone
|
||||
} as ValidationResult<SchemaInfer<this>>
|
||||
}
|
||||
|
||||
public override isOfType(input: unknown): input is Record<SchemaInfer<Keys>, SchemaInfer<Values>> {
|
||||
return isObject(input) && Object.prototype.toString.call(input) === '[object Object]'
|
||||
}
|
||||
}
|
@ -1,16 +1,7 @@
|
||||
import { parceable } from "../Schema"
|
||||
import SchemaItem from "../SchemaItem"
|
||||
import { parceable } from '../Schema'
|
||||
import SchemaItem from '../SchemaItem'
|
||||
|
||||
export default class SchemaString extends SchemaItem<string> {
|
||||
public id = 'string'
|
||||
|
||||
public minLength(value: number, message?: string) {
|
||||
return this.min(value, message)
|
||||
}
|
||||
|
||||
public maxLength(value: number, message?: string) {
|
||||
return this.max(value, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* force the input text to be a minimum of `value` size
|
||||
@ -89,6 +80,13 @@ export default class SchemaString extends SchemaItem<string> {
|
||||
return this
|
||||
}
|
||||
|
||||
public minLength(value: number, message?: string) {
|
||||
return this.min(value, message)
|
||||
}
|
||||
|
||||
public maxLength(value: number, message?: string) {
|
||||
return this.max(value, message)
|
||||
}
|
||||
|
||||
public override isOfType(input: unknown): input is string {
|
||||
return typeof input === 'string'
|
@ -1,8 +1,8 @@
|
||||
import SchemaItem from '../SchemaItem'
|
||||
import { SchemaInfer, ValidationResult } from '../types'
|
||||
|
||||
type ItemType<T extends Array<SchemaItem<any>>> = SchemaInfer<T[number]>
|
||||
export class SchemaUnion<T extends Array<SchemaItem<any>>> extends SchemaItem<ItemType<T>> {
|
||||
type ItemType<T extends Array<SchemaItem>> = SchemaInfer<T[number]>
|
||||
export default class SchemaUnion<T extends Array<SchemaItem>> extends SchemaItem<ItemType<T>> {
|
||||
|
||||
private schemas: T
|
||||
|
||||
@ -14,7 +14,7 @@ export class SchemaUnion<T extends Array<SchemaItem<any>>> extends SchemaItem<It
|
||||
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<SchemaInfer<T[number]>> {
|
||||
|
||||
// check errors from itself
|
||||
let { valid, object, errors = [] } = super.parse(input, options)
|
||||
const { valid, object, errors = [] } = super.parse(input, options)
|
||||
|
||||
// skip checking childs if self is not valid (maybe still try to check childs whan fast is false ?)
|
||||
if (!valid) {
|
57
src/types.d.ts
vendored
Normal file
57
src/types.d.ts
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import SchemaItem from './SchemaItem'
|
||||
|
||||
export type SchemaInfer<Type extends SchemaItem> = Type extends SchemaItem<infer X> ? X : never
|
||||
export type Infer<Type extends SchemaItem> = SchemaInfer<Type>
|
||||
|
||||
export interface ValidationError {
|
||||
message: string
|
||||
field?: string
|
||||
value?: unknown
|
||||
}
|
||||
|
||||
export type ValidationResult<T> = {
|
||||
object: T
|
||||
valid: true
|
||||
errors?: undefined
|
||||
} | {
|
||||
valid: false
|
||||
object?: (T extends object ? Partial<T> : T) | undefined
|
||||
errors: Array<ValidationError>
|
||||
}
|
||||
|
||||
export type ValidationResultOld<T> = {
|
||||
object: T
|
||||
valid: true
|
||||
error?: undefined
|
||||
} | {
|
||||
valid: false
|
||||
object?: (T extends object ? Partial<T> : T) | undefined
|
||||
error: Array<ValidationError>
|
||||
}
|
||||
|
||||
export interface SchemaJSON {
|
||||
i: string
|
||||
a?: Array<string> | undefined
|
||||
c?: Array<unknown> | undefined
|
||||
f?: Array<{
|
||||
n: string
|
||||
a?: Array<unknown> | undefined
|
||||
}> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `SchemaJSON`
|
||||
*/
|
||||
export type SchemaItemJSON = SchemaJSON
|
||||
|
||||
/**
|
||||
* @deprecated use `Record<string, SchemaItem>`
|
||||
*/
|
||||
export type Model = Record<string, SchemaItem>
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type ModelInfer<M extends Model> = {
|
||||
[key in keyof M]: SchemaInfer<M[key]>
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { expect, test } from 'bun:test'
|
||||
import s from '../Schema'
|
||||
import s from '../src/Schema'
|
||||
|
||||
test('number enum', () => {
|
||||
enum Test {
|
||||
@ -74,6 +74,13 @@ test('object', () => {
|
||||
expect(schema.parse({ a: 'a', b: '1' }).valid).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
test('record', () => {
|
||||
const schema = s.record(s.string(), s.number())
|
||||
expect(schema.parse({ a: 1, b: 2 }).valid).toBe(true)
|
||||
expect(schema.parse({ a: 1, b: '1' }).valid).toBe(false)
|
||||
})
|
||||
|
||||
test('string', () => {
|
||||
const schema = s.string()
|
||||
expect(schema.parse('1').valid).toBe(true)
|
||||
|
@ -1,10 +1,34 @@
|
||||
{
|
||||
"exclude": [
|
||||
"cypress"
|
||||
"include": [
|
||||
"src",
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Compilation
|
||||
"baseUrl": "src",
|
||||
"target": "ES2019", // Follow NodeJS oldest supported LTS and use version from https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
"emitDecoratorMetadata": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"pretty": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
// Type Checking
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"alwaysStrict": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
|
29
types.d.ts
vendored
29
types.d.ts
vendored
@ -1,29 +0,0 @@
|
||||
import SchemaItem from "./SchemaItem"
|
||||
|
||||
export type SchemaInfer<Type extends SchemaItem> = Type extends SchemaItem<infer X> ? X : never
|
||||
|
||||
export interface ValidationError {
|
||||
message: string
|
||||
field?: string
|
||||
value?: unknown
|
||||
}
|
||||
|
||||
export type ValidationResult<T> = {
|
||||
object: T
|
||||
valid: true
|
||||
errors?: undefined
|
||||
} | {
|
||||
valid: false
|
||||
object?: (T extends object ? Partial<T> : T) | undefined
|
||||
errors: Array<ValidationError>
|
||||
}
|
||||
|
||||
export interface SchemaJSON<T extends SchemaItem = any> {
|
||||
i: string
|
||||
a?: Array<string> | undefined
|
||||
c?: Array<unknown> | undefined
|
||||
f?: Array<{
|
||||
n: string
|
||||
a?: Array<unknown> | undefined
|
||||
}> | undefined
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user