Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
57 linesCodeBlameRaw
1/**
2 * Each project's store as a Durable Object: the logic is ./store.ts's
3 * `Store`, whose methods are copied onto the object's (and so are
4 * callable over RPC; ./index.ts types the stub as a `Store`). Each
5 * workspace's meter likewise: ./meter.ts's `WorkspaceMeter`.
6 */
7import { DurableObject } from "cloudflare:workers";
8
9import { WorkspaceMeter } from "./meter.ts";
10import { TELEMETRY_MICROS_PER_GB, freeMonthlyBytes, telemetryCost, telemetryDetail } from "./quota.ts";
11import { Store } from "./store.ts";
12
13export class TelemetryStore extends DurableObject {
14 constructor(ctx: DurableObjectState, env: unknown) {
15 super(ctx, env as never);
16 ctx.blockConcurrencyWhile(async () => Store.prototype.open.call(this, ctx));
17 }
18}
19
20for (const name of Object.getOwnPropertyNames(Store.prototype)) {
21 if (name === "constructor") continue;
22 Object.defineProperty(TelemetryStore.prototype, name, Object.getOwnPropertyDescriptor(Store.prototype, name)!);
23}
24
25type MeterEnv = {
26 BILLING?: { fetch: typeof fetch };
27 FREE_TELEMETRY_BYTES_PER_MONTH?: string;
28};
29
30export class TelemetryMeter extends DurableObject<MeterEnv> {
31 constructor(ctx: DurableObjectState, env: MeterEnv) {
32 super(ctx, env);
33 ctx.blockConcurrencyWhile(async () => WorkspaceMeter.prototype.open.call(this, ctx));
34 }
35
36 /** Reports the month's total to billing, at cost past the free amount. */
37 async alarm(): Promise<void> {
38 const billing = this.env.BILLING;
39 if (!billing) return;
40 const free = freeMonthlyBytes(this.env.FREE_TELEMETRY_BYTES_PER_MONTH);
41 await (this as unknown as WorkspaceMeter).note(async (workspace, month, bytes) => {
42 const { billingClient } = await import("@g1t/contracts");
43 const client = billingClient(billing as never);
44 const book = await client.prices().catch(() => null);
45 const perGb = book?.prices.find((p) => p.meter === "telemetry_gb")?.costMicros ?? TELEMETRY_MICROS_PER_GB;
46 // Any answer is billing's to keep (a month it has charged already is
47 // refused for good); only a failure to reach it is tried again.
48 await client.notePending(workspace, "telemetry", telemetryCost(bytes, free, perGb), telemetryDetail(bytes, free), month);
49 return true;
50 });
51 }
52}
53
54for (const name of Object.getOwnPropertyNames(WorkspaceMeter.prototype)) {
55 if (name === "constructor") continue;
56 Object.defineProperty(TelemetryMeter.prototype, name, Object.getOwnPropertyDescriptor(WorkspaceMeter.prototype, name)!);
57}