jevstrudel.git / worker / src / budget.ts
1// The per-user daily Jev budget: one JevBudget Durable Object per signed-in
2// user (`BUDGET.idFromName(userId)`), counting that user's relay calls per
3// UTC day in its own SQLite storage. The counting is budget-counter.ts; the
4// limit is wrangler.json's JEV_DAILY_PER_USER, read here from the object's
5// own env so there is one source for it.
6//
7// A Durable Object, not D1: the count is live coordination (every tab and
8// device of one user racing to spend the same allowance), and an object
9// runs one call at a time, so the check and the charge cannot interleave.
10// It is not a record: yesterday's count is deleted as today's begins.
11import { DurableObject } from 'cloudflare:workers';
12import { balance, createSchema, dailyLimit, spend, type Balance, type Spend } from './budget-counter';
13
14type BudgetEnv = { JEV_DAILY_PER_USER?: unknown };
15
16export class JevBudget extends DurableObject<BudgetEnv> {
17  constructor(ctx: DurableObjectState, env: BudgetEnv) {
18    super(ctx, env);
19    createSchema(ctx.storage.sql);
20  }
21
22  // Charges `calls` relay calls (one, unless said) to today, unless they
23  // do not all fit in what is left of today's budget.
24  spend(calls = 1): Spend {
25    return spend(this.ctx.storage.sql, dailyLimit(this.env.JEV_DAILY_PER_USER), Date.now(), calls);
26  }
27
28  // Today's count, without charging.
29  balance(): Balance {
30    return balance(this.ctx.storage.sql, dailyLimit(this.env.JEV_DAILY_PER_USER), Date.now());
31  }
32}