schema/Schema.ts

166 lines
4.1 KiB
TypeScript

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
}
}