mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(api): take a new idempotency key once the server has answered
A create form kept its key until a save succeeded. The server stores a refusal under the key for a day, so after a 422 or 409 the corrected retry ran as a different body without any protection, and a timeout on it could store the entry twice; an unchanged retry after the cause was gone replayed the stale refusal until the form was closed. Any answer now settles the key. Only a timeout, a dropped connection, or the 409 that says the first request under the key is still running (`X-Idempotent-Replay: false`, now `ApiError.inFlight`) keeps it, so those retries still replay the first answer.
This commit is contained in:
1 parent
0c77eaa085
commit
9683daf4f2
3 files changed
+73
-8
No files matched your search
@@ -79,6 +79,12 @@ export class ApiError extends Error {
|
||||
* so a form can mark the one the server refused. Empty when it named none.
|
||||
*/
|
||||
readonly issuePaths: readonly string[];
|
||||
/**
|
||||
* The idempotency layer's "a request under this key is still running" (a
|
||||
* 409 with `X-Idempotent-Replay: false`): nothing was decided, and a retry
|
||||
* under the same key replays the first request's answer.
|
||||
*/
|
||||
readonly inFlight: boolean;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
@@ -86,12 +92,14 @@ export class ApiError extends Error {
|
||||
meta?: Record<string, unknown>,
|
||||
requestId?: string | null,
|
||||
issuePaths: readonly string[] = [],
|
||||
inFlight = false,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
this.status = status;
|
||||
this.meta = meta;
|
||||
this.issuePaths = issuePaths;
|
||||
this.inFlight = inFlight;
|
||||
const fromMeta =
|
||||
typeof meta?.requestId === "string" && meta.requestId.length > 0
|
||||
? meta.requestId
|
||||
@@ -133,6 +141,7 @@ export async function throwApiError(res: Response): Promise<never> {
|
||||
meta,
|
||||
res.headers.get("x-request-id"),
|
||||
issuePaths,
|
||||
res.status === 409 && res.headers.get("x-idempotent-replay") === "false",
|
||||
);
|
||||
// A server-side failure is worth an id in the session's support info,
|
||||
// whether or not the call site ends up showing it.
|
||||
|
||||
Reference in new issue
Block a user