Skip to main content

Class: CreditManager

Defined in: javascript/src/manager.ts:222

Orchestrates credit operations.

The deduction path is a single atomic, idempotency-keyed store call (deductWithAllowance) that consumes free allowance, enforces spend caps, applies the balance floor and debits the net amount in one transaction (contract §2). The manager is a thin layer that calculates the cost, maps the store's typed error codes to exceptions, and emits lifecycle events only after the operation has succeeded (contract §6).

Optionally accepts a CreditEventEmitter to emit lifecycle events (deducted, deduct_failed, added, refunded, refund_failed, expired, cap_reached, cap_warning, low_balance).

Constructors

Constructor

new CreditManager(store, engine?, emitter?, options?): CreditManager

Defined in: javascript/src/manager.ts:246

Parameters

store

CreditStore

engine?

PricingEngine | null

emitter?

CreditEventEmitter | null

options?

CreditManagerOptions | null

Returns

CreditManager

Accessors

pricingEngine

Get Signature

get pricingEngine(): PricingEngine | null

Defined in: javascript/src/manager.ts:350

The current PricingEngine, or null if not loaded.

Returns

PricingEngine | null

Methods

addCredits()

addCredits(userId, amount, options?): Promise<AddCreditsResult>

Defined in: javascript/src/manager.ts:441

Add credits to a user's account.

Parameters

userId

string

amount

number | Decimal

options?
expiresAt?

Date | null

idempotencyKey?

string | null

Replay-safe idempotency key (parity with deduct/settle/refund).

metadata?

CreditMetadata | null

tier?

string | null

Target credit tier (credit tiers); omitted resolves to the config's default tier.

type?

string

Returns

Promise<AddCreditsResult>


aggregateStats()

aggregateStats(start, end): Promise<AggregateStats>

Defined in: javascript/src/manager.ts:1473

Aggregate statistics across all users in a time window.

Parameters

start

Date

end

Date

Returns

Promise<AggregateStats>


canAfford()

canAfford(userId, metricsOrAmount, options?): Promise<CanAffordResult>

Defined in: javascript/src/manager.ts:971

Advisory affordability check — UI only, non-locking, may be stale (D4/H3).

Never use this as an admission gate; only reserve is authoritative.

Parameters

userId

string

metricsOrAmount

MetricsOrAmount

options?

CanAffordOptions

Returns

Promise<CanAffordResult>


checkAllowance()

checkAllowance(userId): Promise<AllowanceResult>

Defined in: javascript/src/manager.ts:1025

Get remaining free allowance for the current billing period.

Convenience wrapper that routes through the manager so callers never need to reach past it into the raw store. Returns a zero-allowance result for planless users (no exception).

For rolling_30d/anniversary plans (WS9), resolves and threads periodStart so the reported window matches the one actually used by deduct/settle/reserve — a calendar_month plan (the default) takes the fast path unchanged.

Parameters

userId

string

Returns

Promise<AllowanceResult>


checkFeature()

checkFeature(userId, feature): Promise<CheckFeatureResult>

Defined in: javascript/src/manager.ts:381

Check whether a user's plan has a specific feature entitlement.

Passthrough to the store, which distinguishes presence from truthiness (numeric 0 / "" count as present; only null/undefined/false/absent read as missing — contract §5 / M6).

Parameters

userId

string

feature

string

Returns

Promise<CheckFeatureResult>


checkFeatureLimit()

checkFeatureLimit(userId, feature): Promise<FeatureLimitResult>

Defined in: javascript/src/manager.ts:395

Advisory, non-locking read of a per-feature invocation-count limit (UI only).

Resolves the FeatureLimit (if any) from the user's plan and the calendar-aligned window, then delegates counting to the store. Returns limited: false (zeroed fields, action: null) when no FeatureLimit is configured for feature on the user's plan — never used for admission control; that is exclusively the atomic check-and-increment inside deduct/reserve.

Parameters

userId

string

feature

string

Returns

Promise<FeatureLimitResult>


dailySpend()

dailySpend(start, end): Promise<DailySpendRow[]>

Defined in: javascript/src/manager.ts:1508

Daily spend aggregation in a time window.

Parameters

start

Date

end

Date

Returns

Promise<DailySpendRow[]>


deduct()

deduct(userId, metrics, idempotencyKey?, metadata?, feature?): Promise<DeductionResult>

Defined in: javascript/src/manager.ts:1172

Full deduction flow as one atomic store call (contract §2).

  1. breakdown = engine.calculate(metrics); cost = breakdown.total (exact Decimal, no truncation).
  2. If cost <= 0 short-circuit with a zero-amount result.
  3. Otherwise store.deductWithAllowance consumes allowance, enforces caps, applies the balance floor and debits — idempotency-keyed end-to-end.

On a store error a credits.deduct_failed event is emitted and a typed exception is thrown (insufficient_credits → InsufficientCreditsError, cap_reached → CapReachedError). No success event is emitted on error.

