public commit

This commit is contained in:
dswbx
2024-11-16 12:01:47 +01:00
commit 90f80c4280
582 changed files with 49291 additions and 0 deletions

View 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;

View 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>;