Merge branch 'fix/v1437-data' into release/v1.43.7

This commit is contained in:
Marc-André Bombeck committed 2026-10-20 08:12:32 +02:00
commit dee6a27ae4
100 files changed
+2314 -506

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { apiListSuccess, capRows, overfetch } from "../list-cap";
describe("list cap", () => {
it("asks for one row past the limit", () => {
expect(overfetch(100)).toBe(101);
});
it("cuts an over-fetched result back and says it was cut", () => {
expect(capRows([1, 2, 3], 2)).toEqual({
rows: [1, 2],
cap: { truncated: true, limit: 2 },
});
});
it("does not call a list that is exactly full cut", () => {
expect(capRows([1, 2], 2).cap.truncated).toBe(false);
expect(capRows([], 2).cap.truncated).toBe(false);
});
it("keeps data as it is and puts the cap in meta", async () => {
const res = apiListSuccess({ events: [1] }, { truncated: true, limit: 1 });
expect(await res.json()).toEqual({
data: { events: [1] },
error: null,
meta: { truncated: true, limit: 1 },
});
});
});
+55
View File
@@ -0,0 +1,55 @@
/**
* One signal for a list the server cut at a ceiling.
*
* A list endpoint that stops at its own `take` and says nothing is the
* quieter of the two completeness lies: the client renders the last row it
* got as the oldest one there is. Several routes had grown their own answer
* (a `truncated` field here, `rows.length === MAX` there, which reports a
* cut on a list that is exactly full) and most had none.
*
* The pattern, in three steps:
*
* 1. query one row past the limit: `take: overfetch(limit)`;
* 2. cut it back: `const { rows, cap } = capRows(found, limit)`;
* 3. answer with `apiListSuccess(data, cap)`.
*
* The signal rides the envelope's `meta` (`meta.truncated`, `meta.limit`), so
* `data` keeps exactly the shape the route always had and a client that never
* reads `meta` decodes it unchanged. `truncated` is exact: true only when a
* row past the limit exists. The OpenAPI side is `listEnvelope` in
* `src/lib/openapi/routes/shared.ts`.
*/
import { apiSuccessWithMeta } from "@/lib/api-response";
export interface ListCap {
/** A row past `limit` exists and was not returned. */
truncated: boolean;
/** The most rows this response could carry. */
limit: number;
}
/** The `take` that lets {@link capRows} tell a full list from a cut one. */
export function overfetch(limit: number): number {
return limit + 1;
}
/** Cut an over-fetched result back to `limit` and say whether it was cut. */
export function capRows<T>(
found: readonly T[],
limit: number,
): { rows: T[]; cap: ListCap } {
const truncated = found.length > limit;
return {
rows: truncated ? found.slice(0, limit) : [...found],
cap: { truncated, limit },
};
}
/** The success envelope for a capped list: `data` as-is, the cap in `meta`. */
export function apiListSuccess<T>(data: T, cap: ListCap, status = 200) {
return apiSuccessWithMeta(
data,
{ truncated: cap.truncated, limit: cap.limit },
status,
);
}