Parameters

userId

string

metrics

UsageMetrics

idempotencyKey?

string | null

metadata?

CreditMetadata | null

feature?

string | null

Named feature to enforce/tag a per-feature invocation-count limit for.

Returns

Promise<DeductionResult>


deductFixed()

deductFixed(userId, jobName, idempotencyKey?, metadata?): Promise<DeductionResult>

Defined in: javascript/src/manager.ts:1424

Shortcut for fixed-cost batch jobs.

L1: an unknown/typo'd jobName is rejected (throws) instead of silently charging 0 credits — engine.getFixedCost(jobName) === null means the job is not configured.

Parameters

userId

string

jobName

string

idempotencyKey?

string | null

metadata?

CreditMetadata | null

Returns

Promise<DeductionResult>


deductTeam()

deductTeam(teamId, userId, metrics, idempotencyKey?, metadata?): Promise<TeamDeductionResult>

Defined in: javascript/src/manager.ts:1363

Deduct from a team's shared balance pool.

Calculates the cost via the pricing engine (exact Decimal, no truncation), then debits the team balance. Threads an optional idempotencyKey through to the store so retried team charges are not double-counted (H12).

Parameters

teamId

string

userId

string

metrics

UsageMetrics

idempotencyKey?

string | null

metadata?

CreditMetadata | null

Returns

Promise<TeamDeductionResult>


getAvailable()

getAvailable(userId): Promise<AvailableResult>

Defined in: javascript/src/manager.ts:1002

Advisory available = balance − Σ active holds read (UI only, D4/H3).

Parameters

userId

string

Returns

Promise<AvailableResult>


getBalance()

getBalance(userId): Promise<BalanceResult>

Defined in: javascript/src/manager.ts:435

Get a user's current credit balance.

Parameters

userId

string

Returns

Promise<BalanceResult>


getCreditTiers()

getCreditTiers(userId): Promise<TierBalancesResult>

Defined in: javascript/src/manager.ts:1008

Get a user's per-tier credit balances (credit tiers). Thin pass-through, no event emission.

Parameters

userId

string

Returns

Promise<TierBalancesResult>


getUserPlan()

getUserPlan(userId): Promise<GetUserPlanResult>

Defined in: javascript/src/manager.ts:355

Fetch a user's current plan (including feature entitlements).

Parameters

userId

string

Returns

Promise<GetUserPlanResult>


grantSubscriptionCycle()

grantSubscriptionCycle(userId, amount, options?): Promise<AddCreditsResult>

Defined in: javascript/src/manager.ts:502

Grant one billing-cycle's worth of credits — idempotent-safe for a payment-provider webhook handler (Stripe, etc. — bursar stays provider-agnostic) to call even on webhook redelivery.

  1. At most one of expiresAt/ttlDays may be given (throws ConfigError otherwise).
  2. When ttlDays is given, expiresAt = now + ttlDays days.
  3. When replacePrior (default true), any remaining balance in tier is expired immediately via a direct store.addCredits negative adjustment — naturally idempotent (a replay finds the tier already at zero and skips the call).
  4. The new cycle is granted via a direct store.addCredits call (bypassing addCredits so only credits.cycle_renewed fires, not a duplicate credits.added), threading idempotencyKey so a redelivered webhook replays the prior grant instead of double-crediting.
  5. When planKey is given, assigns it via setUserPlan.
  6. Emits credits.cycle_renewed and returns the grant result.

Parameters

userId

string

amount

number | Decimal

options?

GrantSubscriptionCycleOptions

Returns

Promise<AddCreditsResult>


listUsageEvents()

listUsageEvents(userId, options?): Promise<PaginatedTransactions>

Defined in: javascript/src/manager.ts:1495

Parameters

userId

string

options?

ListUsageEventsOptions

Returns

Promise<PaginatedTransactions>


listUserTransactions()

listUserTransactions(userId, options?): Promise<PaginatedTransactions>

Defined in: javascript/src/manager.ts:1488

List all user credit transactions with pagination.

Parameters

userId

string

options?

ListTransactionsOptions

Returns

Promise<PaginatedTransactions>


loadPricingFromStore()

loadPricingFromStore(): Promise<void>

Defined in: javascript/src/manager.ts:309

Load the active pricing config from the store.

Returns

Promise<void>


publishPricing()

publishPricing(config, label?): Promise<void>

Defined in: javascript/src/manager.ts:334

Publish new pricing and update the engine in one call.

H10: the store write is now awaited (was a fire-and-forget void), so a persistence failure surfaces to the caller instead of becoming an unhandled promise rejection.

Parameters

config

PricingConfigData

label?

string | null

Returns

Promise<void>


publishPricingFromDict()

publishPricingFromDict(data): Promise<void>

Defined in: javascript/src/manager.ts:302

Load pricing from a PricingConfigData or raw dict and sync it.

Parameters

