pipework 0.264.3 → 0.266.0
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 +8 -0
- package/REFERENCE.md +23 -4
- package/dist/data/migrate/internal-definitions.d.ts.map +1 -1
- package/dist/data/migrate/internal-definitions.js +2 -1
- package/dist/data/migrate/internal-definitions.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/alerts/sources.d.ts +1 -1
- package/dist/infra/alerts/sources.d.ts.map +1 -1
- package/dist/infra/alerts/sources.js +27 -10
- package/dist/infra/alerts/sources.js.map +1 -1
- package/dist/infra/quota/handle.d.ts +27 -3
- package/dist/infra/quota/handle.d.ts.map +1 -1
- package/dist/infra/quota/handle.js +128 -31
- package/dist/infra/quota/handle.js.map +1 -1
- package/dist/infra/quota/index.d.ts +1 -1
- package/dist/infra/quota/index.d.ts.map +1 -1
- package/dist/infra/quota/index.js.map +1 -1
- package/dist/infra/quota/schema.d.ts +20 -0
- package/dist/infra/quota/schema.d.ts.map +1 -1
- package/dist/infra/quota/schema.js +12 -0
- package/dist/infra/quota/schema.js.map +1 -1
- package/dist/request/context/types.d.ts +2 -0
- package/dist/request/context/types.d.ts.map +1 -1
- package/dist/request/di/resolve.d.ts +1 -1
- package/dist/request/di/resolve.d.ts.map +1 -1
- package/dist/request/http/aborted.d.ts +12 -0
- package/dist/request/http/aborted.d.ts.map +1 -0
- package/dist/request/http/aborted.js +14 -0
- package/dist/request/http/aborted.js.map +1 -0
- package/dist/request/http/namespace.d.ts +3 -1
- package/dist/request/http/namespace.d.ts.map +1 -1
- package/dist/request/http/namespace.js +3 -1
- package/dist/request/http/namespace.js.map +1 -1
- package/dist/request/http/server.d.ts.map +1 -1
- package/dist/request/http/server.js +23 -3
- package/dist/request/http/server.js.map +1 -1
- package/dist/request/http/types.d.ts +1 -0
- package/dist/request/http/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/data/migrate/internal-definitions.ts +2 -1
- package/src/index.ts +3 -1
- package/src/infra/alerts/sources.ts +27 -10
- package/src/infra/quota/handle.ts +157 -35
- package/src/infra/quota/index.ts +1 -0
- package/src/infra/quota/schema.ts +13 -0
- package/src/request/context/types.ts +2 -0
- package/src/request/di/resolve.ts +1 -1
- package/src/request/http/aborted.ts +15 -0
- package/src/request/http/namespace.ts +3 -1
- package/src/request/http/server.ts +21 -1
- package/src/request/http/types.ts +1 -0
|
@@ -52,12 +52,40 @@ function logRecordFailure(key, err) {
|
|
|
52
52
|
getBaseLogger().error({ err, key }, 'quota could not record the budget from a response');
|
|
53
53
|
void recordFault('quota-record', faultMessage(err), { key });
|
|
54
54
|
}
|
|
55
|
+
const DEFAULT_RETAIN_WINDOWS = 48;
|
|
56
|
+
const UNLABELLED = 'unlabelled';
|
|
57
|
+
const toDate = (v) => new Date(v);
|
|
55
58
|
export function createQuota(config = {}) {
|
|
59
|
+
const retain = config.retainWindows ?? DEFAULT_RETAIN_WINDOWS;
|
|
60
|
+
if (!Number.isInteger(retain) || retain < 1) {
|
|
61
|
+
throw new Error(`[pipework] quota.create retainWindows must be a positive integer, got ${String(config.retainWindows)}.`);
|
|
62
|
+
}
|
|
56
63
|
if (config.connection !== undefined && config.database !== undefined) {
|
|
57
64
|
throw new Error('[pipework] quota.create takes connection or database, not both.');
|
|
58
65
|
}
|
|
59
66
|
const read = config.read ?? defaultRead;
|
|
60
67
|
const db = () => config.connection !== undefined ? config.connection.drizzle : resolveDb(config.database);
|
|
68
|
+
/** A new live window began: calls counted before any window was known move into it, and windows past the retention are deleted. */
|
|
69
|
+
async function startWindow(tx, key) {
|
|
70
|
+
await tx.execute(sql `
|
|
71
|
+
WITH moved AS (
|
|
72
|
+
DELETE FROM outbound_quota_spend WHERE key = ${key} AND window_end = 'infinity'::timestamptz
|
|
73
|
+
RETURNING caller, sent, refused)
|
|
74
|
+
INSERT INTO outbound_quota_spend (key, caller, window_end, sent, refused)
|
|
75
|
+
SELECT ${key}, caller, (SELECT reset_at FROM outbound_quota WHERE key = ${key}), sent, refused FROM moved
|
|
76
|
+
ON CONFLICT (key, caller, window_end) DO UPDATE SET
|
|
77
|
+
sent = outbound_quota_spend.sent + EXCLUDED.sent,
|
|
78
|
+
refused = outbound_quota_spend.refused + EXCLUDED.refused,
|
|
79
|
+
updated_at = clock_timestamp()`);
|
|
80
|
+
await tx.execute(sql `
|
|
81
|
+
DELETE FROM outbound_quota_spend
|
|
82
|
+
WHERE key = ${key} AND window_end <> ALL (
|
|
83
|
+
SELECT window_end FROM (
|
|
84
|
+
SELECT window_end FROM outbound_quota_spend WHERE key = ${key} AND window_end <> 'infinity'::timestamptz
|
|
85
|
+
UNION SELECT reset_at FROM outbound_quota WHERE key = ${key}
|
|
86
|
+
ORDER BY window_end DESC LIMIT ${retain}) kept)
|
|
87
|
+
AND window_end <> 'infinity'::timestamptz`);
|
|
88
|
+
}
|
|
61
89
|
async function record(key, input) {
|
|
62
90
|
const headers = toHeaders(input);
|
|
63
91
|
const budget = read(headers);
|
|
@@ -68,26 +96,38 @@ export function createQuota(config = {}) {
|
|
|
68
96
|
if (budget !== null) {
|
|
69
97
|
// Same window: lowest remaining. Different window: a live response (reset in the future) replaces the row; a past reset is ignored.
|
|
70
98
|
const limit = budget.limit ?? null;
|
|
71
|
-
await conn.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
remaining
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
await conn.transaction(async (tx) => {
|
|
100
|
+
const before = await tx.execute(sql `SELECT reset_at FROM outbound_quota WHERE key = ${key} FOR UPDATE`);
|
|
101
|
+
const prior = before[0] === undefined ? null : toDate(before[0]['reset_at']);
|
|
102
|
+
const after = await tx.execute(sql `
|
|
103
|
+
INSERT INTO outbound_quota (key, remaining, "limit", reset_at, updated_at)
|
|
104
|
+
VALUES (${key}, ${budget.remaining}, ${limit}, ${iso(budget.resetAt)}::timestamptz, clock_timestamp())
|
|
105
|
+
ON CONFLICT (key) DO UPDATE SET
|
|
106
|
+
remaining = CASE
|
|
107
|
+
WHEN date_trunc('second', outbound_quota.reset_at) = date_trunc('second', EXCLUDED.reset_at)
|
|
108
|
+
THEN LEAST(outbound_quota.remaining, EXCLUDED.remaining)
|
|
109
|
+
WHEN EXCLUDED.reset_at > now() THEN EXCLUDED.remaining
|
|
110
|
+
ELSE outbound_quota.remaining END,
|
|
111
|
+
"limit" = CASE
|
|
112
|
+
WHEN date_trunc('second', outbound_quota.reset_at) = date_trunc('second', EXCLUDED.reset_at)
|
|
113
|
+
OR EXCLUDED.reset_at > now()
|
|
114
|
+
THEN COALESCE(EXCLUDED."limit", outbound_quota."limit")
|
|
115
|
+
ELSE outbound_quota."limit" END,
|
|
116
|
+
reset_at = CASE
|
|
117
|
+
WHEN date_trunc('second', outbound_quota.reset_at) = date_trunc('second', EXCLUDED.reset_at)
|
|
118
|
+
THEN outbound_quota.reset_at
|
|
119
|
+
WHEN EXCLUDED.reset_at > now() THEN EXCLUDED.reset_at
|
|
120
|
+
ELSE outbound_quota.reset_at END,
|
|
121
|
+
updated_at = clock_timestamp()
|
|
122
|
+
RETURNING reset_at, reset_at > now() AS live`);
|
|
123
|
+
const row = after[0];
|
|
124
|
+
if (row === undefined || row['live'] !== true)
|
|
125
|
+
return;
|
|
126
|
+
const reset = toDate(row['reset_at']);
|
|
127
|
+
if (prior !== null && Math.floor(prior.getTime() / 1000) === Math.floor(reset.getTime() / 1000))
|
|
128
|
+
return;
|
|
129
|
+
await startWindow(tx, key);
|
|
130
|
+
});
|
|
91
131
|
}
|
|
92
132
|
if (retryAfter !== null) {
|
|
93
133
|
await conn.execute(sql `
|
|
@@ -111,33 +151,90 @@ export function createQuota(config = {}) {
|
|
|
111
151
|
resetAt: new Date(row['reset_at']),
|
|
112
152
|
};
|
|
113
153
|
}
|
|
114
|
-
async function take(key, cls) {
|
|
154
|
+
async function take(key, cls, caller) {
|
|
115
155
|
const conn = db();
|
|
116
156
|
const reserve = config.reserve?.[cls] ?? 0;
|
|
157
|
+
// The unit and its count are one statement, so a count and a spend cannot disagree.
|
|
117
158
|
const taken = await conn.execute(sql `
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
159
|
+
WITH taken AS (
|
|
160
|
+
UPDATE outbound_quota SET remaining = remaining - 1, updated_at = clock_timestamp()
|
|
161
|
+
WHERE key = ${key} AND reset_at > now() AND remaining > ${reserve}
|
|
162
|
+
RETURNING key, reset_at)
|
|
163
|
+
INSERT INTO outbound_quota_spend (key, caller, window_end, sent)
|
|
164
|
+
SELECT key, ${caller}, reset_at, 1 FROM taken
|
|
165
|
+
ON CONFLICT (key, caller, window_end) DO UPDATE SET
|
|
166
|
+
sent = outbound_quota_spend.sent + 1, updated_at = clock_timestamp()
|
|
167
|
+
RETURNING 1 AS counted`);
|
|
121
168
|
if (taken.length > 0)
|
|
122
169
|
return;
|
|
123
170
|
const rows = await conn.execute(sql `
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
171
|
+
WITH asked AS (
|
|
172
|
+
UPDATE outbound_quota SET refused_at = clock_timestamp()
|
|
173
|
+
WHERE key = ${key} AND reset_at > now()
|
|
174
|
+
RETURNING key, remaining, reset_at),
|
|
175
|
+
counted AS (
|
|
176
|
+
INSERT INTO outbound_quota_spend (key, caller, window_end, refused)
|
|
177
|
+
SELECT key, ${caller}, reset_at, 1 FROM asked
|
|
178
|
+
ON CONFLICT (key, caller, window_end) DO UPDATE SET
|
|
179
|
+
refused = outbound_quota_spend.refused + 1, updated_at = clock_timestamp()
|
|
180
|
+
RETURNING 1)
|
|
181
|
+
SELECT remaining, reset_at FROM asked`);
|
|
127
182
|
const row = rows[0];
|
|
128
|
-
if (row === undefined)
|
|
129
|
-
|
|
183
|
+
if (row === undefined) {
|
|
184
|
+
// unknown budget: send optimistically, counted until a response places the window
|
|
185
|
+
await conn.execute(sql `
|
|
186
|
+
INSERT INTO outbound_quota_spend (key, caller, window_end, sent)
|
|
187
|
+
VALUES (${key}, ${caller}, 'infinity'::timestamptz, 1)
|
|
188
|
+
ON CONFLICT (key, caller, window_end) DO UPDATE SET
|
|
189
|
+
sent = outbound_quota_spend.sent + 1, updated_at = clock_timestamp()`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
130
192
|
throw new QuotaExhaustedError({
|
|
131
193
|
key, class: cls, reserve,
|
|
132
194
|
remaining: Number(row['remaining']),
|
|
133
|
-
resetAt:
|
|
195
|
+
resetAt: toDate(row['reset_at']),
|
|
134
196
|
});
|
|
135
197
|
}
|
|
198
|
+
async function spend(key) {
|
|
199
|
+
const conn = db();
|
|
200
|
+
const live = await conn.execute(sql `SELECT reset_at FROM outbound_quota WHERE key = ${key} AND reset_at > now()`);
|
|
201
|
+
const liveAt = live[0] === undefined ? null : toDate(live[0]['reset_at']).getTime();
|
|
202
|
+
const rows = await conn.execute(sql `
|
|
203
|
+
SELECT caller, sent, refused,
|
|
204
|
+
CASE WHEN window_end = 'infinity'::timestamptz THEN NULL ELSE window_end END AS window_end
|
|
205
|
+
FROM outbound_quota_spend WHERE key = ${key}`);
|
|
206
|
+
const byWindow = new Map();
|
|
207
|
+
const unplaced = [];
|
|
208
|
+
for (const r of rows) {
|
|
209
|
+
const entry = { caller: String(r['caller']), sent: Number(r['sent']), refused: Number(r['refused']) };
|
|
210
|
+
if (r['window_end'] === null) {
|
|
211
|
+
unplaced.push(entry);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const at = toDate(r['window_end']).getTime();
|
|
215
|
+
const list = byWindow.get(at);
|
|
216
|
+
if (list === undefined)
|
|
217
|
+
byWindow.set(at, [entry]);
|
|
218
|
+
else
|
|
219
|
+
list.push(entry);
|
|
220
|
+
}
|
|
221
|
+
const order = (list) => list.sort((a, b) => (b.sent + b.refused) - (a.sent + a.refused) || (a.caller < b.caller ? -1 : a.caller > b.caller ? 1 : 0));
|
|
222
|
+
const windowOf = (at) => at === null ? null : { resetAt: new Date(at), callers: order(byWindow.get(at) ?? []) };
|
|
223
|
+
const finite = [...byWindow.keys()].sort((a, b) => b - a);
|
|
224
|
+
const currentAt = liveAt;
|
|
225
|
+
const previousAt = finite.find(at => currentAt === null || at < currentAt) ?? null;
|
|
226
|
+
return {
|
|
227
|
+
current: currentAt === null ? null : windowOf(currentAt),
|
|
228
|
+
previous: windowOf(previousAt),
|
|
229
|
+
unplaced: order(unplaced),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
136
232
|
return {
|
|
137
233
|
record,
|
|
138
234
|
state,
|
|
235
|
+
spend,
|
|
139
236
|
async call(target, send, opts = {}) {
|
|
140
|
-
await take(target.key, target.class);
|
|
237
|
+
await take(target.key, target.class, target.caller ?? UNLABELLED);
|
|
141
238
|
let result;
|
|
142
239
|
try {
|
|
143
240
|
const o = opts.retryUnsent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../../src/infra/quota/handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAG/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,WAAW,EAA4B,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../../src/infra/quota/handle.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createQuota } from './handle.js';
|
|
2
|
-
export type { Quota, QuotaConfig, QuotaHeaders, QuotaHeaderInput, QuotaBudget, QuotaState, QuotaCallOptions, } from './handle.js';
|
|
2
|
+
export type { Quota, QuotaConfig, QuotaHeaders, QuotaHeaderInput, QuotaBudget, QuotaState, QuotaCallOptions, QuotaSpend, QuotaWindowSpend, QuotaCallerSpend, } from './handle.js';
|
|
3
3
|
export { QuotaExhaustedError } from './errors.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/quota/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,YAAY,EACV,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/quota/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,YAAY,EACV,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAC7F,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,GAC/C,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/quota/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/quota/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAKzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -23,4 +23,24 @@ export declare const outboundQuota: import("../../data/domain/types.js").Defined
|
|
|
23
23
|
nullable: true;
|
|
24
24
|
}>;
|
|
25
25
|
}, "outbound_quota">;
|
|
26
|
+
export declare const outboundQuotaSpend: import("../../data/domain/types.js").DefinedTable<{
|
|
27
|
+
key: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
|
|
28
|
+
caller: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
|
|
29
|
+
windowEnd: import("../../data/domain/types.js").FieldDescriptor<Date, {}> & import("../../data/domain/field.js").FieldModifiers<Date, {}>;
|
|
30
|
+
sent: import("../../data/domain/types.js").FieldDescriptor<number, {
|
|
31
|
+
hasDefault: true;
|
|
32
|
+
}> & import("../../data/domain/field.js").FieldModifiers<number, {
|
|
33
|
+
hasDefault: true;
|
|
34
|
+
}>;
|
|
35
|
+
refused: import("../../data/domain/types.js").FieldDescriptor<number, {
|
|
36
|
+
hasDefault: true;
|
|
37
|
+
}> & import("../../data/domain/field.js").FieldModifiers<number, {
|
|
38
|
+
hasDefault: true;
|
|
39
|
+
}>;
|
|
40
|
+
updatedAt: import("../../data/domain/types.js").FieldDescriptor<Date, {
|
|
41
|
+
hasDefault: true;
|
|
42
|
+
}> & import("../../data/domain/field.js").FieldModifiers<Date, {
|
|
43
|
+
hasDefault: true;
|
|
44
|
+
}>;
|
|
45
|
+
}, "outbound_quota_spend">;
|
|
26
46
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/infra/quota/schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;IAMxB,uGAAuG;;;;;;oBAEvG,CAAA"}
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/infra/quota/schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;IAMxB,uGAAuG;;;;;;oBAEvG,CAAA;AAIF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;0BAS7B,CAAA"}
|
|
@@ -11,4 +11,16 @@ export const outboundQuota = define('outbound_quota', {
|
|
|
11
11
|
/** When a call was last refused against this row; it counts while it is not older than `updatedAt`. */
|
|
12
12
|
refusedAt: field.timestamp().nullable(),
|
|
13
13
|
});
|
|
14
|
+
// Calls per (key, caller, window): `window_end` is the budget row's reset_at; 'infinity' holds calls made
|
|
15
|
+
// before any window was known, moved into the first window a response records.
|
|
16
|
+
export const outboundQuotaSpend = define('outbound_quota_spend', {
|
|
17
|
+
key: field.text(),
|
|
18
|
+
caller: field.text(),
|
|
19
|
+
windowEnd: field.timestamp(),
|
|
20
|
+
sent: field.integer().default(0),
|
|
21
|
+
refused: field.integer().default(0),
|
|
22
|
+
updatedAt: field.timestamp().default('now'),
|
|
23
|
+
}, {
|
|
24
|
+
primaryKey: ['key', 'caller', 'windowEnd'],
|
|
25
|
+
});
|
|
14
26
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/infra/quota/schema.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,oEAAoE;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAElD,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,EAAE;IACpD,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE;IAC9B,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE;IAC1B,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE;IAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,uGAAuG;IACvG,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/infra/quota/schema.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,oEAAoE;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAElD,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,EAAE;IACpD,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE;IAC9B,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE;IAC1B,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE;IAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,uGAAuG;IACvG,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAA;AAEF,0GAA0G;AAC1G,+EAA+E;AAC/E,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,sBAAsB,EAAE;IAC/D,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE;IACjB,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE;IACpB,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;IAC5B,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAChC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC5C,EAAE;IACD,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC;CAC3C,CAAC,CAAA"}
|
|
@@ -38,6 +38,8 @@ export interface Flow<TAuth = unknown> {
|
|
|
38
38
|
readonly transactions: Map<string, BoundTransaction>;
|
|
39
39
|
/** The trace opened at this entry, or `null` when the entry ran untraced. */
|
|
40
40
|
readonly trace: TraceContext | null;
|
|
41
|
+
/** Aborted when the HTTP client closes the connection before the response is finished; pass it to outbound calls. Unset outside an HTTP request. */
|
|
42
|
+
readonly signal?: AbortSignal;
|
|
41
43
|
}
|
|
42
44
|
/** HTTP request metadata stored in a Flow — method, path, trace ID, session ID. */
|
|
43
45
|
export interface RequestMetadata {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/request/context/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,+GAA+G;AAC/G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,8HAA8H;AAC9H,MAAM,WAAW,IAAI,CAAC,KAAK,GAAG,OAAO;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,sHAAsH;IACtH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0HAA0H;IAC1H,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAA;IAC1C,QAAQ,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IACpD,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/request/context/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,+GAA+G;AAC/G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,8HAA8H;AAC9H,MAAM,WAAW,IAAI,CAAC,KAAK,GAAG,OAAO;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,sHAAsH;IACtH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0HAA0H;IAC1H,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAA;IAC1C,QAAQ,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IACpD,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;IACnC,oJAAoJ;IACpJ,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC"}
|
|
@@ -17,7 +17,7 @@ export interface ResolveOptions {
|
|
|
17
17
|
* would be a footgun.
|
|
18
18
|
*/
|
|
19
19
|
readonly checkpoint?: CheckpointFn;
|
|
20
|
-
/** A job's own signal, injected as the `signal` dep. */
|
|
20
|
+
/** A job's own signal, or an HTTP request's abort signal, injected as the `signal` dep. */
|
|
21
21
|
readonly signal?: AbortSignal;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/request/di/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACnG,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAQjE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,eAAe,CAAA;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAA;IAClC,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/request/di/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PipeworkError } from '../../core/errors/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The reason a request's `signal` aborts with when the client closes the
|
|
4
|
+
* connection before the response is finished. Not an internal error: nothing
|
|
5
|
+
* is sent (the socket is gone) and `onInternalError` is not called.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RequestAbortedError extends PipeworkError {
|
|
8
|
+
readonly code: "REQUEST_ABORTED";
|
|
9
|
+
readonly httpStatus: 499;
|
|
10
|
+
constructor(method: string, route: string);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=aborted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aborted.d.ts","sourceRoot":"","sources":["../../../src/request/http/aborted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,aAAa;IACpD,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAS;IAC1C,QAAQ,CAAC,UAAU,EAAG,GAAG,CAAS;gBAEtB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAG1C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PipeworkError } from '../../core/errors/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The reason a request's `signal` aborts with when the client closes the
|
|
4
|
+
* connection before the response is finished. Not an internal error: nothing
|
|
5
|
+
* is sent (the socket is gone) and `onInternalError` is not called.
|
|
6
|
+
*/
|
|
7
|
+
export class RequestAbortedError extends PipeworkError {
|
|
8
|
+
code = 'REQUEST_ABORTED';
|
|
9
|
+
httpStatus = 499;
|
|
10
|
+
constructor(method, route) {
|
|
11
|
+
super(`[pipework] The client closed the connection before the response was sent (${method} ${route}).`, { method, route });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=aborted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aborted.js","sourceRoot":"","sources":["../../../src/request/http/aborted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,aAAa;IAC3C,IAAI,GAAG,iBAA0B,CAAA;IACjC,UAAU,GAAG,GAAY,CAAA;IAElC,YAAY,MAAc,EAAE,KAAa;QACvC,KAAK,CAAC,6EAA6E,MAAM,IAAI,KAAK,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5H,CAAC;CACF"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { file } from './file.js';
|
|
2
2
|
import { detectMediaType, resolveMediaType } from './sniff.js';
|
|
3
|
-
/** HTTP namespace: `http.file()` builds a file response; `http.detectMediaType()` names bytes the way uploads do; the server itself is managed by manifold.start().
|
|
3
|
+
/** HTTP namespace: `http.file()` builds a file response; `http.detectMediaType()` names bytes the way uploads do; the server itself is managed by manifold.start().
|
|
4
|
+
*
|
|
5
|
+
* A client that disconnects before its response is finished aborts the request: the in-flight statement is cancelled, the transaction rolled back and the connection released. The context's `signal` (`flow.require().signal`) aborts with a `RequestAbortedError` (499, `REQUEST_ABORTED`); pass it to the handler's own outbound calls (`fetch(url, { signal })`, a `quota.call` send). The aborted request is not an internal error: `onInternalError` is not called and nothing is sent. */
|
|
4
6
|
export declare const http: {
|
|
5
7
|
readonly file: typeof file;
|
|
6
8
|
readonly detectMediaType: typeof detectMediaType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAe,MAAM,YAAY,CAAA;AAE3E
|
|
1
|
+
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAe,MAAM,YAAY,CAAA;AAE3E;;ieAEie;AACje,eAAO,MAAM,IAAI;;;;;CAAoE,CAAA"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { file } from './file.js';
|
|
2
2
|
import { detectMediaType, resolveMediaType, SNIFF_BYTES } from './sniff.js';
|
|
3
|
-
/** HTTP namespace: `http.file()` builds a file response; `http.detectMediaType()` names bytes the way uploads do; the server itself is managed by manifold.start().
|
|
3
|
+
/** HTTP namespace: `http.file()` builds a file response; `http.detectMediaType()` names bytes the way uploads do; the server itself is managed by manifold.start().
|
|
4
|
+
*
|
|
5
|
+
* A client that disconnects before its response is finished aborts the request: the in-flight statement is cancelled, the transaction rolled back and the connection released. The context's `signal` (`flow.require().signal`) aborts with a `RequestAbortedError` (499, `REQUEST_ABORTED`); pass it to the handler's own outbound calls (`fetch(url, { signal })`, a `quota.call` send). The aborted request is not an internal error: `onInternalError` is not called and nothing is sent. */
|
|
4
6
|
export const http = { file, detectMediaType, resolveMediaType, SNIFF_BYTES };
|
|
5
7
|
//# sourceMappingURL=namespace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE3E
|
|
1
|
+
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE3E;;ieAEie;AACje,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAW,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/request/http/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EAAc,YAAY,EAAsC,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/request/http/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EAAc,YAAY,EAAsC,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA6B7H,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAGnD,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACjG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAA;IAC7E,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACpD,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB;;;OAGG;IACH,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC;AAED,wHAAwH;AACxH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAE,YAAiB,GAAG,cAAc,CAkqB1F"}
|
|
@@ -10,6 +10,7 @@ import { createRequestContext } from '../context/index.js';
|
|
|
10
10
|
import { executeInTransaction } from '../context/run-in-transaction.js';
|
|
11
11
|
import { resolveAndExecute, parseRouteInputs, resolveHandlerDatabases, buildCheckpoint } from '../di/resolve.js';
|
|
12
12
|
import { createRateLimitClasses } from './rate-limit-classes.js';
|
|
13
|
+
import { RequestAbortedError } from './aborted.js';
|
|
13
14
|
import { buildContext } from './middleware.js';
|
|
14
15
|
import { closeTrace } from '../../trace/entry/close-trace.js';
|
|
15
16
|
import { recordErrorStep } from './error-step.js';
|
|
@@ -382,13 +383,21 @@ export function createServer(instance, config = {}) {
|
|
|
382
383
|
let ctx;
|
|
383
384
|
let uploaded;
|
|
384
385
|
const track = newUploadTrack();
|
|
386
|
+
// Aborted when the client closes the connection before the response is finished.
|
|
387
|
+
const abort = new AbortController();
|
|
388
|
+
const onClose = () => {
|
|
389
|
+
if (!reply.raw.writableEnded && !abort.signal.aborted)
|
|
390
|
+
abort.abort(new RequestAbortedError(method, path));
|
|
391
|
+
};
|
|
392
|
+
reply.raw.once('close', onClose);
|
|
393
|
+
reply.raw.once('finish', () => { reply.raw.removeListener('close', onClose); });
|
|
385
394
|
// Ends the upload stream and closes the connection if the body was left unread.
|
|
386
395
|
const finishUpload = (failed) => {
|
|
387
396
|
if (settleUpload(uploaded?.stream) || (failed && upload !== null))
|
|
388
397
|
void reply.header('connection', 'close');
|
|
389
398
|
};
|
|
390
399
|
try {
|
|
391
|
-
ctx = await buildContext(req, contextOptions, handler.meta.authRequirement);
|
|
400
|
+
ctx = { ...(await buildContext(req, contextOptions, handler.meta.authRequirement)), signal: abort.signal };
|
|
392
401
|
req.pipeworkTraceId = ctx.trace?.id ?? null;
|
|
393
402
|
requestFlows.set(req, ctx);
|
|
394
403
|
if (rateLimitClasses !== null)
|
|
@@ -418,6 +427,7 @@ export function createServer(instance, config = {}) {
|
|
|
418
427
|
},
|
|
419
428
|
...(rawBodyBuffer !== undefined ? { rawBody: rawBodyBuffer } : {}),
|
|
420
429
|
...(uploaded !== undefined ? { upload: uploaded } : {}),
|
|
430
|
+
signal: abort.signal,
|
|
421
431
|
};
|
|
422
432
|
// Fastify hands a missing body over as null; the input sees undefined.
|
|
423
433
|
// A body that is literally JSON null names a content-type and stays null.
|
|
@@ -430,11 +440,11 @@ export function createServer(instance, config = {}) {
|
|
|
430
440
|
// be written and committed, a network call made, and the outcome
|
|
431
441
|
// written again, all under the request's own tenant with RLS
|
|
432
442
|
// enforced. What it costs is stated on `.transaction('manual')`.
|
|
433
|
-
const checkpoint = buildCheckpoint(handler, ctx, resolveHandlerDatabases(handler, instance), tenantConfig, txTimeoutMs);
|
|
443
|
+
const checkpoint = buildCheckpoint(handler, ctx, resolveHandlerDatabases(handler, instance), tenantConfig, txTimeoutMs, abort.signal);
|
|
434
444
|
result = await runInContext(ctx, () => resolveAndExecute(handler, body, req.query, req.params, { ...httpOptions, checkpoint }));
|
|
435
445
|
}
|
|
436
446
|
else if (txDatabases !== null && txDatabases.length > 0) {
|
|
437
|
-
result = await executeInTransaction(ctx, txDatabases, tenantConfig, txTimeoutMs, () => resolveAndExecute(handler, body, req.query, req.params, httpOptions));
|
|
447
|
+
result = await executeInTransaction(ctx, txDatabases, tenantConfig, txTimeoutMs, () => resolveAndExecute(handler, body, req.query, req.params, httpOptions), abort.signal);
|
|
438
448
|
}
|
|
439
449
|
else {
|
|
440
450
|
result = await runInContext(ctx, () => resolveAndExecute(handler, body, req.query, req.params, httpOptions));
|
|
@@ -445,6 +455,16 @@ export function createServer(instance, config = {}) {
|
|
|
445
455
|
}
|
|
446
456
|
catch (error) {
|
|
447
457
|
finishUpload(true);
|
|
458
|
+
// The client is gone, whatever the error was (a cancelled statement, the abort itself):
|
|
459
|
+
// not a fault, nothing to send.
|
|
460
|
+
if (abort.signal.aborted) {
|
|
461
|
+
const reason = abort.signal.reason;
|
|
462
|
+
recordErrorStep(ctx, { method, route: path, status: 499, code: 'REQUEST_ABORTED', error: reason });
|
|
463
|
+
if (ctx !== undefined)
|
|
464
|
+
void closeTrace(instance, ctx.trace, 'error');
|
|
465
|
+
req.log.info({ route: `${method} ${path}`, requestId: String(req.id) }, 'request aborted by client');
|
|
466
|
+
return reply;
|
|
467
|
+
}
|
|
448
468
|
// Records the error step, runs `refused` (the route's onRefused hook) while the
|
|
449
469
|
// trace is still open, then closes the trace.
|
|
450
470
|
const failWith = async (status, code, refused) => {
|