initial refactor

This commit is contained in:
dswbx
2025-06-21 13:35:58 +02:00
parent b2086c4da7
commit 42edce904f
93 changed files with 1021 additions and 1216 deletions
+2 -2
View File
@@ -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";
+34 -32
View File
@@ -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];
}
}
+39 -14
View File
@@ -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;
-63
View File
@@ -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;
+1 -1
View File
@@ -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";
+3 -10
View File
@@ -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 }; */
+8
View File
@@ -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];
};