Files
recap-relay/server/routes/envelope.js
T
2026-05-11 20:03:27 -05:00

59 lines
1.8 KiB
JavaScript

// Standard response envelope. Every /relay/* response (success and
// error both) goes through this so Recap clients can keep their
// credit-balance display accurate regardless of what happened.
//
// Shape: { result, credits_remaining, tier, credit_charged }
import { getOrCreateRow, computeRemaining } from "../credits.js";
import { getTierQuotas } from "../config.js";
// Build the envelope around a result object.
export async function envelope({
result = null,
installId,
tier,
creditCharged = 0,
}) {
const quota = await getTierQuotas();
const row = await getOrCreateRow(installId);
// tier_snapshot on the row was just updated by commitCredit; if no
// credit was committed (free reuse via job_id) it still reflects
// the last-known tier for this install, which is fine.
const balance = computeRemaining(row, quota);
return {
result,
credits_remaining: balance.remaining, // null = unlimited (Max)
tier,
credit_charged: creditCharged,
};
}
// Same shape but for error responses. The error reason goes in `error`
// alongside `result: null`. Clients should still update their balance
// display from `credits_remaining` so failed calls (which were
// refunded) reflect the unchanged balance.
export async function errorEnvelope({
error,
installId,
tier = "core",
statusHint = 500,
}) {
let creditsRemaining = null;
try {
const quota = await getTierQuotas();
const row = await getOrCreateRow(installId || "unknown");
const balance = computeRemaining(row, quota);
creditsRemaining = balance.remaining;
} catch {}
return {
statusHint,
body: {
result: null,
error: typeof error === "string" ? error : error?.message || "unknown_error",
credits_remaining: creditsRemaining,
tier,
credit_charged: 0,
},
};
}