mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +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 { describe, expect, test } from "bun:test";
|
||||||
import { type ObjectQuery, convert, validate } from "../../../src/core/object/query/object-query";
|
import { type ObjectQuery, convert, validate } from "../../../src/core/object/query/object-query";
|
||||||
import { deprecated__whereRepoSchema } from "../../../src/data";
|
|
||||||
|
|
||||||
describe("object-query", () => {
|
describe("object-query", () => {
|
||||||
const q: ObjectQuery = { name: "Michael" };
|
const q: ObjectQuery = { name: "Michael" };
|
||||||
@@ -8,19 +7,6 @@ describe("object-query", () => {
|
|||||||
const q3: ObjectQuery = { name: "Michael", age: { $gt: 18 } };
|
const q3: ObjectQuery = { name: "Michael", age: { $gt: 18 } };
|
||||||
const bag = { q, q2, q3 };
|
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 () => {
|
test("validates", async () => {
|
||||||
const converted = convert({
|
const converted = convert({
|
||||||
name: { $eq: "ch" }
|
name: { $eq: "ch" }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.2.3-rc2",
|
"version": "0.2.3-rc3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:all": "bun run build && bun run build:cli",
|
"build:all": "bun run build && bun run build:cli",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -23,7 +23,17 @@
|
|||||||
"license": "FSL-1.1-MIT",
|
"license": "FSL-1.1-MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libsql/client": "^0.14.0",
|
"@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": {
|
"devDependencies": {
|
||||||
"@libsql/kysely-libsql": "^0.4.1",
|
"@libsql/kysely-libsql": "^0.4.1",
|
||||||
@@ -33,28 +43,17 @@
|
|||||||
"@mantine/notifications": "^7.13.5",
|
"@mantine/notifications": "^7.13.5",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||||
"@rjsf/core": "^5.22.2",
|
"@rjsf/core": "^5.22.2",
|
||||||
"@sinclair/typebox": "^0.32.34",
|
|
||||||
"@tabler/icons-react": "3.18.0",
|
"@tabler/icons-react": "3.18.0",
|
||||||
"@tanstack/react-query": "^5.59.16",
|
"@tanstack/react-query": "^5.59.16",
|
||||||
"@uiw/react-codemirror": "^4.23.6",
|
"@uiw/react-codemirror": "^4.23.6",
|
||||||
"@xyflow/react": "^12.3.2",
|
"@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",
|
"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-hook-form": "^7.53.1",
|
||||||
"react-icons": "5.2.1",
|
"react-icons": "5.2.1",
|
||||||
"react-json-view-lite": "^2.0.1",
|
"react-json-view-lite": "^2.0.1",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"wouter": "^3.3.5",
|
"wouter": "^3.3.5",
|
||||||
"zod": "^3.23.8",
|
|
||||||
"@cfworker/json-schema": "^2.0.1",
|
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-liquid": "^6.2.1",
|
"@codemirror/lang-liquid": "^6.2.1",
|
||||||
|
|||||||
@@ -189,7 +189,11 @@ export class DurableBkndApp extends DurableObject {
|
|||||||
const config = options.config;
|
const config = options.config;
|
||||||
|
|
||||||
// change protocol to websocket if libsql
|
// 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";
|
config.connection.config.protocol = "wss";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { Endpoint, type RequestResponse, type Middleware } from "./server/Endpoint";
|
import type { Hono, MiddlewareHandler } from "hono";
|
||||||
export { zValidator } from "./server/lib/zValidator";
|
|
||||||
export { tbValidator } from "./server/lib/tbValidator";
|
export { tbValidator } from "./server/lib/tbValidator";
|
||||||
export { Exception, BkndError } from "./errors";
|
export { Exception, BkndError } from "./errors";
|
||||||
export { isDebug } from "./env";
|
export { isDebug } from "./env";
|
||||||
@@ -11,7 +11,6 @@ export {
|
|||||||
type TemplateTypes,
|
type TemplateTypes,
|
||||||
type SimpleRendererOptions
|
type SimpleRendererOptions
|
||||||
} from "./template/SimpleRenderer";
|
} from "./template/SimpleRenderer";
|
||||||
export { Controller, type ClassController } from "./server/Controller";
|
|
||||||
export { SchemaObject } from "./object/SchemaObject";
|
export { SchemaObject } from "./object/SchemaObject";
|
||||||
export { DebugLogger } from "./utils/DebugLogger";
|
export { DebugLogger } from "./utils/DebugLogger";
|
||||||
export { Permission } from "./security/Permission";
|
export { Permission } from "./security/Permission";
|
||||||
@@ -26,3 +25,10 @@ export {
|
|||||||
isBooleanLike
|
isBooleanLike
|
||||||
} from "./object/query/query";
|
} from "./object/query/query";
|
||||||
export { Registry, type Constructor } from "./registry/Registry";
|
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();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
const where = c.req.valid("json") as RepoQuery["where"];
|
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));
|
return c.json(this.mutatorResult(result));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ export {
|
|||||||
whereSchema
|
whereSchema
|
||||||
} from "./server/data-query-impl";
|
} from "./server/data-query-impl";
|
||||||
|
|
||||||
export { whereRepoSchema as deprecated__whereRepoSchema } from "./server/query";
|
|
||||||
|
|
||||||
export { Connection } from "./connection/Connection";
|
export { Connection } from "./connection/Connection";
|
||||||
export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection";
|
export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection";
|
||||||
export { SqliteConnection } from "./connection/SqliteConnection";
|
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