Skip to main content

UsageRollupStore

@forge/monorepo


@forge/monorepo / backend/src / UsageRollupStore

Interface: UsageRollupStore

Defined in: backend/src/persistence/index.ts:513

Aggregated consumption, derived from the ledger — never a second source of truth.

Rollups are recomputed, not accumulated. rebuild reads a bucket's raw events and replaces the row. That makes idempotency structural rather than bookkept: re-running a bucket produces the same numbers, and two workers racing the same bucket write the same value. The alternative — accumulating deltas with a set of applied event keys — needs that set to be durable, unbounded and exactly right forever, and any gap in it is silent double counting or silent loss.

The cost is one scan per bucket in the job, not in the query. That is what a rollup job is for; AC-1 is about the read path.

Methods

get()

get(input): Promise<UsageRollup | null>

Defined in: backend/src/persistence/index.ts:541

One bucket, or null when nothing has been recorded in it.

principalId omitted reads the tenant row; supplied reads that person's — #175. They are the same measurement at two grains, stored as a nullable dimension on one table rather than in two tables, because two rebuild paths is two chances to disagree and the day they disagree is the day an invoice is wrong.

A per-principal quota needs this: without it, a limit on one person is checked against everybody's usage.

Parameters

input

TenantScope & object

Returns

Promise<UsageRollup | null>


list()

list(input): Promise<Page<UsageRollup>>

Defined in: backend/src/persistence/index.ts:551

A range of buckets, for a chart — the read path AC-1 is about.

from inclusive, to exclusive, so adjacent ranges tile without overlapping and a caller cannot double-count a boundary bucket by asking for two ranges.

Parameters

input

TenantScope & PageRequest & object

Returns

Promise<Page<UsageRollup>>


listStaleBuckets()

listStaleBuckets(input): Promise<Page<{ bucketStart: string; period: "hour" | "day" | "week" | "month"; principalId?: PrincipalId; }>>

Defined in: backend/src/persistence/index.ts:576

Buckets that have never been computed, or were computed before the newest event in them.

The rollup job's work list, derived from the ledger so an interrupted job resumes by asking again — the same shape listStaleSources uses for re-indexing, and for the same reason.

Parameters

input

TenantScope & PageRequest & object

Returns

Promise<Page<{ bucketStart: string; period: "hour" | "day" | "week" | "month"; principalId?: PrincipalId; }>>


rebuild()

rebuild(input): Promise<UsageRollup>

Defined in: backend/src/persistence/index.ts:528

The store stamps computedAt itself, from its own clock.

Not the caller's, and this is not a convenience: staleness is defined by comparing a bucket's computedAt against when its newest event was recorded, and both of those are the store's timestamps. A caller-supplied value is a caller-supplied answer to "is this rollup current" — and a caller whose clock runs slow, or which passes a fixed value, would mark a bucket permanently stale or permanently fresh. Found by a conformance fixture passing a constant and every bucket coming back stale.

Parameters

input

TenantScope & object

Returns

Promise<UsageRollup>


sum()

sum(input): Promise<UsageTotals>

Defined in: backend/src/persistence/index.ts:566

The sum across a range, without returning the buckets.

Separate from list because a quota check needs one number and a chart needs many, and making the quota check page through buckets would put the read path's cost on the admission path.

Parameters

input

TenantScope & object

Returns

Promise<UsageTotals>