fix RepoQuery typings

This commit is contained in:
dswbx
2025-01-16 16:21:32 +01:00
parent 371184d232
commit c6cbd36231
11 changed files with 39 additions and 39 deletions

View File

@@ -98,14 +98,14 @@ describe("data-query-impl", () => {
test("with", () => {
decode({ with: ["posts"] }, { with: { posts: {} } });
decode({ with: { posts: {} } }, { with: { posts: {} } });
decode({ with: { posts: { limit: "1" } } }, { with: { posts: { limit: 1 } } });
decode({ with: { posts: { limit: 1 } } }, { with: { posts: { limit: 1 } } });
decode(
{
with: {
posts: {
with: {
images: {
select: "id"
select: ["id"]
}
}
}

View File

@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"bin": "./dist/cli/index.js",
"version": "0.5.0",
"version": "0.6.0-rc.0",
"scripts": {
"dev": "vite",
"test": "ALL_TESTS=1 bun test --bail",

View File

@@ -49,7 +49,7 @@ type LiteralExpressionCondition<Exps extends Expressions> = {
[key: string]: Primitive | ExpressionCondition<Exps>;
};
const OperandOr = "$or";
const OperandOr = "$or" as const;
type OperandCondition<Exps extends Expressions> = {
[OperandOr]?: LiteralExpressionCondition<Exps> | ExpressionCondition<Exps>;
};

View File

@@ -1,5 +1,5 @@
import type { DB } from "core";
import type { EntityData, RepoQuery, RepositoryResponse } from "data";
import type { EntityData, RepoQuery, RepoQueryIn, RepositoryResponse } from "data";
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules";
export type DataApiOptions = BaseModuleApiOptions & {
@@ -19,14 +19,14 @@ export class DataApi extends ModuleApi<DataApiOptions> {
readOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
id: PrimaryFieldType,
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
query: Omit<RepoQueryIn, "where" | "limit" | "offset"> = {}
) {
return this.get<Pick<RepositoryResponse<Data>, "meta" | "data">>([entity as any, id], query);
}
readMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
query: Partial<RepoQuery> = {}
query: RepoQueryIn = {}
) {
return this.get<Pick<RepositoryResponse<Data[]>, "meta" | "data">>(
[entity as any],
@@ -38,7 +38,7 @@ export class DataApi extends ModuleApi<DataApiOptions> {
E extends keyof DB | string,
R extends keyof DB | string,
Data = R extends keyof DB ? DB[R] : EntityData
>(entity: E, id: PrimaryFieldType, reference: R, query: Partial<RepoQuery> = {}) {
>(entity: E, id: PrimaryFieldType, reference: R, query: RepoQueryIn = {}) {
return this.get<Pick<RepositoryResponse<Data[]>, "meta" | "data">>(
[entity as any, id, reference],
query ?? this.options.defaultQuery

View File

@@ -98,8 +98,8 @@ export class Entity<
getDefaultSort() {
return {
by: this.config.sort_field,
dir: this.config.sort_dir
by: this.config.sort_field ?? "id",
dir: this.config.sort_dir ?? "asc"
};
}

View File

@@ -30,7 +30,7 @@ function key(e: unknown): string {
return e as string;
}
const expressions: TExpression<any, any, any>[] = [
const expressions = [
exp(
"$eq",
(v: Primitive) => isPrimitive(v),

View File

@@ -8,6 +8,7 @@ export * from "./prototype";
export {
type RepoQuery,
type RepoQueryIn,
defaultQuerySchema,
querySchema,
whereSchema

View File

@@ -7,7 +7,7 @@ import {
Type,
Value
} from "core/utils";
import { WhereBuilder } from "../entities";
import { WhereBuilder, type WhereQuery } from "../entities";
const NumberOrString = (options: SchemaOptions = {}) =>
Type.Transform(Type.Union([Type.Number(), Type.String()], options))
@@ -15,10 +15,8 @@ const NumberOrString = (options: SchemaOptions = {}) =>
.Encode(String);
const limit = NumberOrString({ default: 10 });
const offset = NumberOrString({ default: 0 });
// @todo: allow "id" and "-id"
const sort_default = { by: "id", dir: "asc" };
const sort = Type.Transform(
Type.Union(
@@ -28,20 +26,20 @@ const sort = Type.Transform(
}
)
)
.Decode((value) => {
.Decode((value): { by: string; dir: "asc" | "desc" } => {
if (typeof value === "string") {
if (/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(value)) {
const dir = value[0] === "-" ? "desc" : "asc";
return { by: dir === "desc" ? value.slice(1) : value, dir };
return { by: dir === "desc" ? value.slice(1) : value, dir } as any;
} else if (/^{.*}$/.test(value)) {
return JSON.parse(value);
return JSON.parse(value) as any;
}
return sort_default;
return sort_default as any;
}
return value;
return value as any;
})
.Encode(JSON.stringify);
.Encode((value) => value);
const stringArray = Type.Transform(
Type.Union([Type.String(), Type.Array(Type.String())], { default: [] })
@@ -65,25 +63,18 @@ export const whereSchema = Type.Transform(
})
.Encode(JSON.stringify);
export type ShallowRepoQuery = {
limit?: number;
offset?: number;
sort?: string | { by: string; dir: "asc" | "desc" };
select?: string[];
with?: string[] | Record<string, ShallowRepoQuery>;
join?: string[];
where?: any;
};
export type RepoWithSchema = Record<
string,
Omit<ShallowRepoQuery, "with"> & {
Omit<RepoQueryIn, "with"> & {
with?: unknown;
}
>;
export const withSchema = <TSelf extends TThis>(Self: TSelf) =>
Type.Transform(Type.Union([stringArray, Type.Record(Type.String(), Self)]))
.Decode((value) => {
let _value = value;
let _value = typeof value === "string" ? [value] : value;
if (Array.isArray(value)) {
if (!value.every((v) => typeof v === "string")) {
throw new Error("Invalid 'with' schema");
@@ -121,6 +112,14 @@ export const querySchema = Type.Recursive(
{ $id: "query-schema" }
);
export type RepoQueryIn = Static<typeof querySchema>;
export type RepoQueryIn = {
limit?: number;
offset?: number;
sort?: string | { by: string; dir: "asc" | "desc" };
select?: string[];
with?: string[] | Record<string, RepoQueryIn>;
join?: string[];
where?: WhereQuery;
};
export type RepoQuery = Required<StaticDecode<typeof querySchema>>;
export const defaultQuerySchema = Value.Default(querySchema, {}) as RepoQuery;

View File

@@ -1,6 +1,6 @@
import type { DB, PrimaryFieldType } from "core";
import { encodeSearch, objectTransform } from "core/utils";
import type { EntityData, RepoQuery } from "data";
import type { EntityData, RepoQuery, RepoQueryIn } from "data";
import type { ModuleApi, ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, mutate } from "swr";
import { type Api, useApi } from "ui/client";
@@ -49,7 +49,7 @@ export const useEntity = <
}
return res;
},
read: async (query: Partial<RepoQuery> = {}) => {
read: async (query: RepoQueryIn = {}) => {
const res = id ? await api.readOne(entity, id!, query) : await api.readMany(entity, query);
if (!res.ok) {
throw new UseEntityApiError(res as any, `Failed to read entity "${entity}"`);
@@ -88,7 +88,7 @@ export function makeKey(
api: ModuleApi,
entity: string,
id?: PrimaryFieldType,
query?: Partial<RepoQuery>
query?: RepoQueryIn
) {
return (
"/" +
@@ -105,7 +105,7 @@ export const useEntityQuery = <
>(
entity: Entity,
id?: Id,
query?: Partial<RepoQuery>,
query?: RepoQueryIn,
options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean }
) => {
const api = useApi().data;

View File

@@ -1,4 +1,4 @@
import type { RepoQuery } from "data";
import type { RepoQuery, RepoQueryIn } from "data";
import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema";
import { useId } from "react";
@@ -15,7 +15,7 @@ export type DropzoneContainerProps = {
id: number;
field: string;
};
query?: Partial<RepoQuery>;
query?: RepoQueryIn;
} & Partial<Pick<TAppMediaConfig, "basepath" | "entity_name" | "storage">> &
Partial<DropzoneProps>;

View File

@@ -234,7 +234,7 @@ function EntityDetailInner({
const other = relation.other(entity);
const [navigate] = useNavigate();
const search: Partial<RepoQuery> = {
const search = {
select: other.entity.getSelect(undefined, "table"),
limit: 10,
offset: 0