mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(measurements): isolate per-type failures in the series batch
The batched daily-series endpoint ran every type through one Promise.all with no per-task catch, so a single type's transient reject 500'd the whole batch and blanked every preloaded chart in the dashboard row at once. Wrap each per-type read so a failure degrades to an empty slice for that type only; the failing chart self-fetches through its existing fallback. A degraded_types counter rides the wide event.
This commit is contained in:
1 parent
1a73afa7e9
commit
04b0f38537
2 files changed
+39
-9
No files matched your search
@@ -117,4 +117,24 @@ describe("GET /api/measurements/series-batch", () => {
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(readDailySeries)).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("isolates a single type's failure to an empty slice, others still return", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
const weightRows = [
|
||||
{ type: "WEIGHT", value: 80, measuredAt: FROM, count: 1 },
|
||||
];
|
||||
vi.mocked(readDailySeries).mockImplementation(async ({ type }) => {
|
||||
if (type === "WEIGHT") return weightRows as never;
|
||||
throw new Error("transient db reject");
|
||||
});
|
||||
|
||||
const res = await GET(
|
||||
req(`types=WEIGHT,BLOOD_PRESSURE_SYS&from=${FROM}&to=${TO}`),
|
||||
);
|
||||
// One type rejecting must NOT 500 the whole batch.
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.data.series.WEIGHT).toEqual(weightRows);
|
||||
expect(body.data.series.BLOOD_PRESSURE_SYS).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -63,18 +63,27 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// so the batched reader doesn't re-query it per type.
|
||||
const priorityJson = await loadUserSourcePriority(user.id);
|
||||
|
||||
// Per-type isolation: a single type's transient DB reject degrades to an
|
||||
// empty slice for THAT type only, never a 500 that blanks every chart in
|
||||
// the row. The failing chart self-fetches through its existing fallback.
|
||||
const limit = pLimit(SERIES_BATCH_CONCURRENCY);
|
||||
let degradedTypes = 0;
|
||||
const entries = await Promise.all(
|
||||
uniqueTypes.map((type) =>
|
||||
limit(async () => {
|
||||
const rows = await readDailySeries({
|
||||
userId: user.id,
|
||||
type,
|
||||
from,
|
||||
to,
|
||||
priorityJson,
|
||||
});
|
||||
return [type, rows] as const;
|
||||
limit(async (): Promise<readonly [MeasurementType, DailySeriesRow[]]> => {
|
||||
try {
|
||||
const rows = await readDailySeries({
|
||||
userId: user.id,
|
||||
type,
|
||||
from,
|
||||
to,
|
||||
priorityJson,
|
||||
});
|
||||
return [type, rows] as const;
|
||||
} catch {
|
||||
degradedTypes += 1;
|
||||
return [type, []] as const;
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -89,6 +98,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
meta: {
|
||||
type_count: uniqueTypes.length,
|
||||
total: entries.reduce((sum, [, rows]) => sum + rows.length, 0),
|
||||
degraded_types: degradedTypes,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user