openvibers/openvibe.sdk Commits
openvibe-sdk 0.51.0: commerce tiers (tier on subscription checkouts and credit periods, listTiers, setTier) (#59)
Changes
@@@ -3,6 +3,13 @@All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minorrelease may change an API and says so here.++## 0.51.0 (2026-10-17)++++- `openvibe-sdk/commerce`: subscription tiers priced by OpenVibe.Billing (openvibe-contracts 0.201.0). `createIntent`++ (kind subscription) and `subscribeWithCredit` take an optional `tier`; `listTiers(creator)` reads a creator's tier++ prices and `setTier({ creator, tier, priceCents, status?, key })` sets one (`caps.tier` = `billing.tier.manage`;++ `listTiers` reads with the intent capability when `caps.tier` is not given). Without a tier nothing changes.++## 0.50.1 (2026-10-17)- `openvibe-sdk/db`: a nested `db.tx` (a savepoint) marks its scope done when it ends, like the outermost one. Async@@@ -189,7 +189,7 @@ Until OpenVibe.Tools serves the gateway facade (Tools S6), point the jobs client| `openvibe-sdk/quest` | server | `createQuestClient(client)`: `appQuests.define(slug, {title, why?, steps: [{event_type, count, label}], badge: {name}, url?})`, `appQuests.list()`, `appQuests.retire(slug)` with an app token (audience `openvibe.quest`, `quest.app_quest.manage`); `eventType(projectId, name)` gives the `app.<project_key>.<name>` type to publish at Events. Badges only, never OpenCoins; production events count || `openvibe-sdk/vip` | server | `createVipClient({baseUrl?, tokenClient \| getToken, fetch?, timeoutMs?})`: `evaluate({subject, resource, owner, fallback, mode})`, `checkEntitlement({subject, creator, product, mode})`, `isMember(subject, creator)`; every failure is a denial. `createVipCache({vip, ttlMs, denyTtlMs, unavailableTtlMs})`: `entitlement`, `peekEntitlement`, `evaluate`, `invalidate`, `handleEvent` (`vip.membership.changed` and Billing's entitlement events drop a member's answers at once), `clear`, `bounds` || `openvibe-sdk/usage` | server | `usageSample(fields)` builds a `platform.usage-sample@1` record (required `id`, `idempotency_key`, `service`, `operation`, `quantity`, `unit`; `at` defaults to now ISO, `source` to `openvibe-sdk/usage`; null fields stripped; money fields never invented); `usageKey(service, ...parts)` joins an idempotency key with `:`; `validateUsageSample(record)` → `{ok, errors}` (openvibe-contracts, required lazily). `createUsageReporter({ db, service, source, table, billingUrl, tokenClient, audience, requireProject, ... })` owns one service's readings: `key(...parts)`/`sample(fields)`, and `record(t, reading)` queues the reading idempotency-keyed in the outbox inside the caller's transaction, relayed to Billing's `billing.usage.record` with backoff (a retry is never dropped; only Billing refusing the reading itself — any 4xx but 401/403/404/408/425/429, which stay pending — marks a row rejected; a 401 drops the cached token). The reporter bills whatever reading it is given: never record first-party or sandbox traffic (pass `requireProject: true` to refuse a reading without a `prj_…` project). `openvibe-sdk/govern` builds its `onUsage` record with it |--| `openvibe-sdk/commerce` | server | `createCommerceClient(config, {caps, fetchImpl?, tokenClients?, timeoutMs?})`: the Billing client Tips and VIP share. `config` = `{billing: {url, audience, timeoutMs = 10000}, network: {internalUrl}, oauth: {clientId, clientSecret}}`; `caps` maps `intent`, `transfer`, `subscription`, `entitlement` (and optional `rates`, default `entitlement`) to capability strings, one lazily created `serviceAuth` token client per distinct string (`scope` = the string) unless `tokenClients` supplies one, keyed by logical key or capability. Methods: `createIntent` (`kind` purchase with `bits`, or subscription with `creator`/`autoRenew`), `createTransfer`, `refundTransfer`, `subscribeWithCredit`, `cancelSubscription`, `getSubscription`, `listSubscriptions`, `entitlement`, `rates`, `baseUrl`. `Idempotency-Key` is sent verbatim; a 401 invalidates the token and retries once with the same key; problem+json becomes `CommerceError` (`status`, `code`, `body`, `retryable`). `intentKey(prefix, parts)` joins the parts that are set; `receipt(kind, fields)` → `{kind, at, ...fields}` (null stripped, never posted). `openvibe-contracts` is required lazily |++| `openvibe-sdk/commerce` | server | `createCommerceClient(config, {caps, fetchImpl?, tokenClients?, timeoutMs?})`: the Billing client Tips and VIP share. `config` = `{billing: {url, audience, timeoutMs = 10000}, network: {internalUrl}, oauth: {clientId, clientSecret}}`; `caps` maps `intent`, `transfer`, `subscription`, `entitlement` (and optional `rates`, default `entitlement`) to capability strings, one lazily created `serviceAuth` token client per distinct string (`scope` = the string) unless `tokenClients` supplies one, keyed by logical key or capability. Methods: `createIntent` (`kind` purchase with `bits`, or subscription with `creator`/`tier`/`autoRenew`), `createTransfer`, `refundTransfer`, `subscribeWithCredit` (`tier`), `cancelSubscription`, `getSubscription`, `listSubscriptions`, `entitlement`, `rates`, `listTiers(creator)` and `setTier({creator, tier, priceCents, status?, key})` (a creator's tier prices, kept by Billing; optional `caps.tier` = `billing.tier.manage`, which `setTier` needs), `baseUrl`. `Idempotency-Key` is sent verbatim; a 401 invalidates the token and retries once with the same key; problem+json becomes `CommerceError` (`status`, `code`, `body`, `retryable`). `intentKey(prefix, parts)` joins the parts that are set; `receipt(kind, fields)` → `{kind, at, ...fields}` (null stripped, never posted). `openvibe-contracts` is required lazily || `openvibe-sdk/telemetry` | server | `telemetrySample(fields)` / `validateTelemetrySample(record)` for `platform.telemetry-sample@1`; `createTelemetry({service, instance?, sink, intervalMs = 15000, now?, log?, maxBuffered = 1000})` → `{record(name, value, labels), gauge(name, value, labels), count(name, labels), flush(), stop()}`: buffered samples flushed to `await sink(samples)` on an unref'd interval and on `stop()` (a `gracefulStop` stop step); `record` builds `{service, instance, operation, at, latency_ms, ...labels}` (a non-number value goes to `extra[name]`); a rejecting sink is logged once and its samples kept; past `maxBuffered` the oldest sample is dropped, with a warning at most once a minute; the per-service PostgreSQL sink: `telemetrySchema(table = 'telemetry_samples')` (migration DDL), `createPgTelemetrySink({db, table?, log?})` (one multi-row INSERT per flush, malformed samples dropped with a warning), `pruneTelemetry({db, table?, olderThanDays = 30})`, `startTelemetryRetention({db, table?, olderThanDays = 30, everyMs = 24 h, delayMs = 60 s, log?})` → `{run(), stop()}` (prunes soon after start and then daily on unref'd timers, so a service redeployed more than daily still prunes) || `openvibe-sdk/cost` | server | Build and validate `platform.rate-card@1` and `platform.cost-snapshot@1` records with `rateCard()`, `costSnapshot()`, `validateRateCard()`, and `validateCostSnapshot()`; `estimate()` calculates a charge from a rate card || `openvibe-sdk/runtime` | server | Build and validate `platform.runtime-offer@1` records with `runtimeOffer()` and `validateRuntimeOffer()`; `runtimeClass()`, `RUNTIME_CLASSES`, and `validateRuntimeClass()` handle known runtime classes |@@@ -3,7 +3,7 @@"stage": "alpha","code": true,"wave": "W2",-- "version": "0.50.1",++ "version": "0.51.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30","deployed": false,"deployment": "none: a package released as git tags; services and apps pin a tag",@@@ -1,4 +1,4 @@--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.50.1, MIT). Do not edit.++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.51.0, MIT). Do not edit.// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces// (auth, registry, modules, realtime, media, community, bot, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity./* eslint-disable */@@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);const semver = __load(6);/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.50.1';++const SDK_VERSION = '0.51.0';module.exports = {createClient,@@@ -1,12 +1,12 @@{"name": "openvibe-sdk",-- "version": "0.50.1",++ "version": "0.51.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-sdk",-- "version": "0.50.1",++ "version": "0.51.0","license": "MIT","bin": {"openvibe-download": "bin/openvibe-download.js"@@@ -1,6 +1,6 @@{"name": "openvibe-sdk",-- "version": "0.50.1",++ "version": "0.51.0","description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.","main": "index.js","types": "types/index.d.ts",@@@ -45,7 +45,7 @@ const qs = (o) => {/*** `config`: { billing: { url, audience, timeoutMs = 10000 }, network: { internalUrl }, oauth: { clientId, clientSecret } }.* `caps` maps the logical keys intent / transfer / subscription / entitlement (and optionally rates, default-- * entitlement) to capability strings; a client is created per distinct capability string, so two keys that map to++ * entitlement, and tier: billing.tier.manage, which setTier needs and listTiers prefers over intent) to capability strings; a client is created per distinct capability string, so two keys that map to* one string (or a space-separated scope) share one token. `tokenClients` may be keyed by logical key or by* capability string; any other capability gets a serviceAuth token client with scope = the capability.*/@@@ -96,11 +96,11 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke}return {-- /** { intent, checkout_url }: kind purchase buys `bits` of credit; kind subscription is a checkout for `subject` to `creator`. */-- createIntent: async ({ provider, kind = 'purchase', subject, bits, creator, autoRenew, successUrl, cancelUrl, key, traceparent }) => await call('intent', 'POST', '/api/v1/intents', {++ /** { intent, checkout_url }: kind purchase buys `bits` of credit; kind subscription is a checkout for `subject` to `creator` (at `tier`'s price, 1 by default). */++ createIntent: async ({ provider, kind = 'purchase', subject, bits, creator, tier, autoRenew, successUrl, cancelUrl, key, traceparent }) => await call('intent', 'POST', '/api/v1/intents', {body: {provider, kind, subject: user(subject),-- ...(kind === 'subscription' ? { streamer: user(creator), auto_renew: !!autoRenew } : { bits }),++ ...(kind === 'subscription' ? { streamer: user(creator), auto_renew: !!autoRenew, ...(tier != null ? { tier } : {}) } : { bits }),success_url: successUrl, cancel_url: cancelUrl,},key, traceparent,@@@ -111,9 +111,9 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, tokekey, traceparent,}),refundTransfer: async ({ txnId, amount, reason, key }) => await call('transfer', 'POST', `/api/v1/transfers/${encodeURIComponent(txnId)}/refund`, { body: { amount, reason }, key }),-- /** { subscription, entitlement, transaction }: one period paid from the member's credit. */-- subscribeWithCredit: async ({ subscriber, creator, autoRenew, key, traceparent }) => await call('subscription', 'POST', '/api/v1/subscriptions', {-- body: { subscriber: user(subscriber), streamer: user(creator), source: 'credit', auto_renew: autoRenew !== false },++ /** { subscription, entitlement, transaction }: one period paid from the member's credit (at `tier`'s price, 1 by default). */++ subscribeWithCredit: async ({ subscriber, creator, tier, autoRenew, key, traceparent }) => await call('subscription', 'POST', '/api/v1/subscriptions', {++ body: { subscriber: user(subscriber), streamer: user(creator), source: 'credit', auto_renew: autoRenew !== false, ...(tier != null ? { tier } : {}) },key, traceparent,}),cancelSubscription: async ({ id, key, traceparent }) => await call('subscription', 'POST', `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`, { body: {}, key, traceparent }),@@@ -122,6 +122,12 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke/** { active, expires_at, subscription }: the authoritative answer. */entitlement: async (subject, creator) => await call('entitlement', 'GET', `/api/v1/entitlements/${encodeURIComponent(subject)}${qs({ streamer: creator })}`),rates: async () => await call(caps.rates ? 'rates' : 'entitlement', 'GET', '/api/v1/rates'),++ /** { streamer, tiers: [{ tier, price_cents, status }], default_price_cents, min_price_cents, max_price_cents, max_tiers }: Billing prices a creator's tiers. */++ listTiers: async (creator) => await call(caps.tier ? 'tier' : 'intent', 'GET', `/api/v1/tiers/${encodeURIComponent(creator)}`),++ /** { tier }: set the monthly price (USD cents) of one of `creator`'s tiers (billing.tier.manage; status active or archived). */++ setTier: async ({ creator, tier, priceCents, status, key, traceparent }) => await call('tier', 'PUT', `/api/v1/tiers/${encodeURIComponent(creator)}/${encodeURIComponent(tier)}`, {++ body: { price_cents: priceCents, ...(status ? { status } : {}) }, key, traceparent,++ }),baseUrl: () => base,};}@@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');const semver = require('./semver');/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.50.1';++const SDK_VERSION = '0.51.0';module.exports = {createClient,@@@ -85,6 +85,30 @@ run([assert.equal(calls[4].init.headers['Idempotency-Key'], undefined);assert.equal(client.baseUrl(), 'http://billing.test');}],++ ['tiers: the tier on a subscription checkout and a credit period; listTiers and setTier use billing.tier.manage', async () => {++ const { client: noTierCap, calls: readCalls } = rig([json(200, { tiers: [] })]);++ await noTierCap.listTiers('u2');++ assert.equal(readCalls[0].init.headers.Authorization, 'Bearer intent', 'listTiers without caps.tier reads with the intent capability');++ const calls = [];++ const fetchImpl = async (url, init) => { calls.push({ url, init }); return json(200, { ok: true }); };++ const mk = (name) => ({ authHeaders: async () => ({ Authorization: `Bearer ${name}` }), invalidate: () => {} });++ const client = createCommerceClient(CONFIG, { caps: { ...CAPS, tier: 'billing.tier.manage' }, fetchImpl, tokenClients: { intent: mk('intent'), subscription: mk('subscription'), tier: mk('tier') } });++ await client.createIntent({ provider: 'powerchat', kind: 'subscription', subject: 'u1', creator: 'u2', tier: 2, key: 'k' });++ await client.subscribeWithCredit({ subscriber: 'a', creator: 'b', tier: 3, key: 's' });++ await client.listTiers('u 2');++ await client.setTier({ creator: 'u2', tier: 2, priceCents: 1500, key: 't' });++ await client.setTier({ creator: 'u2', tier: 2, priceCents: 1500, status: 'archived', key: 't2' });++ assert.equal(JSON.parse(calls[0].init.body).tier, 2);++ assert.equal(JSON.parse(calls[1].init.body).tier, 3);++ assert.deepEqual(calls.slice(2, 5).map((c) => [c.init.method, c.url.replace('http://billing.test', '')]), [['GET', '/api/v1/tiers/u%202'], ['PUT', '/api/v1/tiers/u2/2'], ['PUT', '/api/v1/tiers/u2/2']]);++ assert.deepEqual(calls.slice(2, 5).map((c) => c.init.headers.Authorization), ['Bearer tier', 'Bearer tier', 'Bearer tier']);++ assert.deepEqual(JSON.parse(calls[3].init.body), { price_cents: 1500 });++ assert.deepEqual(JSON.parse(calls[4].init.body), { price_cents: 1500, status: 'archived' });++ assert.equal(calls[3].init.headers['Idempotency-Key'], 't');++ const plain = rig([json(200, {})]);++ await plain.client.subscribeWithCredit({ subscriber: 'a', creator: 'b' });++ assert.equal('tier' in JSON.parse(plain.calls[0].init.body), false, 'no tier unless asked: Billing sells tier 1');++ }],['a 401 invalidates the token and retries once with the same Idempotency-Key', async () => {const { client, calls, invalidated } = rig([json(401, { code: 'unauthorized' }), json(200, { transaction: { id: 't' } })]);const out = await client.createTransfer({ from: 'a', to: 'b', amount: 1, kind: 'tip', key: 'tip:i1' });@@@ -16,7 +16,8 @@ export type CommerceConfig = {network?: { internalUrl: string };oauth?: { clientId: string; clientSecret: string };};--export type CommerceCaps = { intent?: string; transfer?: string; subscription?: string; entitlement?: string; rates?: string };++export type CommerceCaps = { intent?: string; transfer?: string; subscription?: string; entitlement?: string; rates?: string; tier?: string };++export type CommerceTier = { tier: number; price_cents: number; status: 'active' | 'archived'; updated_at?: string };export type CommerceTokenClient = { authHeaders(): Promise<Record<string, string>>; invalidate(): void };export type CommerceOptions = {caps: CommerceCaps;@@@ -27,15 +28,19 @@ export type CommerceOptions = {};export type CommerceCall = { key?: string; traceparent?: string };export type CommerceClient = {-- createIntent(a: { provider: string; kind?: 'purchase' | 'subscription'; subject: string; bits?: number; creator?: string; autoRenew?: boolean; successUrl?: string; cancelUrl?: string } & CommerceCall): Promise<any>;++ createIntent(a: { provider: string; kind?: 'purchase' | 'subscription'; subject: string; bits?: number; creator?: string; tier?: number; autoRenew?: boolean; successUrl?: string; cancelUrl?: string } & CommerceCall): Promise<any>;createTransfer(a: { from: string; to: string; amount: number; kind?: string; target?: unknown; message?: string } & CommerceCall): Promise<any>;refundTransfer(a: { txnId: string; amount?: number; reason?: string; key?: string }): Promise<any>;-- subscribeWithCredit(a: { subscriber: string; creator: string; autoRenew?: boolean } & CommerceCall): Promise<any>;++ subscribeWithCredit(a: { subscriber: string; creator: string; tier?: number; autoRenew?: boolean } & CommerceCall): Promise<any>;cancelSubscription(a: { id: string } & CommerceCall): Promise<any>;getSubscription(id: string): Promise<any>;listSubscriptions(a?: { streamer?: string; subscriber?: string; status?: string }): Promise<any>;entitlement(subject: string, creator?: string): Promise<any>;rates(): Promise<any>;++ /** A creator's tier prices (Billing, contracts 0.201.0); uses caps.tier, else caps.intent. */++ listTiers(creator: string): Promise<{ streamer: { type: 'user'; id: string }; tiers: CommerceTier[]; default_price_cents: number; min_price_cents: number; max_price_cents: number; max_tiers: number }>;++ /** Set one tier's monthly price (billing.tier.manage). */++ setTier(a: { creator: string; tier: number; priceCents: number; status?: 'active' | 'archived' } & CommerceCall): Promise<{ tier: CommerceTier }>;baseUrl(): string;};export declare function createCommerceClient(config: CommerceConfig, options: CommerceOptions): CommerceClient;
@@ -3,6 +3,13 @@
@@ -3,6 +3,13 @@
All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minor
All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minor
release may change an API and says so here.
release may change an API and says so here.
+## 0.51.0 (2026-10-17)
+
+- `openvibe-sdk/commerce`: subscription tiers priced by OpenVibe.Billing (openvibe-contracts 0.201.0). `createIntent`
+ (kind subscription) and `subscribeWithCredit` take an optional `tier`; `listTiers(creator)` reads a creator's tier
+ prices and `setTier({ creator, tier, priceCents, status?, key })` sets one (`caps.tier` = `billing.tier.manage`;
+ `listTiers` reads with the intent capability when `caps.tier` is not given). Without a tier nothing changes.
+
## 0.50.1 (2026-10-17)
## 0.50.1 (2026-10-17)
- `openvibe-sdk/db`: a nested `db.tx` (a savepoint) marks its scope done when it ends, like the outermost one. Async
- `openvibe-sdk/db`: a nested `db.tx` (a savepoint) marks its scope done when it ends, like the outermost one. Async
@@ -189,7 +189,7 @@ Until OpenVibe.Tools serves the gateway facade (Tools S6), point the jobs client
@@ -189,7 +189,7 @@ Until OpenVibe.Tools serves the gateway facade (Tools S6), point the jobs client
| `openvibe-sdk/quest` | server | `createQuestClient(client)`: `appQuests.define(slug, {title, why?, steps: [{event_type, count, label}], badge: {name}, url?})`, `appQuests.list()`, `appQuests.retire(slug)` with an app token (audience `openvibe.quest`, `quest.app_quest.manage`); `eventType(projectId, name)` gives the `app.<project_key>.<name>` type to publish at Events. Badges only, never OpenCoins; production events count |
| `openvibe-sdk/quest` | server | `createQuestClient(client)`: `appQuests.define(slug, {title, why?, steps: [{event_type, count, label}], badge: {name}, url?})`, `appQuests.list()`, `appQuests.retire(slug)` with an app token (audience `openvibe.quest`, `quest.app_quest.manage`); `eventType(projectId, name)` gives the `app.<project_key>.<name>` type to publish at Events. Badges only, never OpenCoins; production events count |
| `openvibe-sdk/vip` | server | `createVipClient({baseUrl?, tokenClient \| getToken, fetch?, timeoutMs?})`: `evaluate({subject, resource, owner, fallback, mode})`, `checkEntitlement({subject, creator, product, mode})`, `isMember(subject, creator)`; every failure is a denial. `createVipCache({vip, ttlMs, denyTtlMs, unavailableTtlMs})`: `entitlement`, `peekEntitlement`, `evaluate`, `invalidate`, `handleEvent` (`vip.membership.changed` and Billing's entitlement events drop a member's answers at once), `clear`, `bounds` |
| `openvibe-sdk/vip` | server | `createVipClient({baseUrl?, tokenClient \| getToken, fetch?, timeoutMs?})`: `evaluate({subject, resource, owner, fallback, mode})`, `checkEntitlement({subject, creator, product, mode})`, `isMember(subject, creator)`; every failure is a denial. `createVipCache({vip, ttlMs, denyTtlMs, unavailableTtlMs})`: `entitlement`, `peekEntitlement`, `evaluate`, `invalidate`, `handleEvent` (`vip.membership.changed` and Billing's entitlement events drop a member's answers at once), `clear`, `bounds` |
| `openvibe-sdk/usage` | server | `usageSample(fields)` builds a `platform.usage-sample@1` record (required `id`, `idempotency_key`, `service`, `operation`, `quantity`, `unit`; `at` defaults to now ISO, `source` to `openvibe-sdk/usage`; null fields stripped; money fields never invented); `usageKey(service, ...parts)` joins an idempotency key with `:`; `validateUsageSample(record)` → `{ok, errors}` (openvibe-contracts, required lazily). `createUsageReporter({ db, service, source, table, billingUrl, tokenClient, audience, requireProject, ... })` owns one service's readings: `key(...parts)`/`sample(fields)`, and `record(t, reading)` queues the reading idempotency-keyed in the outbox inside the caller's transaction, relayed to Billing's `billing.usage.record` with backoff (a retry is never dropped; only Billing refusing the reading itself — any 4xx but 401/403/404/408/425/429, which stay pending — marks a row rejected; a 401 drops the cached token). The reporter bills whatever reading it is given: never record first-party or sandbox traffic (pass `requireProject: true` to refuse a reading without a `prj_…` project). `openvibe-sdk/govern` builds its `onUsage` record with it |
| `openvibe-sdk/usage` | server | `usageSample(fields)` builds a `platform.usage-sample@1` record (required `id`, `idempotency_key`, `service`, `operation`, `quantity`, `unit`; `at` defaults to now ISO, `source` to `openvibe-sdk/usage`; null fields stripped; money fields never invented); `usageKey(service, ...parts)` joins an idempotency key with `:`; `validateUsageSample(record)` → `{ok, errors}` (openvibe-contracts, required lazily). `createUsageReporter({ db, service, source, table, billingUrl, tokenClient, audience, requireProject, ... })` owns one service's readings: `key(...parts)`/`sample(fields)`, and `record(t, reading)` queues the reading idempotency-keyed in the outbox inside the caller's transaction, relayed to Billing's `billing.usage.record` with backoff (a retry is never dropped; only Billing refusing the reading itself — any 4xx but 401/403/404/408/425/429, which stay pending — marks a row rejected; a 401 drops the cached token). The reporter bills whatever reading it is given: never record first-party or sandbox traffic (pass `requireProject: true` to refuse a reading without a `prj_…` project). `openvibe-sdk/govern` builds its `onUsage` record with it |
-| `openvibe-sdk/commerce` | server | `createCommerceClient(config, {caps, fetchImpl?, tokenClients?, timeoutMs?})`: the Billing client Tips and VIP share. `config` = `{billing: {url, audience, timeoutMs = 10000}, network: {internalUrl}, oauth: {clientId, clientSecret}}`; `caps` maps `intent`, `transfer`, `subscription`, `entitlement` (and optional `rates`, default `entitlement`) to capability strings, one lazily created `serviceAuth` token client per distinct string (`scope` = the string) unless `tokenClients` supplies one, keyed by logical key or capability. Methods: `createIntent` (`kind` purchase with `bits`, or subscription with `creator`/`autoRenew`), `createTransfer`, `refundTransfer`, `subscribeWithCredit`, `cancelSubscription`, `getSubscription`, `listSubscriptions`, `entitlement`, `rates`, `baseUrl`. `Idempotency-Key` is sent verbatim; a 401 invalidates the token and retries once with the same key; problem+json becomes `CommerceError` (`status`, `code`, `body`, `retryable`). `intentKey(prefix, parts)` joins the parts that are set; `receipt(kind, fields)` → `{kind, at, ...fields}` (null stripped, never posted). `openvibe-contracts` is required lazily |
+| `openvibe-sdk/commerce` | server | `createCommerceClient(config, {caps, fetchImpl?, tokenClients?, timeoutMs?})`: the Billing client Tips and VIP share. `config` = `{billing: {url, audience, timeoutMs = 10000}, network: {internalUrl}, oauth: {clientId, clientSecret}}`; `caps` maps `intent`, `transfer`, `subscription`, `entitlement` (and optional `rates`, default `entitlement`) to capability strings, one lazily created `serviceAuth` token client per distinct string (`scope` = the string) unless `tokenClients` supplies one, keyed by logical key or capability. Methods: `createIntent` (`kind` purchase with `bits`, or subscription with `creator`/`tier`/`autoRenew`), `createTransfer`, `refundTransfer`, `subscribeWithCredit` (`tier`), `cancelSubscription`, `getSubscription`, `listSubscriptions`, `entitlement`, `rates`, `listTiers(creator)` and `setTier({creator, tier, priceCents, status?, key})` (a creator's tier prices, kept by Billing; optional `caps.tier` = `billing.tier.manage`, which `setTier` needs), `baseUrl`. `Idempotency-Key` is sent verbatim; a 401 invalidates the token and retries once with the same key; problem+json becomes `CommerceError` (`status`, `code`, `body`, `retryable`). `intentKey(prefix, parts)` joins the parts that are set; `receipt(kind, fields)` → `{kind, at, ...fields}` (null stripped, never posted). `openvibe-contracts` is required lazily |
| `openvibe-sdk/telemetry` | server | `telemetrySample(fields)` / `validateTelemetrySample(record)` for `platform.telemetry-sample@1`; `createTelemetry({service, instance?, sink, intervalMs = 15000, now?, log?, maxBuffered = 1000})` → `{record(name, value, labels), gauge(name, value, labels), count(name, labels), flush(), stop()}`: buffered samples flushed to `await sink(samples)` on an unref'd interval and on `stop()` (a `gracefulStop` stop step); `record` builds `{service, instance, operation, at, latency_ms, ...labels}` (a non-number value goes to `extra[name]`); a rejecting sink is logged once and its samples kept; past `maxBuffered` the oldest sample is dropped, with a warning at most once a minute; the per-service PostgreSQL sink: `telemetrySchema(table = 'telemetry_samples')` (migration DDL), `createPgTelemetrySink({db, table?, log?})` (one multi-row INSERT per flush, malformed samples dropped with a warning), `pruneTelemetry({db, table?, olderThanDays = 30})`, `startTelemetryRetention({db, table?, olderThanDays = 30, everyMs = 24 h, delayMs = 60 s, log?})` → `{run(), stop()}` (prunes soon after start and then daily on unref'd timers, so a service redeployed more than daily still prunes) |
| `openvibe-sdk/telemetry` | server | `telemetrySample(fields)` / `validateTelemetrySample(record)` for `platform.telemetry-sample@1`; `createTelemetry({service, instance?, sink, intervalMs = 15000, now?, log?, maxBuffered = 1000})` → `{record(name, value, labels), gauge(name, value, labels), count(name, labels), flush(), stop()}`: buffered samples flushed to `await sink(samples)` on an unref'd interval and on `stop()` (a `gracefulStop` stop step); `record` builds `{service, instance, operation, at, latency_ms, ...labels}` (a non-number value goes to `extra[name]`); a rejecting sink is logged once and its samples kept; past `maxBuffered` the oldest sample is dropped, with a warning at most once a minute; the per-service PostgreSQL sink: `telemetrySchema(table = 'telemetry_samples')` (migration DDL), `createPgTelemetrySink({db, table?, log?})` (one multi-row INSERT per flush, malformed samples dropped with a warning), `pruneTelemetry({db, table?, olderThanDays = 30})`, `startTelemetryRetention({db, table?, olderThanDays = 30, everyMs = 24 h, delayMs = 60 s, log?})` → `{run(), stop()}` (prunes soon after start and then daily on unref'd timers, so a service redeployed more than daily still prunes) |
| `openvibe-sdk/cost` | server | Build and validate `platform.rate-card@1` and `platform.cost-snapshot@1` records with `rateCard()`, `costSnapshot()`, `validateRateCard()`, and `validateCostSnapshot()`; `estimate()` calculates a charge from a rate card |
| `openvibe-sdk/cost` | server | Build and validate `platform.rate-card@1` and `platform.cost-snapshot@1` records with `rateCard()`, `costSnapshot()`, `validateRateCard()`, and `validateCostSnapshot()`; `estimate()` calculates a charge from a rate card |
| `openvibe-sdk/runtime` | server | Build and validate `platform.runtime-offer@1` records with `runtimeOffer()` and `validateRuntimeOffer()`; `runtimeClass()`, `RUNTIME_CLASSES`, and `validateRuntimeClass()` handle known runtime classes |
| `openvibe-sdk/runtime` | server | Build and validate `platform.runtime-offer@1` records with `runtimeOffer()` and `validateRuntimeOffer()`; `runtimeClass()`, `RUNTIME_CLASSES`, and `validateRuntimeClass()` handle known runtime classes |
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
"stage": "alpha",
"stage": "alpha",
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
- "version": "0.50.1",
+ "version": "0.51.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
"deployment": "none: a package released as git tags; services and apps pin a tag",
@@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
-// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.50.1, MIT). Do not edit.
+// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.51.0, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// (auth, registry, modules, realtime, media, community, bot, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
// (auth, registry, modules, realtime, media, community, bot, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
/* eslint-disable */
@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);
@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.50.1';
+const SDK_VERSION = '0.51.0';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.50.1",
+ "version": "0.51.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.50.1",
+ "version": "0.51.0",
"license": "MIT",
"license": "MIT",
"bin": {
"bin": {
"openvibe-download": "bin/openvibe-download.js"
"openvibe-download": "bin/openvibe-download.js"
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.50.1",
+ "version": "0.51.0",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"main": "index.js",
"main": "index.js",
"types": "types/index.d.ts",
"types": "types/index.d.ts",
@@ -45,7 +45,7 @@ const qs = (o) => {
@@ -45,7 +45,7 @@ const qs = (o) => {
/**
/**
* `config`: { billing: { url, audience, timeoutMs = 10000 }, network: { internalUrl }, oauth: { clientId, clientSecret } }.
* `config`: { billing: { url, audience, timeoutMs = 10000 }, network: { internalUrl }, oauth: { clientId, clientSecret } }.
* `caps` maps the logical keys intent / transfer / subscription / entitlement (and optionally rates, default
* `caps` maps the logical keys intent / transfer / subscription / entitlement (and optionally rates, default
- * entitlement) to capability strings; a client is created per distinct capability string, so two keys that map to
+ * entitlement, and tier: billing.tier.manage, which setTier needs and listTiers prefers over intent) to capability strings; a client is created per distinct capability string, so two keys that map to
* one string (or a space-separated scope) share one token. `tokenClients` may be keyed by logical key or by
* one string (or a space-separated scope) share one token. `tokenClients` may be keyed by logical key or by
* capability string; any other capability gets a serviceAuth token client with scope = the capability.
* capability string; any other capability gets a serviceAuth token client with scope = the capability.
*/
*/
@@ -96,11 +96,11 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
@@ -96,11 +96,11 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
}
}
return {
return {
- /** { intent, checkout_url }: kind purchase buys `bits` of credit; kind subscription is a checkout for `subject` to `creator`. */
- createIntent: async ({ provider, kind = 'purchase', subject, bits, creator, autoRenew, successUrl, cancelUrl, key, traceparent }) => await call('intent', 'POST', '/api/v1/intents', {
+ /** { intent, checkout_url }: kind purchase buys `bits` of credit; kind subscription is a checkout for `subject` to `creator` (at `tier`'s price, 1 by default). */
+ createIntent: async ({ provider, kind = 'purchase', subject, bits, creator, tier, autoRenew, successUrl, cancelUrl, key, traceparent }) => await call('intent', 'POST', '/api/v1/intents', {
body: {
body: {
provider, kind, subject: user(subject),
provider, kind, subject: user(subject),
- ...(kind === 'subscription' ? { streamer: user(creator), auto_renew: !!autoRenew } : { bits }),
+ ...(kind === 'subscription' ? { streamer: user(creator), auto_renew: !!autoRenew, ...(tier != null ? { tier } : {}) } : { bits }),
success_url: successUrl, cancel_url: cancelUrl,
success_url: successUrl, cancel_url: cancelUrl,
},
},
key, traceparent,
key, traceparent,
@@ -111,9 +111,9 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
@@ -111,9 +111,9 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
key, traceparent,
key, traceparent,
}),
}),
refundTransfer: async ({ txnId, amount, reason, key }) => await call('transfer', 'POST', `/api/v1/transfers/${encodeURIComponent(txnId)}/refund`, { body: { amount, reason }, key }),
refundTransfer: async ({ txnId, amount, reason, key }) => await call('transfer', 'POST', `/api/v1/transfers/${encodeURIComponent(txnId)}/refund`, { body: { amount, reason }, key }),
- /** { subscription, entitlement, transaction }: one period paid from the member's credit. */
- subscribeWithCredit: async ({ subscriber, creator, autoRenew, key, traceparent }) => await call('subscription', 'POST', '/api/v1/subscriptions', {
- body: { subscriber: user(subscriber), streamer: user(creator), source: 'credit', auto_renew: autoRenew !== false },
+ /** { subscription, entitlement, transaction }: one period paid from the member's credit (at `tier`'s price, 1 by default). */
+ subscribeWithCredit: async ({ subscriber, creator, tier, autoRenew, key, traceparent }) => await call('subscription', 'POST', '/api/v1/subscriptions', {
+ body: { subscriber: user(subscriber), streamer: user(creator), source: 'credit', auto_renew: autoRenew !== false, ...(tier != null ? { tier } : {}) },
key, traceparent,
key, traceparent,
}),
}),
cancelSubscription: async ({ id, key, traceparent }) => await call('subscription', 'POST', `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`, { body: {}, key, traceparent }),
cancelSubscription: async ({ id, key, traceparent }) => await call('subscription', 'POST', `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`, { body: {}, key, traceparent }),
@@ -122,6 +122,12 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
@@ -122,6 +122,12 @@ function createCommerceClient(config, { caps, fetchImpl = globalThis.fetch, toke
/** { active, expires_at, subscription }: the authoritative answer. */
/** { active, expires_at, subscription }: the authoritative answer. */
entitlement: async (subject, creator) => await call('entitlement', 'GET', `/api/v1/entitlements/${encodeURIComponent(subject)}${qs({ streamer: creator })}`),
entitlement: async (subject, creator) => await call('entitlement', 'GET', `/api/v1/entitlements/${encodeURIComponent(subject)}${qs({ streamer: creator })}`),
rates: async () => await call(caps.rates ? 'rates' : 'entitlement', 'GET', '/api/v1/rates'),
rates: async () => await call(caps.rates ? 'rates' : 'entitlement', 'GET', '/api/v1/rates'),
+ /** { streamer, tiers: [{ tier, price_cents, status }], default_price_cents, min_price_cents, max_price_cents, max_tiers }: Billing prices a creator's tiers. */
+ listTiers: async (creator) => await call(caps.tier ? 'tier' : 'intent', 'GET', `/api/v1/tiers/${encodeURIComponent(creator)}`),
+ /** { tier }: set the monthly price (USD cents) of one of `creator`'s tiers (billing.tier.manage; status active or archived). */
+ setTier: async ({ creator, tier, priceCents, status, key, traceparent }) => await call('tier', 'PUT', `/api/v1/tiers/${encodeURIComponent(creator)}/${encodeURIComponent(tier)}`, {
+ body: { price_cents: priceCents, ...(status ? { status } : {}) }, key, traceparent,
+ }),
baseUrl: () => base,
baseUrl: () => base,
};
};
}
}
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.50.1';
+const SDK_VERSION = '0.51.0';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -85,6 +85,30 @@ run([
@@ -85,6 +85,30 @@ run([
assert.equal(calls[4].init.headers['Idempotency-Key'], undefined);
assert.equal(calls[4].init.headers['Idempotency-Key'], undefined);
assert.equal(client.baseUrl(), 'http://billing.test');
assert.equal(client.baseUrl(), 'http://billing.test');
}],
}],
+ ['tiers: the tier on a subscription checkout and a credit period; listTiers and setTier use billing.tier.manage', async () => {
+ const { client: noTierCap, calls: readCalls } = rig([json(200, { tiers: [] })]);
+ await noTierCap.listTiers('u2');
+ assert.equal(readCalls[0].init.headers.Authorization, 'Bearer intent', 'listTiers without caps.tier reads with the intent capability');
+ const calls = [];
+ const fetchImpl = async (url, init) => { calls.push({ url, init }); return json(200, { ok: true }); };
+ const mk = (name) => ({ authHeaders: async () => ({ Authorization: `Bearer ${name}` }), invalidate: () => {} });
+ const client = createCommerceClient(CONFIG, { caps: { ...CAPS, tier: 'billing.tier.manage' }, fetchImpl, tokenClients: { intent: mk('intent'), subscription: mk('subscription'), tier: mk('tier') } });
+ await client.createIntent({ provider: 'powerchat', kind: 'subscription', subject: 'u1', creator: 'u2', tier: 2, key: 'k' });
+ await client.subscribeWithCredit({ subscriber: 'a', creator: 'b', tier: 3, key: 's' });
+ await client.listTiers('u 2');
+ await client.setTier({ creator: 'u2', tier: 2, priceCents: 1500, key: 't' });
+ await client.setTier({ creator: 'u2', tier: 2, priceCents: 1500, status: 'archived', key: 't2' });
+ assert.equal(JSON.parse(calls[0].init.body).tier, 2);
+ assert.equal(JSON.parse(calls[1].init.body).tier, 3);
+ assert.deepEqual(calls.slice(2, 5).map((c) => [c.init.method, c.url.replace('http://billing.test', '')]), [['GET', '/api/v1/tiers/u%202'], ['PUT', '/api/v1/tiers/u2/2'], ['PUT', '/api/v1/tiers/u2/2']]);
+ assert.deepEqual(calls.slice(2, 5).map((c) => c.init.headers.Authorization), ['Bearer tier', 'Bearer tier', 'Bearer tier']);
+ assert.deepEqual(JSON.parse(calls[3].init.body), { price_cents: 1500 });
+ assert.deepEqual(JSON.parse(calls[4].init.body), { price_cents: 1500, status: 'archived' });
+ assert.equal(calls[3].init.headers['Idempotency-Key'], 't');
+ const plain = rig([json(200, {})]);
+ await plain.client.subscribeWithCredit({ subscriber: 'a', creator: 'b' });
+ assert.equal('tier' in JSON.parse(plain.calls[0].init.body), false, 'no tier unless asked: Billing sells tier 1');
+ }],
['a 401 invalidates the token and retries once with the same Idempotency-Key', async () => {
['a 401 invalidates the token and retries once with the same Idempotency-Key', async () => {
const { client, calls, invalidated } = rig([json(401, { code: 'unauthorized' }), json(200, { transaction: { id: 't' } })]);
const { client, calls, invalidated } = rig([json(401, { code: 'unauthorized' }), json(200, { transaction: { id: 't' } })]);
const out = await client.createTransfer({ from: 'a', to: 'b', amount: 1, kind: 'tip', key: 'tip:i1' });
const out = await client.createTransfer({ from: 'a', to: 'b', amount: 1, kind: 'tip', key: 'tip:i1' });
@@ -16,7 +16,8 @@ export type CommerceConfig = {
@@ -16,7 +16,8 @@ export type CommerceConfig = {
network?: { internalUrl: string };
network?: { internalUrl: string };
oauth?: { clientId: string; clientSecret: string };
oauth?: { clientId: string; clientSecret: string };
};
};
-export type CommerceCaps = { intent?: string; transfer?: string; subscription?: string; entitlement?: string; rates?: string };
+export type CommerceCaps = { intent?: string; transfer?: string; subscription?: string; entitlement?: string; rates?: string; tier?: string };
+export type CommerceTier = { tier: number; price_cents: number; status: 'active' | 'archived'; updated_at?: string };
export type CommerceTokenClient = { authHeaders(): Promise<Record<string, string>>; invalidate(): void };
export type CommerceTokenClient = { authHeaders(): Promise<Record<string, string>>; invalidate(): void };
export type CommerceOptions = {
export type CommerceOptions = {
caps: CommerceCaps;
caps: CommerceCaps;
@@ -27,15 +28,19 @@ export type CommerceOptions = {
@@ -27,15 +28,19 @@ export type CommerceOptions = {
};
};
export type CommerceCall = { key?: string; traceparent?: string };
export type CommerceCall = { key?: string; traceparent?: string };
export type CommerceClient = {
export type CommerceClient = {
- createIntent(a: { provider: string; kind?: 'purchase' | 'subscription'; subject: string; bits?: number; creator?: string; autoRenew?: boolean; successUrl?: string; cancelUrl?: string } & CommerceCall): Promise<any>;
+ createIntent(a: { provider: string; kind?: 'purchase' | 'subscription'; subject: string; bits?: number; creator?: string; tier?: number; autoRenew?: boolean; successUrl?: string; cancelUrl?: string } & CommerceCall): Promise<any>;
createTransfer(a: { from: string; to: string; amount: number; kind?: string; target?: unknown; message?: string } & CommerceCall): Promise<any>;
createTransfer(a: { from: string; to: string; amount: number; kind?: string; target?: unknown; message?: string } & CommerceCall): Promise<any>;
refundTransfer(a: { txnId: string; amount?: number; reason?: string; key?: string }): Promise<any>;
refundTransfer(a: { txnId: string; amount?: number; reason?: string; key?: string }): Promise<any>;
- subscribeWithCredit(a: { subscriber: string; creator: string; autoRenew?: boolean } & CommerceCall): Promise<any>;
+ subscribeWithCredit(a: { subscriber: string; creator: string; tier?: number; autoRenew?: boolean } & CommerceCall): Promise<any>;
cancelSubscription(a: { id: string } & CommerceCall): Promise<any>;
cancelSubscription(a: { id: string } & CommerceCall): Promise<any>;
getSubscription(id: string): Promise<any>;
getSubscription(id: string): Promise<any>;
listSubscriptions(a?: { streamer?: string; subscriber?: string; status?: string }): Promise<any>;
listSubscriptions(a?: { streamer?: string; subscriber?: string; status?: string }): Promise<any>;
entitlement(subject: string, creator?: string): Promise<any>;
entitlement(subject: string, creator?: string): Promise<any>;
rates(): Promise<any>;
rates(): Promise<any>;
+ /** A creator's tier prices (Billing, contracts 0.201.0); uses caps.tier, else caps.intent. */
+ listTiers(creator: string): Promise<{ streamer: { type: 'user'; id: string }; tiers: CommerceTier[]; default_price_cents: number; min_price_cents: number; max_price_cents: number; max_tiers: number }>;
+ /** Set one tier's monthly price (billing.tier.manage). */
+ setTier(a: { creator: string; tier: number; priceCents: number; status?: 'active' | 'archived' } & CommerceCall): Promise<{ tier: CommerceTier }>;
baseUrl(): string;
baseUrl(): string;
};
};
export declare function createCommerceClient(config: CommerceConfig, options: CommerceOptions): CommerceClient;
export declare function createCommerceClient(config: CommerceConfig, options: CommerceOptions): CommerceClient;