schema/src/Schema.ts

247 lines
6.3 KiB
TypeScript

/* eslint-disable id-blacklist */
import { parseForm, parseFormData, parseQuery } from 'helpers'
import SchemaAny from 'items/any'
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 = {
Any: SchemaAny,
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 any() {
return new SchemaAny()
}
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 {
SchemaAny, SchemaArray,
SchemaBoolean, SchemaDate, SchemaEnum, SchemaItem, SchemaLiteral,
SchemaNullable,
SchemaNumber, SchemaObject, SchemaRecord, SchemaString,
SchemaUnion
}