data

Record<string, unknown> | PricingConfigData

Returns

Promise<void>


refundCredits()

refundCredits(transactionId, amount?, reason?, metadata?): Promise<RefundResult>

Defined in: javascript/src/manager.ts:1328

Refund a previous credit deduction.

H3: the store's error is checked before emitting. A successful refund emits credits.refunded; a failed/duplicate/over-refund emits credits.refund_failed and throws a typed RefundError (no success event is ever emitted for a failed refund).

Parameters

transactionId

string

amount?

number | Decimal

reason?

string

metadata?

CreditMetadata | null

Returns

Promise<RefundResult>


release()

release(userId, leaseId): Promise<ReleaseResult>

Defined in: javascript/src/manager.ts:942

Release a lease without charging (work failed/aborted) — idempotent (H1).

Parameters

userId

string

leaseId

string

Returns

Promise<ReleaseResult>


renew()

renew(userId, leaseId, ttl?): Promise<LeaseResult>

Defined in: javascript/src/manager.ts:954

Extend a lease's TTL for long batch/agentic jobs (B4).

Parameters

userId

string

leaseId

string

ttl?

number | null

Returns

Promise<LeaseResult>


reserve()

reserve(userId, metricsOrAmount, options?): Promise<LeaseResult>

Defined in: javascript/src/manager.ts:755

Atomically acquire a lease — the only admission control (D4).

Resolves the effective policy, enforces requiredFeature, sizes the hold from metricsOrAmount (worst-case in strict, estimate in overdraft — the caller chooses what to pass), and calls the store's atomic createLease. On any business failure throws the coherent typed exception; on success emits credits.reserved and returns the LeaseResult.

Parameters

userId

string

metricsOrAmount

MetricsOrAmount

options?

ReserveOptions

Returns

Promise<LeaseResult>


runBilled()

runBilled<T>(userId, options): Promise<{ deduction: DeductionResult; result: T; }>

Defined in: javascript/src/manager.ts:1056

One-call shortcut wiring reserve → doWork → settle (interface plan §4).

doWork runs the operation and returns { result, actual } where actual is the real usage metrics (or amount) to settle. On any exception from doWork the lease is released and the error re-raised. For long jobs doWork may call renew. A crash between reserve and settle is covered by the lease TTL (and the store's reaper).

Type Parameters

T

T

Parameters

userId

string

options

RunBilledOptions<T>

Returns

Promise<{ deduction: DeductionResult; result: T; }>


settle()

settle(userId, leaseId, metricsOrAmount, options?): Promise<DeductionResult>

Defined in: javascript/src/manager.ts:828

Charge the ACTUAL cost against a lease and finalize it (D5).

De-clamped: bills the full actual cost even if it exceeds the lease hold (overdraft). Never blocks on floor/cap at settle — a cap breach surfaces as a non-blocking credits.cap_warning/credits.cap_reached signal. Emits credits.deducted, then multi-level credits.low_balance and a credits.overdraft signal if the balance went negative.

Parameters

userId

string

leaseId

string

metricsOrAmount

MetricsOrAmount

options?

SettleOptions

Returns

Promise<DeductionResult>


setup()

setup(): Promise<SetupResult>

Defined in: javascript/src/manager.ts:297

Run bundled SQL migrations through the store.

Returns

Promise<SetupResult>


setUserPlan()

setUserPlan(userId, planKey): Promise<void>

Defined in: javascript/src/manager.ts:365

Set a user's subscription plan and emit credits.plan_changed.

The store call is awaited so a persistence failure surfaces to the caller. The event is emitted only after the store write succeeds (contract §6).

Parameters

userId

string

planKey

string

Returns

Promise<void>


spendByModel()

spendByModel(start, end): Promise<SpendByModelRow[]>

Defined in: javascript/src/manager.ts:1483

Aggregate spend by model in a time window.

Parameters

start

Date

end

Date

Returns

Promise<SpendByModelRow[]>


spendByUser()

spendByUser(start, end): Promise<SpendByUserRow[]>

Defined in: javascript/src/manager.ts:1478

Aggregate spend by user in a time window.

Parameters

start

Date

end

Date

Returns

Promise<SpendByUserRow[]>


sweepExpiredCredits()

sweepExpiredCredits(dryRun?): Promise<SweepResult>

Defined in: javascript/src/manager.ts:1466

Sweep expired credits from all users' balances.

When dryRun is true, reports what would be expired without modifying any balances. Unaffected by options.lazyExpiry — this always runs a global sweep; lazy expiry only scopes automatic per-user sweeps.

Parameters

dryRun?

boolean = false

Returns

Promise<SweepResult>


topUsers()

topUsers(limit, start, end): Promise<TopUserRow[]>

Defined in: javascript/src/manager.ts:1503

Top users by spend in a time window with limit.

Parameters

limit

number

start

Date

end

Date

Returns

Promise<TopUserRow[]>