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", () => { test("with", () => {
decode({ with: ["posts"] }, { with: { posts: {} } }); decode({ with: ["posts"] }, { with: { posts: {} } });
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( decode(
{ {
with: { with: {
posts: { posts: {
with: { with: {
images: { images: {
select: "id" select: ["id"]
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import type { DB, PrimaryFieldType } from "core"; import type { DB, PrimaryFieldType } from "core";
import { encodeSearch, objectTransform } from "core/utils"; 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 type { ModuleApi, ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, mutate } from "swr"; import useSWR, { type SWRConfiguration, mutate } from "swr";
import { type Api, useApi } from "ui/client"; import { type Api, useApi } from "ui/client";
@@ -49,7 +49,7 @@ export const useEntity = <
} }
return res; 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); const res = id ? await api.readOne(entity, id!, query) : await api.readMany(entity, query);
if (!res.ok) { if (!res.ok) {
throw new UseEntityApiError(res as any, `Failed to read entity "${entity}"`); throw new UseEntityApiError(res as any, `Failed to read entity "${entity}"`);
@@ -88,7 +88,7 @@ export function makeKey(
api: ModuleApi, api: ModuleApi,
entity: string, entity: string,
id?: PrimaryFieldType, id?: PrimaryFieldType,
query?: Partial<RepoQuery> query?: RepoQueryIn
) { ) {
return ( return (
"/" + "/" +
@@ -105,7 +105,7 @@ export const useEntityQuery = <
>( >(
entity: Entity, entity: Entity,
id?: Id, id?: Id,
query?: Partial<RepoQuery>, query?: RepoQueryIn,
options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean } options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean }
) => { ) => {
const api = useApi().data; 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 { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema"; import type { TAppMediaConfig } from "media/media-schema";
import { useId } from "react"; import { useId } from "react";
@@ -15,7 +15,7 @@ export type DropzoneContainerProps = {
id: number; id: number;
field: string; field: string;
}; };
query?: Partial<RepoQuery>; query?: RepoQueryIn;
} & Partial<Pick<TAppMediaConfig, "basepath" | "entity_name" | "storage">> & } & Partial<Pick<TAppMediaConfig, "basepath" | "entity_name" | "storage">> &
Partial<DropzoneProps>; Partial<DropzoneProps>;

View File

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