openvibe-sdk/usage

Generated at from openvibe-contracts v0.107.0 and openvibe-sdk v0.35.0.

From types/usage.d.ts (server only). Declarations are shown verbatim.

type UsageSample

platform.usage-sample@1: one metered usage reading with retry-safe attribution. Validated by openvibe-contracts (required lazily by validateUsageSample, the first time it is called).

export type UsageSample = { id: string; idempotency_key: string; service: string; project?: string; subject?: string; resource?: string;
    provider?: string; node?: string; cell?: string; region?: string; operation: string; quantity: number; unit: string; at: string;
    /** Money fields are the caller's (rating is Billing's); usageSample never invents them. */
    cost_estimate?: number; free_allowance_used?: number; vibes_charged?: number; route_epoch?: number; trace_id?: string; source: string };

type ValidationError

export type ValidationError = { path: string; message: string };

function usageSample

`at` defaults to now (ISO) and `source` to 'openvibe-sdk/usage'; null/undefined fields are stripped.

export declare function usageSample(fields: Partial<UsageSample>): UsageSample;

function usageKey

The stable idempotency key of a reading: the service, then each part, joined with ':'; a non-empty service is required.

export declare function usageKey(service: string, ...parts: (string | number)[]): string;

function validateUsageSample

{ ok, errors } against platform.usage-sample@1; a missing openvibe-contracts never claims validity (ok: false, errors: []).

export declare function validateUsageSample(record: unknown): { ok: boolean; errors: ValidationError[] };

type UsageTokenClient

Anything with getToken({ audience }) — openvibe-sdk/auth's createServiceTokenClient. `invalidate` drops the cached token (called for a Billing 401 so the retry mints a fresh one).

export type UsageTokenClient = { getToken(ctx?: { audience?: string; scope?: string | string[] }): Promise<string>;
    invalidate?(ctx?: { audience?: string }): void };

type UsageReporterOptions

export type UsageReporterOptions = {
    /** An openvibe-sdk/db handle (server); the outbox table lives in the same database. */
    db: unknown;
    /** The Contracts service id (services/<id>.json), e.g. 'run', 'tools', 'ai'. */
    service: string;
    /** The sample's `source`, e.g. 'openvibe-node.worker', 'ai.runs'. */
    source: string;
    /** The outbox table (default 'usage_outbox'); services create it in a migration. */
    table?: string;
    /** OpenVibe.Billing's base URL; without it (or a tokenClient) only queueing happens. */
    billingUrl?: string;
    tokenClient?: UsageTokenClient | null;
    /** The token audience (default 'openvibe.billing'). */
    audience?: string;
    /** When true, `record()` refuses a reading whose `project` is not a `prj_…` id (default false: the
     *  reporter bills whatever reading it is given, so callers must never record first-party or sandbox traffic). */
    requireProject?: boolean;
    fetchImpl?: import('./core').FetchLike;
    /** The Billing POST timeout (default 5000). */
    timeoutMs?: number;
    /** The relay tick (default 2000). */
    intervalMs?: number;
    /** Readings per publish (default 1: one reading per request). */
    batchSize?: number;
    now?: () => number;
    log?: { warn?(...args: unknown[]): void };
};

interface UsageReporter

One service's usage reporter: build/validate readings, queue them idempotency-keyed, relay to Billing.

export interface UsageReporter {
    readonly enabled: boolean;
    readonly service: string;
    readonly source: string;
    readonly table: string;
    /** The service's stable key, e.g. key('job', id, n) -> 'run:job:<id>:<n>'. */
    key(...parts: (string | number)[]): string;
    /** A reading for this service and source; `id` defaults to `idempotency_key`. */
    sample(fields: Partial<UsageSample>): UsageSample;
    /** INSIDE the caller's transaction (db.tx's handle): queue the reading under its idempotency_key. The
     *  reporter bills whatever reading it is given — never record first-party or sandbox traffic; with
     *  `requireProject: true` a reading without a `prj_…` project is refused. */
    record(t: unknown, reading: UsageSample | Record<string, unknown>): Promise<boolean>;
    /** Create the outbox table where the handle may (tests, PGlite); services put it in a migration instead. */
    ensureSchema(): Promise<unknown>;
    start(): void;
    stop(): Promise<unknown>;
    kick(): void;
    flush(): Promise<{ sent: number; failed: number; rejected: number }>;
    prune(olderThanMs?: number): Promise<unknown>;
    pending(): Promise<number>;
    rejected(): Promise<number>;
}

function createUsageReporter

The shared step-7 reporter: readings queue idempotency-keyed in `table` inside the caller's transaction and relay to Billing's billing.usage.record with createPgOutbox. Never dropped (transient failures — 5xx, network errors and 4xx about credentials, grant, address or load: 401/403/404/408/425/429 — retry with backoff across restarts; a 401 also drops the cached token); only Billing refusing the reading itself (any other 4xx: 400, 402, 409, 410, 413, 415, 422 …) marks a row rejected, kept with its error and never sent again.

export declare function createUsageReporter(options: UsageReporterOptions): UsageReporter;