@unbrained/pm-web 2026.9.23 → 2026.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +64 -6
- package/dist/app.js +8 -5
- package/dist/app.js.map +1 -1
- package/dist/db.d.ts +2 -2
- package/dist/db.js +30 -2
- package/dist/db.js.map +1 -1
- package/dist/idempotency.d.ts +59 -0
- package/dist/idempotency.js +361 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/routes/groups.js +10 -1
- package/dist/routes/groups.js.map +1 -1
- package/dist/routes/pm.js +125 -108
- package/dist/routes/pm.js.map +1 -1
- package/dist/services/package-catalog.js +1 -1
- package/dist/services/package-catalog.js.map +1 -1
- package/dist/services/pm-runner.d.ts +3 -2
- package/dist/services/pm-runner.js +19 -6
- package/dist/services/pm-runner.js.map +1 -1
- package/manifest.json +2 -2
- package/package.json +23 -21
- package/public/src/api.js +79 -0
- package/public/src/api.js.map +1 -1
- package/public/src/api.ts +77 -0
- package/public/src/app.js +3 -1
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +3 -1
- package/public/src/offline-recovery.js +65 -0
- package/public/src/offline-recovery.js.map +1 -0
- package/public/src/offline-recovery.ts +68 -0
- package/public/src/sw.ts +431 -33
- package/public/src/views/auth.js +3 -1
- package/public/src/views/auth.js.map +1 -1
- package/public/src/views/auth.ts +3 -1
- package/public/src/views/graph.js +8 -2
- package/public/src/views/graph.js.map +1 -1
- package/public/src/views/graph.ts +9 -2
- package/public/sw.js +379 -26
- package/sql/schema.sql +25 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { pool } from "./db.js";
|
|
3
|
+
import { extractToken, verifyToken } from "./auth.js";
|
|
4
|
+
import { isUnsafeMethod } from "./rate-limit.js";
|
|
5
|
+
/**
|
|
6
|
+
* Minimum length an `Idempotency-Key` header value may have.
|
|
7
|
+
*
|
|
8
|
+
* Keys are generated by the offline mutation queue as 128-bit UUID-shaped
|
|
9
|
+
* strings, so anything shorter than this is a client bug rather than a key,
|
|
10
|
+
* and accepting it would let a truncated or hand-typed value collide with a
|
|
11
|
+
* future real key.
|
|
12
|
+
*/
|
|
13
|
+
export const IDEMPOTENCY_KEY_MIN_LENGTH = 8;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum length an `Idempotency-Key` header value may have.
|
|
16
|
+
*
|
|
17
|
+
* Bounds the stored key so a hostile client cannot stuff multi-kilobyte
|
|
18
|
+
* strings into the unique index; real keys are 36-character UUID strings.
|
|
19
|
+
*/
|
|
20
|
+
export const IDEMPOTENCY_KEY_MAX_LENGTH = 200;
|
|
21
|
+
/**
|
|
22
|
+
* How long a duplicate waits for the in-flight original to settle, in
|
|
23
|
+
* milliseconds, before answering 409. Override: `PM_WEB_IDEMPOTENCY_WAIT_MS`.
|
|
24
|
+
*
|
|
25
|
+
* The default covers the longest mutating route (pm CLI commands time out at
|
|
26
|
+
* 30 s), so a concurrent duplicate normally observes the original's stored
|
|
27
|
+
* outcome instead of being told to retry.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_WAIT_MS = 30_000;
|
|
30
|
+
/** How often a waiting duplicate re-checks the store, in milliseconds. */
|
|
31
|
+
const DEFAULT_POLL_MS = 25;
|
|
32
|
+
/** Age after which an unfinished execution is reported as outcome unknown. */
|
|
33
|
+
const DEFAULT_PENDING_TIMEOUT_MS = 300_000;
|
|
34
|
+
/**
|
|
35
|
+
* How long a settled idempotency record is kept before it is swept, in
|
|
36
|
+
* milliseconds. Override: `PM_WEB_IDEMPOTENCY_RETENTION_MS`.
|
|
37
|
+
*
|
|
38
|
+
* Completed outcomes are deduplicated within this window. A retry after expiry
|
|
39
|
+
* can execute again. Unknown outcomes have the same bounded retention, measured
|
|
40
|
+
* from settlement; stale pending intents must settle before they can expire.
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
43
|
+
/** Handler assertions are process-local and cannot be forged through request headers. */
|
|
44
|
+
const preCommitFailures = new WeakSet();
|
|
45
|
+
/** Mark a failure only after proving no mutation committed (for example, confirmed rollback before COMMIT was attempted). */
|
|
46
|
+
export function markIdempotencyPreCommitFailure(res) {
|
|
47
|
+
preCommitFailures.add(res);
|
|
48
|
+
}
|
|
49
|
+
/** Machine-readable refusal reused by failure settlement and stale-intent recovery. */
|
|
50
|
+
const UNKNOWN_OUTCOME = JSON.stringify({
|
|
51
|
+
code: "PM_IDEMPOTENCY_OUTCOME_UNKNOWN",
|
|
52
|
+
error: "Idempotency outcome unknown; reconcile the original mutation before submitting new work",
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Compute the fingerprint that binds one idempotency key to one request.
|
|
56
|
+
*
|
|
57
|
+
* The fingerprint covers the method, the full path with query string, and the
|
|
58
|
+
* parsed JSON body, so a key can only ever be replayed for the identical
|
|
59
|
+
* request it was created for. Re-serializing the parsed body is stable here
|
|
60
|
+
* because a retry sends the same bytes (the offline queue persists the exact
|
|
61
|
+
* body string), which parses to the same property order; a different body —
|
|
62
|
+
* different properties or different values — produces a different fingerprint
|
|
63
|
+
* and is rejected rather than silently deduplicated.
|
|
64
|
+
*
|
|
65
|
+
* @param method - The HTTP method of the request.
|
|
66
|
+
* @param path - The request path including any query string.
|
|
67
|
+
* @param body - The parsed request body, or `null` when the request has none.
|
|
68
|
+
* @returns A hex SHA-256 digest over the request identity.
|
|
69
|
+
*/
|
|
70
|
+
export function requestFingerprint(method, path, body) {
|
|
71
|
+
return crypto
|
|
72
|
+
.createHash("sha256")
|
|
73
|
+
.update(`${method}\n${path}\n${JSON.stringify(body ?? null)}`)
|
|
74
|
+
.digest("hex");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a positive millisecond duration from the environment.
|
|
78
|
+
*
|
|
79
|
+
* Accepts only finite positive numbers; anything else (unset, empty, zero,
|
|
80
|
+
* negative, non-numeric) falls back to the shipped default so a typo in an
|
|
81
|
+
* environment variable degrades to the documented behaviour instead of
|
|
82
|
+
* disabling a timeout entirely.
|
|
83
|
+
*
|
|
84
|
+
* @param name - The environment variable to read.
|
|
85
|
+
* @param fallback - The default used when the variable is unusable.
|
|
86
|
+
* @returns The resolved duration in milliseconds.
|
|
87
|
+
*/
|
|
88
|
+
function envDurationMs(name, fallback) {
|
|
89
|
+
const parsed = Number(process.env[name]);
|
|
90
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Insert the intent to execute one keyed request, or report a conflict.
|
|
94
|
+
*
|
|
95
|
+
* `ON CONFLICT DO NOTHING` makes the insert atomic against a concurrent
|
|
96
|
+
* duplicate: exactly one request can own a (user, key) pair at a time, and
|
|
97
|
+
* the loser learns it lost by receiving no row rather than by an error it
|
|
98
|
+
* would have to disambiguate from a genuine failure.
|
|
99
|
+
*
|
|
100
|
+
* @returns The id of the inserted row when this call owns the execution, or
|
|
101
|
+
* `null` when the key is already held.
|
|
102
|
+
*/
|
|
103
|
+
async function insertPendingKey(userId, key, method, path, fingerprint) {
|
|
104
|
+
const result = await pool.query(`INSERT INTO pm_idempotency_keys (user_id, idempotency_key, method, path, request_fingerprint)
|
|
105
|
+
SELECT id, $2, $3, $4, $5 FROM pm_users WHERE id = $1 FOR KEY SHARE
|
|
106
|
+
ON CONFLICT (user_id, idempotency_key) DO NOTHING
|
|
107
|
+
RETURNING id`, [userId, key, method, path, fingerprint]);
|
|
108
|
+
const row = result.rows[0];
|
|
109
|
+
return row?.id ?? null;
|
|
110
|
+
}
|
|
111
|
+
/** Check that a verified token still names an existing account before keying. */
|
|
112
|
+
async function accountExists(userId) {
|
|
113
|
+
if (typeof userId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId))
|
|
114
|
+
return false;
|
|
115
|
+
const result = await pool.query("SELECT id FROM pm_users WHERE id = $1", [userId]);
|
|
116
|
+
return result.rows.length > 0;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Read the current idempotency record for one (account, key) pair.
|
|
120
|
+
*
|
|
121
|
+
* @returns The record, or `null` when no row exists (which can only happen
|
|
122
|
+
* transiently: a settled record is swept only by retention, and a pending
|
|
123
|
+
* one only by an explicit transient refusal).
|
|
124
|
+
*/
|
|
125
|
+
async function selectKey(userId, key) {
|
|
126
|
+
const result = await pool.query(`SELECT id, request_fingerprint, status_code, response_body, response_content_type, created_at, outcome_state
|
|
127
|
+
FROM pm_idempotency_keys
|
|
128
|
+
WHERE user_id = $1 AND idempotency_key = $2`, [userId, key]);
|
|
129
|
+
return result.rows[0] ?? null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sweep idempotency records past the retention window.
|
|
133
|
+
*
|
|
134
|
+
* Called at most once per minute per guard instance. Stale pending records first
|
|
135
|
+
* become terminal unknown outcomes; retention starts at that settlement time.
|
|
136
|
+
*/
|
|
137
|
+
async function deleteExpiredKeys() {
|
|
138
|
+
await pool.query(`UPDATE pm_idempotency_keys SET outcome_state = 'outcome_unknown', status_code = 409,
|
|
139
|
+
response_body = $2, response_content_type = 'application/json', updated_at = NOW()
|
|
140
|
+
WHERE status_code IS NULL AND outcome_state = 'pending'
|
|
141
|
+
AND created_at < NOW() - ($1::double precision * interval '1 ms')`, [envDurationMs("PM_WEB_IDEMPOTENCY_PENDING_TIMEOUT_MS", DEFAULT_PENDING_TIMEOUT_MS), UNKNOWN_OUTCOME]);
|
|
142
|
+
await pool.query(`DELETE FROM pm_idempotency_keys
|
|
143
|
+
WHERE status_code IS NOT NULL AND updated_at < NOW() - ($1::double precision * interval '1 ms')`, [envDurationMs("PM_WEB_IDEMPOTENCY_RETENTION_MS", DEFAULT_RETENTION_MS)]);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Store a definitive outcome. Release only explicit pre-mutation refusals
|
|
147
|
+
* (425, 429, or handler-proven pre-commit failures). All other server failures
|
|
148
|
+
* settle as terminal unknown outcomes because they may follow a commit.
|
|
149
|
+
*/
|
|
150
|
+
async function persistOutcome(rowId, statusCode, body, contentType, preCommit = false) {
|
|
151
|
+
if (statusCode === 425 || statusCode === 429 || (statusCode >= 500 && preCommit)) {
|
|
152
|
+
await pool.query(`DELETE FROM pm_idempotency_keys WHERE id = $1 AND outcome_state = 'pending'`, [rowId]);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const unknown = statusCode >= 500;
|
|
156
|
+
await pool.query(`UPDATE pm_idempotency_keys
|
|
157
|
+
SET status_code = $2, response_body = $3, response_content_type = $4, outcome_state = $5, updated_at = NOW()
|
|
158
|
+
WHERE id = $1 AND outcome_state = 'pending'`, [rowId, unknown ? 409 : statusCode, unknown ? UNKNOWN_OUTCOME : body,
|
|
159
|
+
unknown ? "application/json" : contentType, unknown ? "outcome_unknown" : "completed"]);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Answer a duplicate from the stored outcome of the original request.
|
|
163
|
+
*
|
|
164
|
+
* The `Idempotency-Replayed` header lets the client (and the tests) tell a
|
|
165
|
+
* replay from a fresh execution without diffing bodies. Responses that had no
|
|
166
|
+
* body keep having none; responses that had one are served with the content
|
|
167
|
+
* type they originally carried.
|
|
168
|
+
*/
|
|
169
|
+
function replayStored(record, res) {
|
|
170
|
+
res.setHeader("Idempotency-Replayed", "true");
|
|
171
|
+
if (record.response_body === null) {
|
|
172
|
+
res.status(record.status_code ?? 500).end();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
res.setHeader("Content-Type", record.response_content_type ?? "application/json");
|
|
176
|
+
res.status(record.status_code ?? 500).send(record.response_body);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Answer a settled record, or reject a key recycled onto a different request.
|
|
180
|
+
*
|
|
181
|
+
* @param record - The settled record held under the client's key.
|
|
182
|
+
* @param fingerprint - The fingerprint of the request being answered.
|
|
183
|
+
* @param res - The response to answer on.
|
|
184
|
+
*/
|
|
185
|
+
function finishSettled(record, fingerprint, res) {
|
|
186
|
+
if (record.request_fingerprint !== fingerprint) {
|
|
187
|
+
res.status(422).json({
|
|
188
|
+
error: "Idempotency-Key was already used for a different request",
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
replayStored(record, res);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Capture the executing request's response and persist it when it finishes.
|
|
196
|
+
*
|
|
197
|
+
* `res.json`/`res.send` are wrapped once to record the body the handler
|
|
198
|
+
* produced, and the `finish` listener persists whatever status the response
|
|
199
|
+
* left with — so handlers that never call `json`/`send` (204s, `end()`) are
|
|
200
|
+
* still memoized, just without a body.
|
|
201
|
+
*
|
|
202
|
+
* @param rowId - The id of this execution's idempotency record.
|
|
203
|
+
* @param res - The executing response.
|
|
204
|
+
*/
|
|
205
|
+
function captureOutcome(rowId, res) {
|
|
206
|
+
let capturedBody = null;
|
|
207
|
+
const originalJson = res.json.bind(res);
|
|
208
|
+
const originalSend = res.send.bind(res);
|
|
209
|
+
/** Capture the first response serialization without recording request content. */
|
|
210
|
+
const capture = (body) => {
|
|
211
|
+
if (capturedBody !== null)
|
|
212
|
+
return;
|
|
213
|
+
capturedBody = typeof body === "string" ? body : JSON.stringify(body ?? null);
|
|
214
|
+
};
|
|
215
|
+
res.json = (body) => {
|
|
216
|
+
capture(body);
|
|
217
|
+
return originalJson(body);
|
|
218
|
+
};
|
|
219
|
+
res.send = (body) => {
|
|
220
|
+
capture(body);
|
|
221
|
+
return originalSend(body);
|
|
222
|
+
};
|
|
223
|
+
let finished = false;
|
|
224
|
+
/** Finish and premature close share one persistence path, avoiding conflicting writes. */
|
|
225
|
+
const settle = (statusCode) => {
|
|
226
|
+
const contentType = res.getHeader("Content-Type");
|
|
227
|
+
void persistOutcome(rowId, statusCode, capturedBody, typeof contentType === "string" ? contentType : null, preCommitFailures.has(res)).catch((error) => {
|
|
228
|
+
// Do not log response bodies, tokens or database error details.
|
|
229
|
+
console.error("Failed to persist idempotency outcome; key remains pending", error instanceof Error ? error.name : "unknown error");
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
res.on("finish", () => {
|
|
233
|
+
finished = true;
|
|
234
|
+
settle(res.statusCode);
|
|
235
|
+
});
|
|
236
|
+
res.on("close", () => {
|
|
237
|
+
if (!finished)
|
|
238
|
+
settle(500);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Build the middleware that makes keyed mutating requests at-most-once.
|
|
243
|
+
*
|
|
244
|
+
* Scope: keyed mutating `/api` requests outside `/api/auth`. Auth responses
|
|
245
|
+
* always execute live so session cookies are never lost to body-only replay.
|
|
246
|
+
* An expected-account header is checked against an existing authenticated user
|
|
247
|
+
* before execution or claiming, including recovery probes to `/api/auth/me`.
|
|
248
|
+
*
|
|
249
|
+
* Exactly-once per account: the key is stored together with the authenticated
|
|
250
|
+
* user, so two accounts using the same key never interfere. The first
|
|
251
|
+
* execution atomically claims the (account, key) pair, stores its outcome
|
|
252
|
+
* when the response finishes, and any duplicate — a retry after an ambiguous
|
|
253
|
+
* response the client never saw, or a concurrent duplicate of an in-flight
|
|
254
|
+
* request — is answered from that stored outcome without re-executing. A key
|
|
255
|
+
* reused for a *different* request is rejected with 422 rather than silently
|
|
256
|
+
* deduplicated; an unfinished record older than the pending timeout is
|
|
257
|
+
* answered with 409 outcome unknown without re-execution; an in-flight
|
|
258
|
+
* duplicate waits for it to settle and answers 409 only at the
|
|
259
|
+
* deadline.
|
|
260
|
+
*
|
|
261
|
+
* @returns An Express middleware enforcing the contract on its mount.
|
|
262
|
+
*/
|
|
263
|
+
export function idempotencyGuard() {
|
|
264
|
+
let nextSweepAt = 0;
|
|
265
|
+
return async (req, res, next) => {
|
|
266
|
+
const authRoute = /^\/api\/auth(?:\/|$)/i.test(req.originalUrl.split("?")[0]);
|
|
267
|
+
const expectedAccount = req.get("x-pm-expected-account");
|
|
268
|
+
const checkExpected = expectedAccount !== undefined
|
|
269
|
+
&& (!authRoute || /^\/api\/auth\/me\/?(?:\?|$)/i.test(req.originalUrl));
|
|
270
|
+
const header = req.get("idempotency-key");
|
|
271
|
+
const keyed = isUnsafeMethod(req.method) && !authRoute && header !== undefined;
|
|
272
|
+
if (!keyed && !checkExpected) {
|
|
273
|
+
next();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Only record requests we can attribute to an account; an unauthenticated
|
|
277
|
+
// or invalid-session request is passed through for the route to refuse,
|
|
278
|
+
// so an anonymous probe can never occupy a key.
|
|
279
|
+
let userId = null;
|
|
280
|
+
const token = extractToken(req);
|
|
281
|
+
if (token) {
|
|
282
|
+
try {
|
|
283
|
+
userId = verifyToken(token).userId;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
userId = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (userId !== null && !await accountExists(userId))
|
|
290
|
+
userId = null;
|
|
291
|
+
if (checkExpected && (userId === null || expectedAccount !== userId)) {
|
|
292
|
+
res.status(409).json({ code: "PM_EXPECTED_ACCOUNT_MISMATCH", error: "The signed-in account differs from the account that approved this work" });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!keyed || userId === null) {
|
|
296
|
+
next();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const key = header.trim();
|
|
300
|
+
if (key.length < IDEMPOTENCY_KEY_MIN_LENGTH || key.length > IDEMPOTENCY_KEY_MAX_LENGTH) {
|
|
301
|
+
res.status(400).json({
|
|
302
|
+
error: `Idempotency-Key must be between ${IDEMPOTENCY_KEY_MIN_LENGTH} and ${IDEMPOTENCY_KEY_MAX_LENGTH} characters`,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (Date.now() >= nextSweepAt) {
|
|
307
|
+
// Cleanup can fail only before an intent exists, leaving the key retryable.
|
|
308
|
+
nextSweepAt = Date.now() + 60_000;
|
|
309
|
+
await deleteExpiredKeys();
|
|
310
|
+
}
|
|
311
|
+
const fingerprint = requestFingerprint(req.method, req.originalUrl, req.body);
|
|
312
|
+
const deadline = Date.now() + envDurationMs("PM_WEB_IDEMPOTENCY_WAIT_MS", DEFAULT_WAIT_MS);
|
|
313
|
+
let rowId = await insertPendingKey(userId, key, req.method, req.originalUrl, fingerprint);
|
|
314
|
+
while (rowId === null) {
|
|
315
|
+
const record = await selectKey(userId, key);
|
|
316
|
+
if (record && record.status_code !== null) {
|
|
317
|
+
// The original settled before this duplicate arrived: replay it.
|
|
318
|
+
finishSettled(record, fingerprint, res);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (record && record.request_fingerprint !== fingerprint) {
|
|
322
|
+
res.status(422).json({
|
|
323
|
+
error: "Idempotency-Key was already used for a different request",
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (record === null) {
|
|
328
|
+
// Deletion can race token validation; the locked INSERT SELECT refuses
|
|
329
|
+
// a missing user without a foreign-key failure or a permanent wait.
|
|
330
|
+
if (!await accountExists(userId)) {
|
|
331
|
+
next();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
rowId = await insertPendingKey(userId, key, req.method, req.originalUrl, fingerprint);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (isStalePending(record)) {
|
|
338
|
+
await persistOutcome(record.id, 500, null, null);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (Date.now() >= deadline) {
|
|
342
|
+
res.status(409).json({ code: "PM_IDEMPOTENCY_IN_FLIGHT", error: "The request with this Idempotency-Key is still executing; retry later" });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
await new Promise((resolve) => { setTimeout(resolve, envDurationMs("PM_WEB_IDEMPOTENCY_POLL_MS", DEFAULT_POLL_MS)); });
|
|
346
|
+
}
|
|
347
|
+
captureOutcome(rowId, res);
|
|
348
|
+
next();
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Report whether a pending record is old enough to be a crashed execution.
|
|
353
|
+
*
|
|
354
|
+
* @param record - The still-pending record to age-check.
|
|
355
|
+
* @returns `true` when the record predates the pending timeout.
|
|
356
|
+
*/
|
|
357
|
+
function isStalePending(record) {
|
|
358
|
+
const timeoutMs = envDurationMs("PM_WEB_IDEMPOTENCY_PENDING_TIMEOUT_MS", DEFAULT_PENDING_TIMEOUT_MS);
|
|
359
|
+
return Date.now() - record.created_at.getTime() > timeoutMs;
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -262,7 +262,7 @@ function processAlive(pid) {
|
|
|
262
262
|
}
|
|
263
263
|
export default defineExtension({
|
|
264
264
|
name: "pm-web",
|
|
265
|
-
version: "2026.
|
|
265
|
+
version: "2026.10.5",
|
|
266
266
|
activate(api) {
|
|
267
267
|
// -----------------------------------------------------------------------
|
|
268
268
|
// Command: pm web [--port <port>] [--detach]
|
package/dist/routes/groups.js
CHANGED
|
@@ -3,6 +3,7 @@ import { pool } from "../db.js";
|
|
|
3
3
|
import { requireAuth } from "../middleware/auth.js";
|
|
4
4
|
import { routeParam, uuidParamGuard } from "./route-params.js";
|
|
5
5
|
import { findUserByEmail, notFoundWhenEmpty, parseGroupInput } from "./route-helpers.js";
|
|
6
|
+
import { markIdempotencyPreCommitFailure } from "../idempotency.js";
|
|
6
7
|
const router = Router();
|
|
7
8
|
router.use(requireAuth);
|
|
8
9
|
// Both group ids and the member :userId are UUIDs; a malformed one is a bad
|
|
@@ -31,7 +32,11 @@ router.post("/", async (req, res) => {
|
|
|
31
32
|
if (!input)
|
|
32
33
|
return;
|
|
33
34
|
const { name, description } = input;
|
|
34
|
-
const client = await pool.connect()
|
|
35
|
+
const client = await pool.connect().catch((error) => {
|
|
36
|
+
markIdempotencyPreCommitFailure(res);
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
39
|
+
let commitAttempted = false;
|
|
35
40
|
try {
|
|
36
41
|
await client.query("BEGIN");
|
|
37
42
|
const groupResult = await client.query(`INSERT INTO pm_groups (owner_id, name, description)
|
|
@@ -40,11 +45,15 @@ router.post("/", async (req, res) => {
|
|
|
40
45
|
const group = groupResult.rows[0];
|
|
41
46
|
// Add owner as a member with role 'owner'
|
|
42
47
|
await client.query(`INSERT INTO pm_group_members (group_id, user_id, role) VALUES ($1, $2, 'owner')`, [group.id, req.user.userId]);
|
|
48
|
+
commitAttempted = true;
|
|
43
49
|
await client.query("COMMIT");
|
|
44
50
|
res.status(201).json({ group: { ...groupResult.rows[0], role: "owner", member_count: "1" } });
|
|
45
51
|
}
|
|
46
52
|
catch (err) {
|
|
47
53
|
await client.query("ROLLBACK");
|
|
54
|
+
// A failed COMMIT can have succeeded remotely; later ROLLBACK cannot prove otherwise.
|
|
55
|
+
if (!commitAttempted)
|
|
56
|
+
markIdempotencyPreCommitFailure(res);
|
|
48
57
|
console.error("Create group error:", err);
|
|
49
58
|
res.status(500).json({ error: "Failed to create group" });
|
|
50
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/routes/groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,WAAW,EAAoB,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/routes/groups.ts"],"names":[],"mappings":"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"}
|