UNPKG

elysia

Version:

Ergonomic Framework for Human

395 lines (394 loc) 12.4 kB
import { Type, Kind } from "@sinclair/typebox"; import { compile, createType, loadFileType, tryParse, validateFile } from "./utils.mjs"; import { ELYSIA_FORM_DATA, form } from "../utils.mjs"; import { ValidationError } from "../error.mjs"; import { parseDateTimeEmptySpace } from "./format.mjs"; const t = Object.assign({}, Type); createType( "UnionEnum", (schema, value) => (typeof value == "number" || typeof value == "string" || value === null) && schema.enum.includes(value) ), createType( "ArrayBuffer", (schema, value) => value instanceof ArrayBuffer ); const internalFiles = createType( "Files", (options, value) => { if (options.minItems && options.minItems > 1 && !Array.isArray(value)) return !1; if (!Array.isArray(value)) return validateFile(options, value); if (options.minItems && value.length < options.minItems || options.maxItems && value.length > options.maxItems) return !1; for (let i = 0; i < value.length; i++) if (!validateFile(options, value[i])) return !1; return !0; } ), internalFormData = createType( "ElysiaForm", ({ compiler, ...schema }, value) => { if (!(value instanceof FormData)) return !1; if (compiler) { if (!(ELYSIA_FORM_DATA in value)) throw new ValidationError("property", schema, value); if (!compiler.Check(value[ELYSIA_FORM_DATA])) throw compiler.Error(value[ELYSIA_FORM_DATA]); } return !0; } ), ElysiaType = { // @ts-ignore String: (property) => Type.String(property), Numeric: (property) => { const schema = Type.Number(property), compiler = compile(schema); return t.Transform( t.Union( [ t.String({ format: "numeric", default: 0 }), t.Number(property) ], property ) ).Decode((value) => { const number = +value; if (isNaN(number)) return value; if (property && !compiler.Check(number)) throw compiler.Error(number); return number; }).Encode((value) => value); }, NumericEnum(item, property) { const schema = Type.Enum(item, property), compiler = compile(schema); return t.Transform( t.Union([t.String({ format: "numeric" }), t.Number()], property) ).Decode((value) => { const number = +value; if (isNaN(number) || !compiler.Check(number)) throw compiler.Error(number); return number; }).Encode((value) => value); }, Integer: (property) => { const schema = Type.Integer(property), compiler = compile(schema); return t.Transform( t.Union( [ t.String({ format: "integer", default: 0 }), Type.Integer(property) ], property ) ).Decode((value) => { const number = +value; if (!compiler.Check(number)) throw compiler.Error(number); return number; }).Encode((value) => value); }, Date: (property) => { const schema = Type.Date(property), compiler = compile(schema), _default = property?.default ? new Date(property.default) : void 0; return t.Transform( t.Union( [ Type.Date(property), t.String({ format: "date-time", default: _default?.toISOString() }), t.String({ format: "date", default: _default?.toISOString() }), t.Number({ default: _default?.getTime() }) ], property ) ).Decode((value) => { if (typeof value == "number") { const date2 = new Date(value); if (!compiler.Check(date2)) throw compiler.Error(date2); return date2; } if (value instanceof Date) return value; const date = new Date(parseDateTimeEmptySpace(value)); if (!date || isNaN(date.getTime())) throw new ValidationError("property", schema, date); if (!compiler.Check(date)) throw compiler.Error(date); return date; }).Encode((value) => { if (value instanceof Date) return value.toISOString(); if (typeof value == "string") { const parsed = new Date(parseDateTimeEmptySpace(value)); if (isNaN(parsed.getTime())) throw new ValidationError("property", schema, value); return parsed.toISOString(); } if (!compiler.Check(value)) throw compiler.Error(value); return value; }); }, BooleanString: (property) => { const schema = Type.Boolean(property), compiler = compile(schema); return t.Transform( t.Union( [ t.Boolean(property), t.String({ format: "boolean", default: !1 }) ], property ) ).Decode((value) => { if (typeof value == "string") return value === "true"; if (value !== void 0 && !compiler.Check(value)) throw compiler.Error(value); return value; }).Encode((value) => value); }, ObjectString: (properties, options) => { const schema = t.Object(properties, options), compiler = compile(schema); return t.Transform( t.Union( [ t.String({ format: "ObjectString", default: options?.default }), schema ], { elysiaMeta: "ObjectString" } ) ).Decode((value) => { if (typeof value == "string") { if (value.charCodeAt(0) !== 123) throw new ValidationError("property", schema, value); if (!compiler.Check(value = tryParse(value, schema))) throw compiler.Error(value); return compiler.Decode(value); } return value; }).Encode((value) => { let original; if (typeof value == "string" && (value = tryParse(original = value, schema)), !compiler.Check(value)) throw compiler.Error(value); return original ?? JSON.stringify(value); }); }, ArrayString: (children = t.String(), options) => { const schema = t.Array(children, options), compiler = compile(schema), decode = (value, isProperty = !1) => { if (value.charCodeAt(0) === 91) { if (!compiler.Check(value = tryParse(value, schema))) throw compiler.Error(value); return compiler.Decode(value); } if (isProperty) return value; throw new ValidationError("property", schema, value); }; return t.Transform( t.Union( [ t.String({ format: "ArrayString", default: options?.default }), schema ], { elysiaMeta: "ArrayString" } ) ).Decode((value) => { if (Array.isArray(value)) { let values = []; for (let i = 0; i < value.length; i++) { const v = value[i]; if (typeof v == "string") { const t2 = decode(v, !0); Array.isArray(t2) ? values = values.concat(t2) : values.push(t2); continue; } values.push(v); } return values; } return typeof value == "string" ? decode(value) : value; }).Encode((value) => { let original; if (typeof value == "string" && (value = tryParse(original = value, schema)), !compiler.Check(value)) throw new ValidationError("property", schema, value); return original ?? JSON.stringify(value); }); }, ArrayQuery: (children = t.String(), options) => { const schema = t.Array(children, options), compiler = compile(schema), decode = (value) => value.indexOf(",") !== -1 ? compiler.Decode(value.split(",")) : [value]; return t.Transform( t.Union( [ t.String({ default: options?.default }), schema ], { elysiaMeta: "ArrayQuery" } ) ).Decode((value) => { if (Array.isArray(value)) { let values = []; for (let i = 0; i < value.length; i++) { const v = value[i]; if (typeof v == "string") { const t2 = decode(v); Array.isArray(t2) ? values = values.concat(t2) : values.push(t2); continue; } values.push(v); } return values; } return typeof value == "string" ? decode(value) : value; }).Encode((value) => { let original; if (typeof value == "string" && (value = tryParse(original = value, schema)), !compiler.Check(value)) throw new ValidationError("property", schema, value); return original ?? JSON.stringify(value); }); }, File: createType( "File", validateFile ), Files: (options = {}) => t.Transform(internalFiles(options)).Decode((value) => Array.isArray(value) ? value : [value]).Encode((value) => value), Nullable: (schema, options) => t.Union([schema, t.Null()], { ...options, nullable: !0 }), /** * Allow Optional, Nullable and Undefined */ MaybeEmpty: (schema, options) => t.Union([schema, t.Null(), t.Undefined()], options), Cookie: (properties, { domain, expires, httpOnly, maxAge, path, priority, sameSite, secure, secrets, sign, ...options } = {}) => { const v = t.Object(properties, options); return v.config = { domain, expires, httpOnly, maxAge, path, priority, sameSite, secure, secrets, sign }, v; }, UnionEnum: (values, options = {}) => { const type = values.every((value) => typeof value == "string") ? { type: "string" } : values.every((value) => typeof value == "number") ? { type: "number" } : values.every((value) => value === null) ? { type: "null" } : {}; if (values.some((x) => typeof x == "object" && x !== null)) throw new Error("This type does not support objects or arrays"); return { // default is need for generating error message default: values[0], ...options, [Kind]: "UnionEnum", ...type, enum: values }; }, NoValidate: (v, enabled = !0) => (v.noValidate = enabled, v), Form: (v, options = {}) => { const schema = t.Object(v, { default: form({}), ...options }), compiler = compile(schema); return t.Union([ schema, // @ts-expect-error internalFormData({ compiler }) ]); }, ArrayBuffer(options = {}) { return { // default is need for generating error message default: [1, 2, 3], ...options, [Kind]: "ArrayBuffer" }; }, Uint8Array: (options) => { const schema = Type.Uint8Array(options), compiler = compile(schema); return t.Transform(t.Union([t.ArrayBuffer(), Type.Uint8Array(options)])).Decode((value) => { if (value instanceof ArrayBuffer) { if (!compiler.Check(value = new Uint8Array(value))) throw compiler.Error(value); return value; } return value; }).Encode((value) => value); } }; t.BooleanString = ElysiaType.BooleanString, t.ObjectString = ElysiaType.ObjectString, t.ArrayString = ElysiaType.ArrayString, t.ArrayQuery = ElysiaType.ArrayQuery, t.Numeric = ElysiaType.Numeric, t.NumericEnum = ElysiaType.NumericEnum, t.Integer = ElysiaType.Integer, t.File = (arg) => (arg?.type && loadFileType(), ElysiaType.File({ default: "File", ...arg, extension: arg?.type, type: "string", format: "binary" })), t.Files = (arg) => (arg?.type && loadFileType(), ElysiaType.Files({ ...arg, elysiaMeta: "Files", default: "Files", extension: arg?.type, type: "array", items: { ...arg, default: "Files", type: "string", format: "binary" } })), t.Nullable = ElysiaType.Nullable, t.MaybeEmpty = ElysiaType.MaybeEmpty, t.Cookie = ElysiaType.Cookie, t.Date = ElysiaType.Date, t.UnionEnum = ElysiaType.UnionEnum, t.NoValidate = ElysiaType.NoValidate, t.Form = ElysiaType.Form, t.ArrayBuffer = ElysiaType.ArrayBuffer, t.Uint8Array = ElysiaType.Uint8Array; import { TypeSystemPolicy, TypeSystem, TypeSystemDuplicateFormat, TypeSystemDuplicateTypeKind } from "@sinclair/typebox/system"; import { TypeRegistry, FormatRegistry } from "@sinclair/typebox"; import { TypeCompiler, TypeCheck } from "@sinclair/typebox/compiler"; export { ElysiaType, FormatRegistry, TypeCheck, TypeCompiler, TypeRegistry, TypeSystem, TypeSystemDuplicateFormat, TypeSystemDuplicateTypeKind, TypeSystemPolicy, t };