added readOneBy, updateMany, deleteMany, exists

This commit is contained in:
dswbx
2025-03-05 08:02:57 +01:00
parent 4f52537ea0
commit ef629321ab
9 changed files with 299 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
import type { DB } from "core";
import type { EntityData, RepoQueryIn, RepositoryResponse } from "data";
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules";
import type { FetchPromise, ResponseObject } from "modules/ModuleApi";
export type DataApiOptions = BaseModuleApiOptions & {
queryLengthLimit: number;
@@ -18,6 +19,12 @@ export class DataApi extends ModuleApi<DataApiOptions> {
};
}
private requireObjectSet(obj: any, message?: string) {
if (!obj || typeof obj !== "object" || Object.keys(obj).length === 0) {
throw new Error(message ?? "object is required");
}
}
readOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
id: PrimaryFieldType,
@@ -29,6 +36,18 @@ export class DataApi extends ModuleApi<DataApiOptions> {
);
}
readOneBy<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
query: Omit<RepoQueryIn, "limit" | "offset" | "sort"> = {},
) {
type T = Pick<RepositoryResponse<Data>, "meta" | "data">;
return this.readMany(entity, {
...query,
limit: 1,
offset: 0,
}).refine((data) => data[0]) as unknown as FetchPromise<ResponseObject<T>>;
}
readMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
query: RepoQueryIn = {},
@@ -63,25 +82,64 @@ export class DataApi extends ModuleApi<DataApiOptions> {
return this.post<RepositoryResponse<Data>>(["entity", entity as any], input);
}
createMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
input: Omit<Data, "id">[],
) {
if (!input || !Array.isArray(input) || input.length === 0) {
throw new Error("input is required");
}
return this.post<RepositoryResponse<Data[]>>(["entity", entity as any], input);
}
updateOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
id: PrimaryFieldType,
input: Partial<Omit<Data, "id">>,
) {
if (!id) throw new Error("ID is required");
return this.patch<RepositoryResponse<Data>>(["entity", entity as any, id], input);
}
updateMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
where: RepoQueryIn["where"],
update: Partial<Omit<Data, "id">>,
) {
this.requireObjectSet(where);
return this.patch<RepositoryResponse<Data[]>>(["entity", entity as any], {
update,
where,
});
}
deleteOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
id: PrimaryFieldType,
) {
if (!id) throw new Error("ID is required");
return this.delete<RepositoryResponse<Data>>(["entity", entity as any, id]);
}
deleteMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
entity: E,
where: RepoQueryIn["where"],
) {
this.requireObjectSet(where);
return this.delete<RepositoryResponse<Data>>(["entity", entity as any], where);
}
count<E extends keyof DB | string>(entity: E, where: RepoQueryIn["where"] = {}) {
return this.post<RepositoryResponse<{ entity: E; count: number }>>(
["entity", entity as any, "fn", "count"],
where,
);
}
exists<E extends keyof DB | string>(entity: E, where: RepoQueryIn["where"] = {}) {
return this.post<RepositoryResponse<{ entity: E; exists: boolean }>>(
["entity", entity as any, "fn", "exists"],
where,
);
}
}

View File

@@ -343,14 +343,20 @@ export class DataController extends Controller {
"/:entity",
permission(DataPermissions.entityCreate),
tb("param", Type.Object({ entity: Type.String() })),
tb("json", Type.Union([Type.Object({}), Type.Array(Type.Object({}))])),
async (c) => {
const { entity } = c.req.param();
if (!this.entityExists(entity)) {
return this.notFound(c);
}
const body = (await c.req.json()) as EntityData;
const result = await this.em.mutator(entity).insertOne(body);
const body = (await c.req.json()) as EntityData | EntityData[];
if (Array.isArray(body)) {
const result = await this.em.mutator(entity).insertMany(body);
return c.json(this.mutatorResult(result), 201);
}
const result = await this.em.mutator(entity).insertOne(body);
return c.json(this.mutatorResult(result), 201);
},
);

View File

@@ -270,9 +270,14 @@ export class Mutator<
}
// @todo: decide whether entries should be deleted all at once or one by one (for events)
async deleteWhere(where?: RepoQuery["where"]): Promise<MutatorResponse<Output[]>> {
async deleteWhere(where: RepoQuery["where"]): Promise<MutatorResponse<Output[]>> {
const entity = this.entity;
// @todo: add a way to delete all by adding force?
if (!where || typeof where !== "object" || Object.keys(where).length === 0) {
throw new Error("Where clause must be provided for mass deletion");
}
const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning(
entity.getSelect(),
);
@@ -282,11 +287,16 @@ export class Mutator<
async updateWhere(
data: Partial<Input>,
where?: RepoQuery["where"],
where: RepoQuery["where"],
): Promise<MutatorResponse<Output[]>> {
const entity = this.entity;
const validatedData = await this.getValidatedData(data, "update");
// @todo: add a way to delete all by adding force?
if (!where || typeof where !== "object" || Object.keys(where).length === 0) {
throw new Error("Where clause must be provided for mass update");
}
const query = this.appendWhere(this.conn.updateTable(entity.name), where)
.set(validatedData as any)
.returning(entity.getSelect());