fix(db): accumulate many-includes and nest relations in prisma facade
CI / check (push) Successful in 26s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m12s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-31 20:57:52 +02:00
1 parent e5ff7ec9e5
commit 20b85381fe
3 files changed
+97 -11

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ describe("CI workflow", () => {
const workflow = readFileSync(
resolve(process.cwd(), ".gitea/workflows/ci.yaml"),
"utf8",
);
).replace(/\r\n/g, "\n");
it("runs check then production deploy on push to main", () => {
expect(workflow).toContain("pnpm biome:lint");
+50
View File
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
/**
* Mirrors the many-relation grouping fix in prisma-facade queryRelationRows.
* Keeps the contract local so we don't export private helpers.
*/
function groupRelationRows(
rows: Array<{ __fk: string; id: number }>,
mode: "one" | "many",
): Map<string, unknown> {
const map = new Map<string, unknown>();
for (const row of rows) {
const key = String(row.__fk);
const cleaned = { id: row.id };
if (mode === "many") {
const list = map.get(key);
if (Array.isArray(list)) list.push(cleaned);
else map.set(key, [cleaned]);
} else if (!map.has(key)) {
map.set(key, cleaned);
}
}
return map;
}
describe("prisma-facade relation grouping", () => {
it("accumulates many rows per parent key", () => {
const map = groupRelationRows(
[
{ __fk: "1", id: 10 },
{ __fk: "1", id: 11 },
{ __fk: "2", id: 20 },
],
"many",
);
expect(map.get("1")).toEqual([{ id: 10 }, { id: 11 }]);
expect(map.get("2")).toEqual([{ id: 20 }]);
});
it("keeps a single row for one relations", () => {
const map = groupRelationRows(
[
{ __fk: "1", id: 10 },
{ __fk: "1", id: 11 },
],
"one",
);
expect(map.get("1")).toEqual({ id: 10 });
});
});
+46 -10
View File
@@ -270,24 +270,36 @@ async function queryRelationRows(
rel: any,
opts: any,
keyValues: unknown[],
mode: "one" | "many",
): Promise<Map<string, any>> {
const join = resolveJoin(table, rel);
const byCol = join.fkCols[0];
const condition = inArray(byCol, coerceArray(byCol, keyValues));
const parts = [
inArray(byCol, coerceArray(byCol, keyValues)),
buildCondition(rel.referencedTable, opts.where),
].filter(Boolean);
const condition = parts.length === 1 ? parts[0] : and(...parts);
const sel = projection(rel.referencedTable, opts.select);
const orderBy = buildOrderBy(rel.referencedTable, opts.orderBy);
let stmt: any = sel
? client.select({ ...sel, __fk: byCol }).from(rel.referencedTable)
: client.select().from(rel.referencedTable);
stmt = stmt.where(condition);
if (condition) stmt = stmt.where(condition);
if (orderBy.length) stmt = stmt.orderBy(...orderBy);
const rows = (await stmt) as any[];
const map = new Map<string, any>();
for (const row of rows) {
const key = row.__fk ?? row[colKey(byCol)];
map.set(String(key), sel ? pickRow(row, opts.select) : row);
const key = String(row.__fk ?? row[colKey(byCol)]);
const cleaned = sel ? pickRow(row, opts.select) : row;
if (mode === "many") {
const list = map.get(key);
if (Array.isArray(list)) list.push(cleaned);
else map.set(key, [cleaned]);
} else if (!map.has(key)) {
map.set(key, cleaned);
}
}
return map;
}
@@ -386,13 +398,25 @@ async function attachIncludes(
for (const row of rows) row[rel.fieldName] = null;
continue;
}
const map = await queryRelationRows(client, table, rel, opts, values);
const map = await queryRelationRows(
client,
table,
rel,
opts,
values,
"one",
);
const join = resolveJoin(table, rel);
const refCol = join.refCols[0];
const related: any[] = [];
for (const row of rows) {
const refValue = row[colKey(refCol)];
row[rel.fieldName] =
refValue == null ? null : (map.get(String(refValue)) ?? null);
const hit = refValue == null ? null : (map.get(String(refValue)) ?? null);
row[rel.fieldName] = hit;
if (hit) related.push(hit);
}
if (opts.include && related.length > 0) {
await attachIncludes(client, rel.referencedTable, related, opts.include);
}
}
@@ -402,13 +426,25 @@ async function attachIncludes(
for (const row of rows) row[rel.fieldName] = [];
continue;
}
const map = await queryRelationRows(client, table, rel, opts, values);
const map = await queryRelationRows(
client,
table,
rel,
opts,
values,
"many",
);
const join = resolveJoin(table, rel);
const refCol = join.refCols[0];
const related: any[] = [];
for (const row of rows) {
const refValue = row[colKey(refCol)];
row[rel.fieldName] =
refValue == null ? [] : (map.get(String(refValue)) ?? []);
const list = refValue == null ? [] : (map.get(String(refValue)) ?? []);
row[rel.fieldName] = list;
for (const item of list) related.push(item);
}
if (opts.include && related.length > 0) {
await attachIncludes(client, rel.referencedTable, related, opts.include);
}
}