mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 20:37:21 +00:00
public commit
This commit is contained in:
77
app/src/data/server/data-query-impl.ts
Normal file
77
app/src/data/server/data-query-impl.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
type SchemaOptions,
|
||||
type Static,
|
||||
type StaticDecode,
|
||||
StringEnum,
|
||||
Type,
|
||||
Value
|
||||
} from "core/utils";
|
||||
import type { Simplify } from "type-fest";
|
||||
import { WhereBuilder } from "../entities";
|
||||
|
||||
const NumberOrString = (options: SchemaOptions = {}) =>
|
||||
Type.Transform(Type.Union([Type.Number(), Type.String()], options))
|
||||
.Decode((value) => Number.parseInt(String(value)))
|
||||
.Encode(String);
|
||||
|
||||
const limit = NumberOrString({ default: 10 });
|
||||
|
||||
const offset = NumberOrString({ default: 0 });
|
||||
|
||||
// @todo: allow "id" and "-id"
|
||||
const sort = Type.Transform(
|
||||
Type.Union(
|
||||
[Type.String(), Type.Object({ by: Type.String(), dir: StringEnum(["asc", "desc"]) })],
|
||||
{
|
||||
default: { by: "id", dir: "asc" }
|
||||
}
|
||||
)
|
||||
)
|
||||
.Decode((value) => {
|
||||
if (typeof value === "string") {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
return value;
|
||||
})
|
||||
.Encode(JSON.stringify);
|
||||
|
||||
const stringArray = Type.Transform(
|
||||
Type.Union([Type.String(), Type.Array(Type.String())], { default: [] })
|
||||
)
|
||||
.Decode((value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value;
|
||||
} else if (value.includes(",")) {
|
||||
return value.split(",");
|
||||
}
|
||||
return [value];
|
||||
})
|
||||
.Encode((value) => (Array.isArray(value) ? value : [value]));
|
||||
|
||||
export const whereSchema = Type.Transform(
|
||||
Type.Union([Type.String(), Type.Object({})], { default: {} })
|
||||
)
|
||||
.Decode((value) => {
|
||||
const q = typeof value === "string" ? JSON.parse(value) : value;
|
||||
return WhereBuilder.convert(q);
|
||||
})
|
||||
.Encode(JSON.stringify);
|
||||
|
||||
export const querySchema = Type.Object(
|
||||
{
|
||||
limit: Type.Optional(limit),
|
||||
offset: Type.Optional(offset),
|
||||
sort: Type.Optional(sort),
|
||||
select: Type.Optional(stringArray),
|
||||
with: Type.Optional(stringArray),
|
||||
join: Type.Optional(stringArray),
|
||||
where: Type.Optional(whereSchema)
|
||||
},
|
||||
{
|
||||
additionalProperties: false
|
||||
}
|
||||
);
|
||||
|
||||
export type RepoQueryIn = Simplify<Static<typeof querySchema>>;
|
||||
export type RepoQuery = Required<StaticDecode<typeof querySchema>>;
|
||||
export const defaultQuerySchema = Value.Default(querySchema, {}) as RepoQuery;
|
||||
112
app/src/data/server/query.ts
Normal file
112
app/src/data/server/query.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
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