import type { DB, EntityData, RepoQueryIn } from "bknd"; import type { Insertable, Selectable, Updateable } from "kysely"; import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules"; import type { FetchPromise, ResponseObject } from "modules/ModuleApi"; import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult"; export type DataApiOptions = BaseModuleApiOptions & { queryLengthLimit: number; defaultQuery: Partial; }; export class DataApi extends ModuleApi { protected override getDefaultOptions(): Partial { return { basepath: "/api/data", queryLengthLimit: 1000, defaultQuery: { limit: 10, }, }; } private requireObjectSet(obj: any, message?: string) { if (!obj || typeof obj !== "object" || Object.keys(obj).length === 0) { throw new Error(message ?? "object is required"); } } readOne( entity: E, id: PrimaryFieldType, query: Omit = {}, ) { type Data = E extends keyof DB ? Selectable : EntityData; return this.get>(["entity", entity as any, id], query); } readOneBy( entity: E, query: Omit = {}, ) { type Data = E extends keyof DB ? Selectable : EntityData; type T = RepositoryResultJSON; // @todo: if none found, still returns meta... return this.readMany(entity, { ...query, limit: 1, offset: 0, }).refine((data) => data[0]) as unknown as FetchPromise>; } readMany(entity: E, query: RepoQueryIn = {}) { type Data = E extends keyof DB ? Selectable : EntityData; type T = RepositoryResultJSON; const input = query ?? this.options.defaultQuery; const req = this.get(["entity", entity as any], input); if (req.request.url.length <= this.options.queryLengthLimit) { return req; } return this.post(["entity", entity as any, "query"], input); } readManyByReference( entity: E, id: PrimaryFieldType, reference: R, query: RepoQueryIn = {}, ) { type Data = R extends keyof DB ? Selectable : EntityData; return this.get>( ["entity", entity as any, id, reference], query ?? this.options.defaultQuery, ); } createOne( entity: E, input: Insertable, ) { type Data = E extends keyof DB ? Selectable : EntityData; return this.post>(["entity", entity as any], input); } createMany( entity: E, input: Insertable[], ) { if (!input || !Array.isArray(input) || input.length === 0) { throw new Error("input is required"); } type Data = E extends keyof DB ? Selectable : EntityData; return this.post>(["entity", entity as any], input); } updateOne( entity: E, id: PrimaryFieldType, input: Updateable, ) { if (!id) throw new Error("ID is required"); type Data = E extends keyof DB ? Selectable : EntityData; return this.patch>(["entity", entity as any, id], input); } updateMany( entity: E, where: RepoQueryIn["where"], update: Updateable, ) { this.requireObjectSet(where); type Data = E extends keyof DB ? Selectable : EntityData; return this.patch>(["entity", entity as any], { update, where, }); } deleteOne(entity: E, id: PrimaryFieldType) { if (!id) throw new Error("ID is required"); type Data = E extends keyof DB ? Selectable : EntityData; return this.delete>(["entity", entity as any, id]); } deleteMany(entity: E, where: RepoQueryIn["where"]) { this.requireObjectSet(where); type Data = E extends keyof DB ? Selectable : EntityData; return this.delete>(["entity", entity as any], where); } count(entity: E, where: RepoQueryIn["where"] = {}) { return this.post>( ["entity", entity as any, "fn", "count"], where, ); } exists(entity: E, where: RepoQueryIn["where"] = {}) { return this.post>( ["entity", entity as any, "fn", "exists"], where, ); } }