mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-03 08:36:01 +00:00
initial refactor
This commit is contained in:
@@ -26,7 +26,7 @@ export {
|
||||
} from "./object/query/query";
|
||||
export { Registry, type Constructor } from "./registry/Registry";
|
||||
export { getFlashMessage } from "./server/flash";
|
||||
export {
|
||||
/* export {
|
||||
s,
|
||||
parse,
|
||||
jsc,
|
||||
@@ -35,7 +35,7 @@ export {
|
||||
openAPISpecs,
|
||||
type ParseOptions,
|
||||
InvalidSchemaError,
|
||||
} from "./object/schema";
|
||||
} from "./object/schema"; */
|
||||
|
||||
export * from "./drivers";
|
||||
export * from "./console";
|
||||
|
||||
@@ -1,43 +1,39 @@
|
||||
import { get, has, omit, set } from "lodash-es";
|
||||
import {
|
||||
Default,
|
||||
type Static,
|
||||
type TObject,
|
||||
getFullPathKeys,
|
||||
mergeObjectWith,
|
||||
parse,
|
||||
stripMark,
|
||||
} from "../utils";
|
||||
import { getFullPathKeys, mergeObjectWith } from "../utils";
|
||||
import { type s, parse, stripMark } from "core/object/schema";
|
||||
|
||||
export type SchemaObjectOptions<Schema extends TObject> = {
|
||||
onUpdate?: (config: Static<Schema>) => void | Promise<void>;
|
||||
export type SchemaObjectOptions<Schema extends s.Schema> = {
|
||||
onUpdate?: (config: s.Static<Schema>) => void | Promise<void>;
|
||||
onBeforeUpdate?: (
|
||||
from: Static<Schema>,
|
||||
to: Static<Schema>,
|
||||
) => Static<Schema> | Promise<Static<Schema>>;
|
||||
from: s.Static<Schema>,
|
||||
to: s.Static<Schema>,
|
||||
) => s.Static<Schema> | Promise<s.Static<Schema>>;
|
||||
restrictPaths?: string[];
|
||||
overwritePaths?: (RegExp | string)[];
|
||||
forceParse?: boolean;
|
||||
};
|
||||
|
||||
export class SchemaObject<Schema extends TObject> {
|
||||
private readonly _default: Partial<Static<Schema>>;
|
||||
private _value: Static<Schema>;
|
||||
private _config: Static<Schema>;
|
||||
type TSchema = s.ObjectSchema<any>;
|
||||
|
||||
export class SchemaObject<Schema extends TSchema = TSchema> {
|
||||
private readonly _default: Partial<s.Static<Schema>>;
|
||||
private _value: s.Static<Schema>;
|
||||
private _config: s.Static<Schema>;
|
||||
private _restriction_bypass: boolean = false;
|
||||
|
||||
constructor(
|
||||
private _schema: Schema,
|
||||
initial?: Partial<Static<Schema>>,
|
||||
initial?: Partial<s.Static<Schema>>,
|
||||
private options?: SchemaObjectOptions<Schema>,
|
||||
) {
|
||||
this._default = Default(_schema, {} as any) as any;
|
||||
this._default = _schema.template() as any;
|
||||
this._value = initial
|
||||
? parse(_schema, structuredClone(initial as any), {
|
||||
withDefaults: true,
|
||||
forceParse: this.isForceParse(),
|
||||
skipMark: this.isForceParse(),
|
||||
})
|
||||
: this._default;
|
||||
: (this._default as any);
|
||||
this._config = Object.freeze(this._value);
|
||||
}
|
||||
|
||||
@@ -45,18 +41,21 @@ export class SchemaObject<Schema extends TObject> {
|
||||
return this.options?.forceParse ?? true;
|
||||
}
|
||||
|
||||
default(): Static<Schema> {
|
||||
default() {
|
||||
return this._default;
|
||||
}
|
||||
|
||||
private async onBeforeUpdate(from: Static<Schema>, to: Static<Schema>): Promise<Static<Schema>> {
|
||||
private async onBeforeUpdate(
|
||||
from: s.Static<Schema>,
|
||||
to: s.Static<Schema>,
|
||||
): Promise<s.Static<Schema>> {
|
||||
if (this.options?.onBeforeUpdate) {
|
||||
return this.options.onBeforeUpdate(from, to);
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
get(options?: { stripMark?: boolean }): Static<Schema> {
|
||||
get(options?: { stripMark?: boolean }): s.Static<Schema> {
|
||||
if (options?.stripMark) {
|
||||
return stripMark(this._config);
|
||||
}
|
||||
@@ -68,7 +67,7 @@ export class SchemaObject<Schema extends TObject> {
|
||||
return structuredClone(this._config);
|
||||
}
|
||||
|
||||
async set(config: Static<Schema>, noEmit?: boolean): Promise<Static<Schema>> {
|
||||
async set(config: s.Static<Schema>, noEmit?: boolean): Promise<s.Static<Schema>> {
|
||||
const valid = parse(this._schema, structuredClone(config) as any, {
|
||||
forceParse: true,
|
||||
skipMark: this.isForceParse(),
|
||||
@@ -118,9 +117,9 @@ export class SchemaObject<Schema extends TObject> {
|
||||
return;
|
||||
}
|
||||
|
||||
async patch(path: string, value: any): Promise<[Partial<Static<Schema>>, Static<Schema>]> {
|
||||
async patch(path: string, value: any): Promise<[Partial<s.Static<Schema>>, s.Static<Schema>]> {
|
||||
const current = this.clone();
|
||||
const partial = path.length > 0 ? (set({}, path, value) as Partial<Static<Schema>>) : value;
|
||||
const partial = path.length > 0 ? (set({}, path, value) as Partial<s.Static<Schema>>) : value;
|
||||
|
||||
this.throwIfRestricted(partial);
|
||||
|
||||
@@ -168,9 +167,12 @@ export class SchemaObject<Schema extends TObject> {
|
||||
return [partial, newConfig];
|
||||
}
|
||||
|
||||
async overwrite(path: string, value: any): Promise<[Partial<Static<Schema>>, Static<Schema>]> {
|
||||
async overwrite(
|
||||
path: string,
|
||||
value: any,
|
||||
): Promise<[Partial<s.Static<Schema>>, s.Static<Schema>]> {
|
||||
const current = this.clone();
|
||||
const partial = path.length > 0 ? (set({}, path, value) as Partial<Static<Schema>>) : value;
|
||||
const partial = path.length > 0 ? (set({}, path, value) as Partial<s.Static<Schema>>) : value;
|
||||
|
||||
this.throwIfRestricted(partial);
|
||||
|
||||
@@ -194,7 +196,7 @@ export class SchemaObject<Schema extends TObject> {
|
||||
return has(this._config, path);
|
||||
}
|
||||
|
||||
async remove(path: string): Promise<[Partial<Static<Schema>>, Static<Schema>]> {
|
||||
async remove(path: string): Promise<[Partial<s.Static<Schema>>, s.Static<Schema>]> {
|
||||
this.throwIfRestricted(path);
|
||||
|
||||
if (!this.has(path)) {
|
||||
@@ -202,9 +204,9 @@ export class SchemaObject<Schema extends TObject> {
|
||||
}
|
||||
|
||||
const current = this.clone();
|
||||
const removed = get(current, path) as Partial<Static<Schema>>;
|
||||
const removed = get(current, path) as Partial<s.Static<Schema>>;
|
||||
const config = omit(current, path);
|
||||
const newConfig = await this.set(config);
|
||||
const newConfig = await this.set(config as any);
|
||||
return [removed, newConfig];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { mergeObject } from "core/utils";
|
||||
|
||||
//export { jsc, type Options, type Hook } from "./validator";
|
||||
import * as s from "jsonv-ts";
|
||||
|
||||
export { validator as jsc, type Options } from "jsonv-ts/hono";
|
||||
@@ -8,9 +5,18 @@ export { describeRoute, schemaToSpec, openAPISpecs } from "jsonv-ts/hono";
|
||||
|
||||
export { s };
|
||||
|
||||
export const stripMark = <O extends object>(o: O): O => o;
|
||||
export const mark = <O extends object>(o: O): O => o;
|
||||
|
||||
export const stringIdentifier = s.string({
|
||||
pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$",
|
||||
minLength: 2,
|
||||
maxLength: 150,
|
||||
});
|
||||
|
||||
export class InvalidSchemaError extends Error {
|
||||
constructor(
|
||||
public schema: s.TAnySchema,
|
||||
public schema: s.Schema,
|
||||
public value: unknown,
|
||||
public errors: s.ErrorDetail[] = [],
|
||||
) {
|
||||
@@ -19,33 +25,52 @@ export class InvalidSchemaError extends Error {
|
||||
`Error: ${JSON.stringify(errors[0], null, 2)}`,
|
||||
);
|
||||
}
|
||||
|
||||
first() {
|
||||
return this.errors[0]!;
|
||||
}
|
||||
|
||||
firstToString() {
|
||||
const first = this.first();
|
||||
return `${first.error} at ${first.instanceLocation}`;
|
||||
}
|
||||
}
|
||||
|
||||
export type ParseOptions = {
|
||||
withDefaults?: boolean;
|
||||
coerse?: boolean;
|
||||
coerce?: boolean;
|
||||
clone?: boolean;
|
||||
skipMark?: boolean; // @todo: do something with this
|
||||
forceParse?: boolean; // @todo: do something with this
|
||||
onError?: (errors: s.ErrorDetail[]) => void;
|
||||
};
|
||||
|
||||
export const cloneSchema = <S extends s.TSchema>(schema: S): S => {
|
||||
export const cloneSchema = <S extends s.Schema>(schema: S): S => {
|
||||
const json = schema.toJSON();
|
||||
return s.fromSchema(json) as S;
|
||||
};
|
||||
|
||||
export function parse<S extends s.TAnySchema>(
|
||||
export function parse<S extends s.Schema, Options extends ParseOptions = ParseOptions>(
|
||||
_schema: S,
|
||||
v: unknown,
|
||||
opts: ParseOptions = {},
|
||||
): s.StaticCoerced<S> {
|
||||
const schema = (opts.clone ? cloneSchema(_schema as any) : _schema) as s.TSchema;
|
||||
const value = opts.coerse !== false ? schema.coerce(v) : v;
|
||||
opts?: Options,
|
||||
): Options extends { coerce: true } ? s.StaticCoerced<S> : s.Static<S> {
|
||||
const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema;
|
||||
let value = opts?.coerce !== false ? schema.coerce(v) : v;
|
||||
if (opts?.withDefaults) {
|
||||
value = schema.template(value, { withOptional: true });
|
||||
}
|
||||
|
||||
const result = schema.validate(value, {
|
||||
shortCircuit: true,
|
||||
ignoreUnsupported: true,
|
||||
});
|
||||
if (!result.valid) throw new InvalidSchemaError(schema, v, result.errors);
|
||||
if (opts.withDefaults) {
|
||||
return mergeObject(schema.template({ withOptional: true }), value) as any;
|
||||
if (!result.valid) {
|
||||
if (opts?.onError) {
|
||||
opts.onError(result.errors);
|
||||
} else {
|
||||
throw new InvalidSchemaError(schema, v, result.errors);
|
||||
}
|
||||
}
|
||||
|
||||
return value as any;
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import type { Context, Env, Input, MiddlewareHandler, ValidationTargets } from "hono";
|
||||
import { validator as honoValidator } from "hono/validator";
|
||||
import type { Static, StaticCoerced, TAnySchema } from "jsonv-ts";
|
||||
|
||||
export type Options = {
|
||||
coerce?: boolean;
|
||||
includeSchema?: boolean;
|
||||
};
|
||||
|
||||
type ValidationResult = {
|
||||
valid: boolean;
|
||||
errors: {
|
||||
keywordLocation: string;
|
||||
instanceLocation: string;
|
||||
error: string;
|
||||
data?: unknown;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type Hook<T, E extends Env, P extends string> = (
|
||||
result: { result: ValidationResult; data: T },
|
||||
c: Context<E, P>,
|
||||
) => Response | Promise<Response> | void;
|
||||
|
||||
export const validator = <
|
||||
// @todo: somehow hono prevents the usage of TSchema
|
||||
Schema extends TAnySchema,
|
||||
Target extends keyof ValidationTargets,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Opts extends Options = Options,
|
||||
Out = Opts extends { coerce: false } ? Static<Schema> : StaticCoerced<Schema>,
|
||||
I extends Input = {
|
||||
in: { [K in Target]: Static<Schema> };
|
||||
out: { [K in Target]: Out };
|
||||
},
|
||||
>(
|
||||
target: Target,
|
||||
schema: Schema,
|
||||
options?: Opts,
|
||||
hook?: Hook<Out, E, P>,
|
||||
): MiddlewareHandler<E, P, I> => {
|
||||
// @ts-expect-error not typed well
|
||||
return honoValidator(target, async (_value, c) => {
|
||||
const value = options?.coerce !== false ? schema.coerce(_value) : _value;
|
||||
// @ts-ignore
|
||||
const result = schema.validate(value);
|
||||
if (!result.valid) {
|
||||
return c.json({ ...result, schema }, 400);
|
||||
}
|
||||
|
||||
if (hook) {
|
||||
const hookResult = hook({ result, data: value as Out }, c);
|
||||
if (hookResult) {
|
||||
return hookResult;
|
||||
}
|
||||
}
|
||||
|
||||
return value as Out;
|
||||
});
|
||||
};
|
||||
|
||||
export const jsc = validator;
|
||||
@@ -5,7 +5,7 @@ export * from "./perf";
|
||||
export * from "./file";
|
||||
export * from "./reqres";
|
||||
export * from "./xml";
|
||||
export type { Prettify, PrettifyRec } from "./types";
|
||||
export type { Prettify, PrettifyRec, RecursivePartial } from "./types";
|
||||
export * from "./typebox";
|
||||
export * from "./dates";
|
||||
export * from "./crypto";
|
||||
|
||||
@@ -14,16 +14,9 @@ import {
|
||||
type ValueErrorIterator,
|
||||
} from "@sinclair/typebox/errors";
|
||||
import { Check, Default, Value, type ValueError } from "@sinclair/typebox/value";
|
||||
import type { RecursivePartial } from "../types";
|
||||
|
||||
export type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
: T[P] extends object | undefined
|
||||
? RecursivePartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
type ParseOptions = {
|
||||
/* type ParseOptions = {
|
||||
useDefaults?: boolean;
|
||||
decode?: boolean;
|
||||
onError?: (errors: ValueErrorIterator) => void;
|
||||
@@ -198,4 +191,4 @@ SetErrorFunction((error) => {
|
||||
|
||||
export type { Static, StaticDecode, TSchema, TObject, ValueError, SchemaOptions };
|
||||
|
||||
export { Value, Default, Errors, Check };
|
||||
export { Value, Default, Errors, Check }; */
|
||||
|
||||
Vendored
+8
@@ -6,3 +6,11 @@ export type Prettify<T> = {
|
||||
export type PrettifyRec<T> = {
|
||||
[K in keyof T]: T[K] extends object ? Prettify<T[K]> : T[K];
|
||||
} & NonNullable<unknown>;
|
||||
|
||||
export type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
: T[P] extends object | undefined
|
||||
? RecursivePartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user