mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
moved main pkg back to deps to reduce tarball size + removed zod + cleanup old code + fixed tests
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import { describe, expect, it, test } from "bun:test";
|
||||
import { Endpoint } from "../../src/core";
|
||||
import { mockFetch2, unmockFetch } from "./helper";
|
||||
|
||||
const testC: any = {
|
||||
json: (res: any) => Response.json(res)
|
||||
};
|
||||
const testNext = async () => {};
|
||||
|
||||
describe("Endpoint", async () => {
|
||||
it("behaves as expected", async () => {
|
||||
const endpoint = new Endpoint("GET", "/test", async () => {
|
||||
return { hello: "test" };
|
||||
});
|
||||
|
||||
expect(endpoint.method).toBe("GET");
|
||||
expect(endpoint.path).toBe("/test");
|
||||
|
||||
const handler = endpoint.toHandler();
|
||||
const response = await handler(testC, testNext);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(await response.json()).toEqual({ hello: "test" });
|
||||
});
|
||||
|
||||
it("can be $request(ed)", async () => {
|
||||
const obj = { hello: "test" };
|
||||
const baseUrl = "https://local.com:123";
|
||||
const endpoint = Endpoint.get("/test", async () => obj);
|
||||
|
||||
mockFetch2(async (input: RequestInfo, init: RequestInit) => {
|
||||
expect(input).toBe(`${baseUrl}/test`);
|
||||
return new Response(JSON.stringify(obj), { status: 200 });
|
||||
});
|
||||
const response = await endpoint.$request({}, baseUrl);
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
ok: true,
|
||||
response: obj
|
||||
});
|
||||
unmockFetch();
|
||||
});
|
||||
|
||||
it("resolves helper functions", async () => {
|
||||
const params = ["/test", () => ({ hello: "test" })];
|
||||
|
||||
["get", "post", "patch", "put", "delete"].forEach((method) => {
|
||||
const endpoint = Endpoint[method](...params);
|
||||
expect(endpoint.method).toBe(method.toUpperCase());
|
||||
expect(endpoint.path).toBe(params[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { type ObjectQuery, convert, validate } from "../../../src/core/object/query/object-query";
|
||||
import { deprecated__whereRepoSchema } from "../../../src/data";
|
||||
|
||||
describe("object-query", () => {
|
||||
const q: ObjectQuery = { name: "Michael" };
|
||||
@@ -8,19 +7,6 @@ describe("object-query", () => {
|
||||
const q3: ObjectQuery = { name: "Michael", age: { $gt: 18 } };
|
||||
const bag = { q, q2, q3 };
|
||||
|
||||
test("translates into legacy", async () => {
|
||||
for (const [key, value] of Object.entries(bag)) {
|
||||
const obj = convert(value);
|
||||
try {
|
||||
const parsed = deprecated__whereRepoSchema.parse(obj);
|
||||
expect(parsed).toBeDefined();
|
||||
} catch (e) {
|
||||
console.log("errored", { obj, value });
|
||||
console.error(key, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("validates", async () => {
|
||||
const converted = convert({
|
||||
name: { $eq: "ch" }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"bin": "./dist/cli/index.js",
|
||||
"version": "0.2.3-rc2",
|
||||
"version": "0.2.3-rc3",
|
||||
"scripts": {
|
||||
"build:all": "bun run build && bun run build:cli",
|
||||
"dev": "vite",
|
||||
@@ -23,7 +23,17 @@
|
||||
"license": "FSL-1.1-MIT",
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@tanstack/react-form": "0.19.2"
|
||||
"@tanstack/react-form": "0.19.2",
|
||||
"@sinclair/typebox": "^0.32.34",
|
||||
"kysely": "^0.27.4",
|
||||
"liquidjs": "^10.15.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"hono": "^4.6.12",
|
||||
"fast-xml-parser": "^4.4.0",
|
||||
"@cfworker/json-schema": "^2.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"oauth4webapi": "^2.11.1",
|
||||
"aws4fetch": "^1.0.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@libsql/kysely-libsql": "^0.4.1",
|
||||
@@ -33,28 +43,17 @@
|
||||
"@mantine/notifications": "^7.13.5",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@rjsf/core": "^5.22.2",
|
||||
"@sinclair/typebox": "^0.32.34",
|
||||
"@tabler/icons-react": "3.18.0",
|
||||
"@tanstack/react-query": "^5.59.16",
|
||||
"@uiw/react-codemirror": "^4.23.6",
|
||||
"@xyflow/react": "^12.3.2",
|
||||
"aws4fetch": "^1.0.18",
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^4.4.0",
|
||||
"hono": "^4.6.12",
|
||||
"jotai": "^2.10.1",
|
||||
"kysely": "^0.27.4",
|
||||
"liquidjs": "^10.15.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"oauth4webapi": "^2.11.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-icons": "5.2.1",
|
||||
"react-json-view-lite": "^2.0.1",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"wouter": "^3.3.5",
|
||||
"zod": "^3.23.8",
|
||||
"@cfworker/json-schema": "^2.0.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-liquid": "^6.2.1",
|
||||
|
||||
@@ -189,7 +189,11 @@ export class DurableBkndApp extends DurableObject {
|
||||
const config = options.config;
|
||||
|
||||
// change protocol to websocket if libsql
|
||||
if ("type" in config.connection && config.connection.type === "libsql") {
|
||||
if (
|
||||
config?.connection &&
|
||||
"type" in config.connection &&
|
||||
config.connection.type === "libsql"
|
||||
) {
|
||||
config.connection.config.protocol = "wss";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { Endpoint, type RequestResponse, type Middleware } from "./server/Endpoint";
|
||||
export { zValidator } from "./server/lib/zValidator";
|
||||
import type { Hono, MiddlewareHandler } from "hono";
|
||||
|
||||
export { tbValidator } from "./server/lib/tbValidator";
|
||||
export { Exception, BkndError } from "./errors";
|
||||
export { isDebug } from "./env";
|
||||
@@ -11,7 +11,6 @@ export {
|
||||
type TemplateTypes,
|
||||
type SimpleRendererOptions
|
||||
} from "./template/SimpleRenderer";
|
||||
export { Controller, type ClassController } from "./server/Controller";
|
||||
export { SchemaObject } from "./object/SchemaObject";
|
||||
export { DebugLogger } from "./utils/DebugLogger";
|
||||
export { Permission } from "./security/Permission";
|
||||
@@ -26,3 +25,10 @@ export {
|
||||
isBooleanLike
|
||||
} from "./object/query/query";
|
||||
export { Registry, type Constructor } from "./registry/Registry";
|
||||
|
||||
// compatibility
|
||||
export type Middleware = MiddlewareHandler<any, any, any>;
|
||||
export interface ClassController {
|
||||
getController: () => Hono<any, any, any>;
|
||||
getMiddleware?: MiddlewareHandler<any, any, any>;
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import { Hono, type MiddlewareHandler, type ValidationTargets } from "hono";
|
||||
import type { H } from "hono/types";
|
||||
import { safelyParseObjectValues } from "../utils";
|
||||
import type { Endpoint, Middleware } from "./Endpoint";
|
||||
import { zValidator } from "./lib/zValidator";
|
||||
|
||||
type RouteProxy<Endpoints> = {
|
||||
[K in keyof Endpoints]: Endpoints[K];
|
||||
};
|
||||
|
||||
export interface ClassController {
|
||||
getController: () => Hono<any, any, any>;
|
||||
getMiddleware?: MiddlewareHandler<any, any, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class Controller<
|
||||
Endpoints extends Record<string, Endpoint> = Record<string, Endpoint>,
|
||||
Middlewares extends Record<string, Middleware> = Record<string, Middleware>
|
||||
> {
|
||||
protected endpoints: Endpoints = {} as Endpoints;
|
||||
protected middlewares: Middlewares = {} as Middlewares;
|
||||
|
||||
public prefix: string = "/";
|
||||
public routes: RouteProxy<Endpoints>;
|
||||
|
||||
constructor(
|
||||
prefix: string = "/",
|
||||
endpoints: Endpoints = {} as Endpoints,
|
||||
middlewares: Middlewares = {} as Middlewares
|
||||
) {
|
||||
this.prefix = prefix;
|
||||
this.endpoints = endpoints;
|
||||
this.middlewares = middlewares;
|
||||
|
||||
this.routes = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_, name: string) => {
|
||||
return this.endpoints[name];
|
||||
}
|
||||
}
|
||||
) as RouteProxy<Endpoints>;
|
||||
}
|
||||
|
||||
add<Name extends string, E extends Endpoint>(
|
||||
this: Controller<Endpoints>,
|
||||
name: Name,
|
||||
endpoint: E
|
||||
): Controller<Endpoints & Record<Name, E>> {
|
||||
const newEndpoints = {
|
||||
...this.endpoints,
|
||||
[name]: endpoint
|
||||
} as Endpoints & Record<Name, E>;
|
||||
const newController: Controller<Endpoints & Record<Name, E>> = new Controller<
|
||||
Endpoints & Record<Name, E>
|
||||
>();
|
||||
newController.endpoints = newEndpoints;
|
||||
newController.middlewares = this.middlewares;
|
||||
return newController;
|
||||
}
|
||||
|
||||
get<Name extends keyof Endpoints>(name: Name): Endpoints[Name] {
|
||||
return this.endpoints[name];
|
||||
}
|
||||
|
||||
honoify(_hono: Hono = new Hono()) {
|
||||
const hono = _hono.basePath(this.prefix);
|
||||
|
||||
// apply middlewares
|
||||
for (const m_name in this.middlewares) {
|
||||
const middleware = this.middlewares[m_name];
|
||||
|
||||
if (typeof middleware === "function") {
|
||||
//if (isDebug()) console.log("+++ appyling middleware", m_name, middleware);
|
||||
hono.use(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
// apply endpoints
|
||||
for (const name in this.endpoints) {
|
||||
const endpoint = this.endpoints[name];
|
||||
if (!endpoint) continue;
|
||||
|
||||
const handlers: H[] = [];
|
||||
|
||||
const supportedValidations: Array<keyof ValidationTargets> = ["param", "query", "json"];
|
||||
|
||||
// if validations are present, add them to the handlers
|
||||
for (const validation of supportedValidations) {
|
||||
if (endpoint.validation[validation]) {
|
||||
handlers.push(async (c, next) => {
|
||||
// @todo: potentially add "strict" to all schemas?
|
||||
const res = await zValidator(
|
||||
validation,
|
||||
endpoint.validation[validation] as any,
|
||||
(target, value, c) => {
|
||||
if (["query", "param"].includes(target)) {
|
||||
return safelyParseObjectValues(value);
|
||||
}
|
||||
//console.log("preprocess", target, value, c.req.raw.url);
|
||||
return value;
|
||||
}
|
||||
)(c, next);
|
||||
|
||||
if (res instanceof Response && res.status === 400) {
|
||||
const error = await res.json();
|
||||
return c.json(
|
||||
{
|
||||
error: "Validation error",
|
||||
target: validation,
|
||||
message: error
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// add actual handler
|
||||
handlers.push(endpoint.toHandler());
|
||||
|
||||
const method = endpoint.method.toLowerCase() as
|
||||
| "get"
|
||||
| "post"
|
||||
| "put"
|
||||
| "delete"
|
||||
| "patch";
|
||||
|
||||
//if (isDebug()) console.log("--- adding", method, endpoint.path);
|
||||
hono[method](endpoint.path, ...handlers);
|
||||
}
|
||||
|
||||
return hono;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const endpoints: any = {};
|
||||
for (const name in this.endpoints) {
|
||||
const endpoint = this.endpoints[name];
|
||||
if (!endpoint) continue;
|
||||
|
||||
endpoints[name] = {
|
||||
method: endpoint.method,
|
||||
path: (this.prefix + endpoint.path).replace("//", "/")
|
||||
};
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import type { Context, MiddlewareHandler, Next, ValidationTargets } from "hono";
|
||||
import type { Handler } from "hono/types";
|
||||
import { encodeSearch, replaceUrlParam } from "../utils";
|
||||
import type { Prettify } from "../utils";
|
||||
|
||||
type ZodSchema = { [key: string]: any };
|
||||
|
||||
type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||||
type Validation<P, Q, B> = {
|
||||
[K in keyof ValidationTargets]?: any;
|
||||
} & {
|
||||
param?: P extends ZodSchema ? P : undefined;
|
||||
query?: Q extends ZodSchema ? Q : undefined;
|
||||
json?: B extends ZodSchema ? B : undefined;
|
||||
};
|
||||
|
||||
type ValidationInput<P, Q, B> = {
|
||||
param?: P extends ZodSchema ? P["_input"] : undefined;
|
||||
query?: Q extends ZodSchema ? Q["_input"] : undefined;
|
||||
json?: B extends ZodSchema ? B["_input"] : undefined;
|
||||
};
|
||||
|
||||
type HonoEnv = any;
|
||||
|
||||
export type Middleware = MiddlewareHandler<any, any, any>;
|
||||
|
||||
type HandlerFunction<P extends string, R> = (c: Context<HonoEnv, P, any>, next: Next) => R;
|
||||
export type RequestResponse<R> = {
|
||||
status: number;
|
||||
ok: boolean;
|
||||
response: Awaited<R>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class Endpoint<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
> {
|
||||
constructor(
|
||||
readonly method: Method,
|
||||
readonly path: Path,
|
||||
readonly handler: HandlerFunction<Path, R>,
|
||||
readonly validation: Validation<P, Q, B> = {}
|
||||
) {}
|
||||
|
||||
// @todo: typing is not ideal
|
||||
async $request(
|
||||
args?: ValidationInput<P, Q, B>,
|
||||
baseUrl: string = "http://localhost:28623"
|
||||
): Promise<Prettify<RequestResponse<R>>> {
|
||||
let path = this.path as string;
|
||||
if (args?.param) {
|
||||
path = replaceUrlParam(path, args.param);
|
||||
}
|
||||
|
||||
if (args?.query) {
|
||||
path += "?" + encodeSearch(args.query);
|
||||
}
|
||||
|
||||
const url = [baseUrl, path].join("").replace(/\/$/, "");
|
||||
const options: RequestInit = {
|
||||
method: this.method,
|
||||
headers: {} as any
|
||||
};
|
||||
|
||||
if (!["GET", "HEAD"].includes(this.method)) {
|
||||
if (args?.json) {
|
||||
options.body = JSON.stringify(args.json);
|
||||
options.headers!["Content-Type"] = "application/json";
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url, options);
|
||||
return {
|
||||
status: res.status,
|
||||
ok: res.ok,
|
||||
response: (await res.json()) as any
|
||||
};
|
||||
}
|
||||
|
||||
toHandler(): Handler {
|
||||
return async (c, next) => {
|
||||
const res = await this.handler(c, next);
|
||||
//console.log("toHandler:isResponse", res instanceof Response);
|
||||
//return res;
|
||||
if (res instanceof Response) {
|
||||
return res;
|
||||
}
|
||||
return c.json(res as any) as unknown as Handler;
|
||||
};
|
||||
}
|
||||
|
||||
static get<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
>(path: Path, handler: HandlerFunction<Path, R>, validation?: Validation<P, Q, B>) {
|
||||
return new Endpoint<Path, P, Q, B, R>("GET", path, handler, validation);
|
||||
}
|
||||
|
||||
static post<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
>(path: Path, handler: HandlerFunction<Path, R>, validation?: Validation<P, Q, B>) {
|
||||
return new Endpoint<Path, P, Q, B, R>("POST", path, handler, validation);
|
||||
}
|
||||
|
||||
static patch<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
>(path: Path, handler: HandlerFunction<Path, R>, validation?: Validation<P, Q, B>) {
|
||||
return new Endpoint<Path, P, Q, B, R>("PATCH", path, handler, validation);
|
||||
}
|
||||
|
||||
static put<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
>(path: Path, handler: HandlerFunction<Path, R>, validation?: Validation<P, Q, B>) {
|
||||
return new Endpoint<Path, P, Q, B, R>("PUT", path, handler, validation);
|
||||
}
|
||||
|
||||
static delete<
|
||||
Path extends string = any,
|
||||
P extends ZodSchema = any,
|
||||
Q extends ZodSchema = any,
|
||||
B extends ZodSchema = any,
|
||||
R = any
|
||||
>(path: Path, handler: HandlerFunction<Path, R>, validation?: Validation<P, Q, B>) {
|
||||
return new Endpoint<Path, P, Q, B, R>("DELETE", path, handler, validation);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import type {
|
||||
Context,
|
||||
Env,
|
||||
Input,
|
||||
MiddlewareHandler,
|
||||
TypedResponse,
|
||||
ValidationTargets,
|
||||
} from "hono";
|
||||
import { validator } from "hono/validator";
|
||||
import type { ZodError, ZodSchema, z } from "zod";
|
||||
|
||||
export type Hook<T, E extends Env, P extends string, O = {}> = (
|
||||
result: { success: true; data: T } | { success: false; error: ZodError; data: T },
|
||||
c: Context<E, P>,
|
||||
) => Response | void | TypedResponse<O> | Promise<Response | void | TypedResponse<O>>;
|
||||
|
||||
type HasUndefined<T> = undefined extends T ? true : false;
|
||||
|
||||
export const zValidator = <
|
||||
T extends ZodSchema,
|
||||
Target extends keyof ValidationTargets,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
In = z.input<T>,
|
||||
Out = z.output<T>,
|
||||
I extends Input = {
|
||||
in: HasUndefined<In> extends true
|
||||
? {
|
||||
[K in Target]?: K extends "json"
|
||||
? In
|
||||
: HasUndefined<keyof ValidationTargets[K]> extends true
|
||||
? { [K2 in keyof In]?: ValidationTargets[K][K2] }
|
||||
: { [K2 in keyof In]: ValidationTargets[K][K2] };
|
||||
}
|
||||
: {
|
||||
[K in Target]: K extends "json"
|
||||
? In
|
||||
: HasUndefined<keyof ValidationTargets[K]> extends true
|
||||
? { [K2 in keyof In]?: ValidationTargets[K][K2] }
|
||||
: { [K2 in keyof In]: ValidationTargets[K][K2] };
|
||||
};
|
||||
out: { [K in Target]: Out };
|
||||
},
|
||||
V extends I = I,
|
||||
>(
|
||||
target: Target,
|
||||
schema: T,
|
||||
preprocess?: (target: string, value: In, c: Context<E, P>) => V, // <-- added
|
||||
hook?: Hook<z.infer<T>, E, P>,
|
||||
): MiddlewareHandler<E, P, V> =>
|
||||
// @ts-expect-error not typed well
|
||||
validator(target, async (value, c) => {
|
||||
// added: preprocess value first if given
|
||||
const _value = preprocess ? preprocess(target, value, c) : (value as any);
|
||||
const result = await schema.safeParseAsync(_value);
|
||||
|
||||
if (hook) {
|
||||
const hookResult = await hook({ data: value, ...result }, c);
|
||||
if (hookResult) {
|
||||
if (hookResult instanceof Response) {
|
||||
return hookResult;
|
||||
}
|
||||
|
||||
if ("response" in hookResult) {
|
||||
return hookResult.response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
return c.json(result, 400);
|
||||
}
|
||||
|
||||
return result.data as z.infer<T>;
|
||||
});
|
||||
@@ -369,9 +369,9 @@ export class DataController implements ClassController {
|
||||
return c.notFound();
|
||||
}
|
||||
const where = c.req.valid("json") as RepoQuery["where"];
|
||||
console.log("where", where);
|
||||
//console.log("where", where);
|
||||
|
||||
const result = await this.em.mutator(entity).deleteMany(where);
|
||||
const result = await this.em.mutator(entity).deleteWhere(where);
|
||||
|
||||
return c.json(this.mutatorResult(result));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ export {
|
||||
whereSchema
|
||||
} from "./server/data-query-impl";
|
||||
|
||||
export { whereRepoSchema as deprecated__whereRepoSchema } from "./server/query";
|
||||
|
||||
export { Connection } from "./connection/Connection";
|
||||
export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection";
|
||||
export { SqliteConnection } from "./connection/SqliteConnection";
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const date = z.union([z.date(), z.string()]);
|
||||
const numeric = z.union([z.number(), date]);
|
||||
const boolean = z.union([z.boolean(), z.literal(1), z.literal(0)]);
|
||||
const value = z.union([z.string(), boolean, numeric]);
|
||||
|
||||
const expressionCond = z.union([
|
||||
z.object({ $eq: value }).strict(),
|
||||
z.object({ $ne: value }).strict(),
|
||||
z.object({ $isnull: boolean }).strict(),
|
||||
z.object({ $notnull: boolean }).strict(),
|
||||
z.object({ $in: z.array(value) }).strict(),
|
||||
z.object({ $notin: z.array(value) }).strict(),
|
||||
z.object({ $gt: numeric }).strict(),
|
||||
z.object({ $gte: numeric }).strict(),
|
||||
z.object({ $lt: numeric }).strict(),
|
||||
z.object({ $lte: numeric }).strict(),
|
||||
z.object({ $between: z.array(numeric).min(2).max(2) }).strict()
|
||||
] as const);
|
||||
|
||||
// prettier-ignore
|
||||
const nonOperandString = z
|
||||
.string()
|
||||
.regex(/^(?!\$).*/)
|
||||
.min(1);
|
||||
|
||||
// {name: 'Michael'}
|
||||
const literalCond = z.record(nonOperandString, value);
|
||||
|
||||
// { status: { $eq: 1 } }
|
||||
const literalExpressionCond = z.record(nonOperandString, value.or(expressionCond));
|
||||
|
||||
const operandCond = z
|
||||
.object({
|
||||
$and: z.array(literalCond.or(expressionCond).or(literalExpressionCond)).optional(),
|
||||
$or: z.array(literalCond.or(expressionCond).or(literalExpressionCond)).optional()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const literalSchema = literalCond.or(literalExpressionCond);
|
||||
export type LiteralSchemaIn = z.input<typeof literalSchema>;
|
||||
export type LiteralSchema = z.output<typeof literalSchema>;
|
||||
|
||||
export const filterSchema = literalSchema.or(operandCond);
|
||||
export type FilterSchemaIn = z.input<typeof filterSchema>;
|
||||
export type FilterSchema = z.output<typeof filterSchema>;
|
||||
|
||||
const stringArray = z
|
||||
.union([
|
||||
z.string().transform((v) => {
|
||||
if (v.includes(",")) return v.split(",");
|
||||
return v;
|
||||
}),
|
||||
z.array(z.string())
|
||||
])
|
||||
.default([])
|
||||
.transform((v) => (Array.isArray(v) ? v : [v]));
|
||||
|
||||
export const whereRepoSchema = z
|
||||
.preprocess((v: unknown) => {
|
||||
try {
|
||||
return JSON.parse(v as string);
|
||||
} catch {
|
||||
return v;
|
||||
}
|
||||
}, filterSchema)
|
||||
.default({});
|
||||
|
||||
const repoQuerySchema = z.object({
|
||||
limit: z.coerce.number().default(10),
|
||||
offset: z.coerce.number().default(0),
|
||||
sort: z
|
||||
.preprocess(
|
||||
(v: unknown) => {
|
||||
try {
|
||||
return JSON.parse(v as string);
|
||||
} catch {
|
||||
return v;
|
||||
}
|
||||
},
|
||||
z.union([
|
||||
z.string().transform((v) => {
|
||||
if (v.includes(":")) {
|
||||
let [field, dir] = v.split(":") as [string, string];
|
||||
if (!["asc", "desc"].includes(dir)) dir = "asc";
|
||||
return { by: field, dir } as { by: string; dir: "asc" | "desc" };
|
||||
} else {
|
||||
return { by: v, dir: "asc" } as { by: string; dir: "asc" | "desc" };
|
||||
}
|
||||
}),
|
||||
z.object({
|
||||
by: z.string(),
|
||||
dir: z.enum(["asc", "desc"])
|
||||
})
|
||||
])
|
||||
)
|
||||
.default({ by: "id", dir: "asc" }),
|
||||
select: stringArray,
|
||||
with: stringArray,
|
||||
join: stringArray,
|
||||
debug: z
|
||||
.preprocess((v) => {
|
||||
if (["0", "false"].includes(String(v))) return false;
|
||||
return Boolean(v);
|
||||
}, z.boolean())
|
||||
.default(false), //z.coerce.boolean().catch(false),
|
||||
where: whereRepoSchema
|
||||
});
|
||||
|
||||
type RepoQueryIn = z.input<typeof repoQuerySchema>;
|
||||
type RepoQuery = z.output<typeof repoQuerySchema>;
|
||||
Reference in New Issue
Block a user