codexmobile-relay / server /relay-runtime-pending.js
Codex
deploy: CodexMobile Relay
90f0300
Raw
History Blame Contribute Delete
2.96 kB
export function createPendingRequestStore({
createRequestId,
metrics,
onChanged,
pendingRequestsMax,
browserPendingRequestsMax
}) {
const requests = new Map();
function countForClient(clientKey) {
if (!clientKey) {
return 0;
}
let count = 0;
for (const pending of requests.values()) {
if (pending.clientKey === clientKey) {
count += 1;
}
}
return count;
}
function pendingLimitError(error) {
metrics.pendingLimitRejectedTotal += 1;
metrics.rateLimitedTotal += 1;
return Object.assign(new Error(error), { status: 429 });
}
function assertCapacity(clientKey = '') {
if (requests.size >= pendingRequestsMax) {
throw pendingLimitError('relay_pending_limit_exceeded');
}
if (clientKey && countForClient(clientKey) >= browserPendingRequestsMax) {
throw pendingLimitError('relay_client_pending_limit_exceeded');
}
}
function deleteRequest(requestId) {
const deleted = requests.delete(requestId);
if (deleted) {
onChanged?.();
}
return deleted;
}
const create = ({ envelope, timeoutMs, epoch, clientKey = '', streamHandlers = null }) => {
const requestId = envelope.requestId || createRequestId();
const pending = {
resolve: () => {},
reject: () => {},
timer: null,
epoch,
clientKey,
streamHandlers,
streamSequence: 0,
streamBytes: 0,
streamStarted: false
};
const response = new Promise((resolve, reject) => {
pending.resolve = resolve;
pending.reject = reject;
});
pending.timer = setTimeout(() => {
deleteRequest(requestId);
metrics.relayRequestsTimedOut += 1;
pending.reject(Object.assign(new Error('relay_request_timeout'), { status: 502 }));
}, timeoutMs);
requests.set(requestId, pending);
onChanged?.();
return { requestId, epoch, response };
};
function failForEpoch(epoch, status, error) {
for (const [requestId, pending] of requests.entries()) {
if (pending.epoch !== epoch) {
continue;
}
clearTimeout(pending.timer);
deleteRequest(requestId);
pending.reject(Object.assign(new Error(error), { status }));
}
}
function fail(requestId, status, error) {
const pending = requests.get(requestId);
if (!pending) {
return;
}
clearTimeout(pending.timer);
deleteRequest(requestId);
pending.reject(Object.assign(new Error(error), { status }));
}
function settle(requestId, callback) {
const pending = requests.get(requestId);
if (!pending) {
return null;
}
clearTimeout(pending.timer);
deleteRequest(requestId);
callback(pending);
return pending;
}
return {
get size() {
return requests.size;
},
assertCapacity,
create,
delete: deleteRequest,
fail,
failForEpoch,
get: (requestId) => requests.get(requestId),
settle
};
}