@usebillow/sdk 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/{billing-B16zL5xU.d.cts → billing-8ug1Gw05.d.cts} +9 -9
  3. package/dist/{billing-BF4OGaLI.d.ts → billing-Bs10EcjR.d.ts} +9 -9
  4. package/dist/{billing-status-BZQN_gm7.d.cts → billing-status-DZkB0VPK.d.cts} +5 -5
  5. package/dist/{billing-status-BZQN_gm7.d.ts → billing-status-DZkB0VPK.d.ts} +5 -5
  6. package/dist/{chunk-CCG4F5FK.js → chunk-47J7FYM5.js} +2 -2
  7. package/dist/chunk-47J7FYM5.js.map +1 -0
  8. package/dist/{chunk-GT5VBLN5.js → chunk-XKN7ZRAR.js} +45 -22
  9. package/dist/chunk-XKN7ZRAR.js.map +1 -0
  10. package/dist/config.cjs +3 -3
  11. package/dist/config.cjs.map +1 -1
  12. package/dist/config.d.cts +9 -9
  13. package/dist/config.d.ts +9 -9
  14. package/dist/config.js +3 -3
  15. package/dist/config.js.map +1 -1
  16. package/dist/{hosted-domains-DwakPG4J.d.cts → hosted-domains-Ci6jeNAe.d.cts} +15 -1
  17. package/dist/{hosted-domains-DSTQ1GZz.d.ts → hosted-domains-DSs5Iw9U.d.ts} +15 -1
  18. package/dist/index.cjs +43 -20
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +52 -47
  21. package/dist/index.d.ts +52 -47
  22. package/dist/index.js +2 -2
  23. package/dist/ingestion.cjs.map +1 -1
  24. package/dist/ingestion.d.cts +18 -18
  25. package/dist/ingestion.d.ts +18 -18
  26. package/dist/ingestion.js.map +1 -1
  27. package/dist/react.cjs +28 -5
  28. package/dist/react.cjs.map +1 -1
  29. package/dist/react.d.cts +6 -6
  30. package/dist/react.d.ts +6 -6
  31. package/dist/react.js +2 -2
  32. package/dist/react.js.map +1 -1
  33. package/dist/server.cjs.map +1 -1
  34. package/dist/server.d.cts +11 -11
  35. package/dist/server.d.ts +11 -11
  36. package/dist/server.js.map +1 -1
  37. package/dist/status.cjs.map +1 -1
  38. package/dist/status.d.cts +1 -1
  39. package/dist/status.d.ts +1 -1
  40. package/dist/status.js +1 -1
  41. package/dist/webhooks.cjs.map +1 -1
  42. package/dist/webhooks.d.cts +20 -15
  43. package/dist/webhooks.d.ts +20 -15
  44. package/dist/webhooks.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/chunk-CCG4F5FK.js.map +0 -1
  47. package/dist/chunk-GT5VBLN5.js.map +0 -1
package/dist/config.d.cts CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Billow } from './index.cjs';
2
- import { F as FeatureKind, M as MeterConfig, C as CreateProductInput, a as CreateCouponInput } from './billing-B16zL5xU.cjs';
2
+ import { F as FeatureKind, M as MeterConfig, C as CreateProductInput, a as CreateCouponInput } from './billing-8ug1Gw05.cjs';
3
3
  import './index.d-DSEYhV2c.cjs';
4
4
  import 'zod';
5
- import './billing-status-BZQN_gm7.cjs';
5
+ import './billing-status-DZkB0VPK.cjs';
6
6
  import './status.cjs';
7
- import './hosted-domains-DwakPG4J.cjs';
7
+ import './hosted-domains-Ci6jeNAe.cjs';
8
8
 
9
9
  /**
10
10
  * Code-as-config sync (ARCHITECTURE §12 "code-as-config sync"). Keep your catalog
11
- * — features, products, coupons — in a typed config file and `syncConfig` it into
11
+ * - features, products, coupons - in a typed config file and `syncConfig` it into
12
12
  * a billow server. Sync is **declarative and additive**: it creates whatever is
13
13
  * missing and *reports drift* on what already exists, but never deletes and never
14
14
  * rewrites an existing product (catalog edits are made deliberately through the
15
- * dashboard/API — ADR-0015 — not silently on every sync).
15
+ * dashboard/API - ADR-0015 - not silently on every sync).
16
16
  *
17
17
  * import { defineConfig, syncConfig } from "@usebillow/sdk/config";
18
18
  * const config = defineConfig({ features: [...], products: [...], coupons: [...] });
@@ -25,15 +25,15 @@ interface ConfigFeature {
25
25
  kind: FeatureKind;
26
26
  /**
27
27
  * For a `metered` feature: how its usage aggregates (ADR-0011). Omit to get a `sum`
28
- * meter. Set it here so sync creates the meter you want instead of the default — after
28
+ * meter. Set it here so sync creates the meter you want instead of the default - after
29
29
  * usage is recorded the aggregation can no longer change.
30
30
  */
31
31
  meter?: MeterConfig;
32
32
  }
33
- /** A product entry — the same shape as `billow.products.create`. */
33
+ /** A product entry - the same shape as `billow.products.create`. */
34
34
  type ConfigProduct = CreateProductInput;
35
35
  /**
36
- * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** — it
36
+ * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** - it
37
37
  * is the stable identity sync diffs against (a code-less coupon can't be matched).
38
38
  */
39
39
  type ConfigCoupon = Omit<CreateCouponInput, "code"> & {
@@ -44,7 +44,7 @@ interface BillowConfig {
44
44
  products?: ConfigProduct[];
45
45
  coupons?: ConfigCoupon[];
46
46
  }
47
- /** Identity helper — returns its argument, typed, for editor completion + checks. */
47
+ /** Identity helper - returns its argument, typed, for editor completion + checks. */
48
48
  declare function defineConfig(config: BillowConfig): BillowConfig;
49
49
  type SyncAction = "create" | "unchanged" | "drift";
50
50
  interface SyncItem {
package/dist/config.d.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Billow } from './index.js';
2
- import { F as FeatureKind, M as MeterConfig, C as CreateProductInput, a as CreateCouponInput } from './billing-BF4OGaLI.js';
2
+ import { F as FeatureKind, M as MeterConfig, C as CreateProductInput, a as CreateCouponInput } from './billing-Bs10EcjR.js';
3
3
  import './index.d-DSEYhV2c.js';
4
4
  import 'zod';
5
- import './billing-status-BZQN_gm7.js';
5
+ import './billing-status-DZkB0VPK.js';
6
6
  import './status.js';
7
- import './hosted-domains-DSTQ1GZz.js';
7
+ import './hosted-domains-DSs5Iw9U.js';
8
8
 
9
9
  /**
10
10
  * Code-as-config sync (ARCHITECTURE §12 "code-as-config sync"). Keep your catalog
11
- * — features, products, coupons — in a typed config file and `syncConfig` it into
11
+ * - features, products, coupons - in a typed config file and `syncConfig` it into
12
12
  * a billow server. Sync is **declarative and additive**: it creates whatever is
13
13
  * missing and *reports drift* on what already exists, but never deletes and never
14
14
  * rewrites an existing product (catalog edits are made deliberately through the
15
- * dashboard/API — ADR-0015 — not silently on every sync).
15
+ * dashboard/API - ADR-0015 - not silently on every sync).
16
16
  *
17
17
  * import { defineConfig, syncConfig } from "@usebillow/sdk/config";
18
18
  * const config = defineConfig({ features: [...], products: [...], coupons: [...] });
@@ -25,15 +25,15 @@ interface ConfigFeature {
25
25
  kind: FeatureKind;
26
26
  /**
27
27
  * For a `metered` feature: how its usage aggregates (ADR-0011). Omit to get a `sum`
28
- * meter. Set it here so sync creates the meter you want instead of the default — after
28
+ * meter. Set it here so sync creates the meter you want instead of the default - after
29
29
  * usage is recorded the aggregation can no longer change.
30
30
  */
31
31
  meter?: MeterConfig;
32
32
  }
33
- /** A product entry — the same shape as `billow.products.create`. */
33
+ /** A product entry - the same shape as `billow.products.create`. */
34
34
  type ConfigProduct = CreateProductInput;
35
35
  /**
36
- * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** — it
36
+ * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** - it
37
37
  * is the stable identity sync diffs against (a code-less coupon can't be matched).
38
38
  */
39
39
  type ConfigCoupon = Omit<CreateCouponInput, "code"> & {
@@ -44,7 +44,7 @@ interface BillowConfig {
44
44
  products?: ConfigProduct[];
45
45
  coupons?: ConfigCoupon[];
46
46
  }
47
- /** Identity helper — returns its argument, typed, for editor completion + checks. */
47
+ /** Identity helper - returns its argument, typed, for editor completion + checks. */
48
48
  declare function defineConfig(config: BillowConfig): BillowConfig;
49
49
  type SyncAction = "create" | "unchanged" | "drift";
50
50
  interface SyncItem {
package/dist/config.js CHANGED
@@ -147,7 +147,7 @@ function normMeter(kind, meter) {
147
147
  return JSON.stringify({
148
148
  aggregation: meter?.aggregation ?? "sum",
149
149
  eventProperty: meter?.eventProperty ?? null,
150
- // The filter list and each filter's values are unordered sets — sort both so a mere
150
+ // The filter list and each filter's values are unordered sets - sort both so a mere
151
151
  // reordering isn't reported as drift (matches the server's no-op semantics).
152
152
  filters: (meter?.filters ?? []).map((f) => ({ key: f.key, values: [...f.values].sort() })).sort((a, b) => {
153
153
  if (a.key !== b.key) return a.key < b.key ? -1 : 1;
@@ -158,7 +158,7 @@ function normMeter(kind, meter) {
158
158
  roundingMode: meter?.roundingMode ?? "none",
159
159
  roundingScale: meter?.roundingScale ?? null,
160
160
  // Reset anchoring (plan 07). Server omits these for cycle meters, so default to cycle/null
161
- // on both sides — an omitted anchor and an explicit `cycle` compare equal (no false drift).
161
+ // on both sides - an omitted anchor and an explicit `cycle` compare equal (no false drift).
162
162
  resetAnchor: meter?.resetAnchor ?? "cycle",
163
163
  resetTz: meter?.resetTz ?? null
164
164
  });
@@ -177,7 +177,7 @@ function normPrice(p) {
177
177
  usageTiers: canonTiers(p.usageTiers),
178
178
  packageSize: p.packageSize ?? null,
179
179
  rateBps: p.rateBps ?? null,
180
- // Tax overrides (Phase E) — null = inherit org default; included so a change
180
+ // Tax overrides (Phase E) - null = inherit org default; included so a change
181
181
  // is detected as drift.
182
182
  taxRateBps: p.taxRateBps ?? null,
183
183
  taxBehavior: p.taxBehavior ?? null
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["/**\n * Code-as-config sync (ARCHITECTURE §12 \"code-as-config sync\"). Keep your catalog\n * — features, products, coupons — in a typed config file and `syncConfig` it into\n * a billow server. Sync is **declarative and additive**: it creates whatever is\n * missing and *reports drift* on what already exists, but never deletes and never\n * rewrites an existing product (catalog edits are made deliberately through the\n * dashboard/API — ADR-0015 — not silently on every sync).\n *\n * import { defineConfig, syncConfig } from \"@usebillow/sdk/config\";\n * const config = defineConfig({ features: [...], products: [...], coupons: [...] });\n * await syncConfig(billow, config, { onProgress: console.log });\n */\nimport type {\n Billow,\n Coupon,\n CreateCouponInput,\n CreateProductInput,\n Feature,\n FeatureKind,\n MeterConfig,\n Product,\n} from \"./index\";\n\nexport interface ConfigFeature {\n slug: string;\n name: string;\n kind: FeatureKind;\n /**\n * For a `metered` feature: how its usage aggregates (ADR-0011). Omit to get a `sum`\n * meter. Set it here so sync creates the meter you want instead of the default — after\n * usage is recorded the aggregation can no longer change.\n */\n meter?: MeterConfig;\n}\n\n/** A product entry — the same shape as `billow.products.create`. */\nexport type ConfigProduct = CreateProductInput;\n\n/**\n * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** — it\n * is the stable identity sync diffs against (a code-less coupon can't be matched).\n */\nexport type ConfigCoupon = Omit<CreateCouponInput, \"code\"> & { code: string };\n\nexport interface BillowConfig {\n features?: ConfigFeature[];\n products?: ConfigProduct[];\n coupons?: ConfigCoupon[];\n}\n\n/** Identity helper — returns its argument, typed, for editor completion + checks. */\nexport function defineConfig(config: BillowConfig): BillowConfig {\n return config;\n}\n\nexport type SyncAction = \"create\" | \"unchanged\" | \"drift\";\n\nexport interface SyncItem {\n kind: \"feature\" | \"product\" | \"coupon\";\n /** Slug (features/products) or code (coupons). */\n id: string;\n action: SyncAction;\n /** For `drift`: which aspects differ from the server (human-readable). */\n changes?: string[];\n}\n\nexport interface SyncPlan {\n features: SyncItem[];\n products: SyncItem[];\n coupons: SyncItem[];\n}\n\nexport interface SyncResult {\n plan: SyncPlan;\n created: { features: string[]; products: string[]; coupons: string[] };\n /**\n * Items that still differ from the config after the sync - reported, not applied. With\n * `applyMetadata`, product metadata that was written is removed from here (see `applied`).\n */\n drift: SyncItem[];\n /** Product slugs whose metadata was written this run (only when `applyMetadata` is set). */\n applied: { productMetadata: string[] };\n}\n\n/**\n * Diff a config against the server without writing anything. Each entry is\n * `create` (missing), `unchanged` (matches), or `drift` (exists but differs).\n */\nexport async function planConfig(billow: Billow, config: BillowConfig): Promise<SyncPlan> {\n // Collect the whole catalog in as few round-trips as the server allows (page size caps at 100).\n const [features, products, coupons] = await Promise.all([\n billow.features.list({ limit: 100 }).listAll(),\n billow.products.list(),\n billow.coupons.list({ limit: 100 }).listAll(),\n ]);\n\n const featureBySlug = new Map(features.map((f) => [f.slug, f]));\n const productBySlug = new Map(products.map((p) => [p.slug, p]));\n const couponByCode = new Map(coupons.filter((c) => c.code).map((c) => [c.code as string, c]));\n\n return {\n features: (config.features ?? []).map((f) => planFeature(f, featureBySlug.get(f.slug))),\n products: (config.products ?? []).map((p) => planProduct(p, productBySlug.get(p.slug))),\n coupons: (config.coupons ?? []).map((c) => planCoupon(c, couponByCode.get(c.code))),\n };\n}\n\n/**\n * Apply a config: create everything missing (features → products → coupons, the\n * dependency order, since products reference feature slugs), and return what was\n * created plus any drift it found.\n *\n * Structural drift (prices, entitlements, name, ...) is never auto-applied - those edits are\n * version-bearing (ADR-0015: collected at renewal) and belong to a deliberate `products.update`.\n * With `applyMetadata: true`, product **metadata** drift IS written, because metadata lives on\n * the product row (not the version), so updating it is version-independent and cannot rewrite\n * billed history. This closes the write-side gap where a config's product metadata could only\n * be reported, never pushed. The write is a full-map replace (config is authoritative): a key\n * present on the server but absent from the config is cleared.\n */\nexport async function syncConfig(\n billow: Billow,\n config: BillowConfig,\n opts: { onProgress?: (message: string) => void; applyMetadata?: boolean } = {},\n): Promise<SyncResult> {\n const plan = await planConfig(billow, config);\n const progress = opts.onProgress ?? (() => {});\n\n // Create in dependency order — features → products → coupons — since products\n // reference feature slugs. Each kind is created before the next begins.\n const created = {\n features: await createMissing(\n plan.features,\n config.features ?? [],\n (f) => f.slug,\n \"feature\",\n (f) => billow.features.create(f),\n progress,\n ),\n products: await createMissing(\n plan.products,\n config.products ?? [],\n (p) => p.slug,\n \"product\",\n (p) => billow.products.create(p),\n progress,\n ),\n coupons: await createMissing(\n plan.coupons,\n config.coupons ?? [],\n (c) => c.code,\n \"coupon\",\n (c) => billow.coupons.create(c),\n progress,\n ),\n };\n\n // Version-independent metadata is the one field safe to auto-write (a product-row column,\n // not a version). Apply it before computing drift so a resolved item drops out of the report.\n const applied = { productMetadata: [] as string[] };\n if (opts.applyMetadata) {\n const isMetadataChange = (c: string) => c.startsWith(\"metadata\");\n const productDefBySlug = new Map((config.products ?? []).map((p) => [p.slug, p]));\n for (const item of plan.products) {\n if (item.action !== \"drift\" || !item.changes?.some(isMetadataChange)) continue;\n const def = productDefBySlug.get(item.id);\n if (!def) continue;\n await billow.products.update(item.id, { metadata: def.metadata ?? {} });\n applied.productMetadata.push(item.id);\n progress(`~ product ${item.id} (metadata applied)`);\n // Reflect the write in the plan: drop metadata from this item, and mark it unchanged if\n // it had no other drift, so the returned drift no longer reports what we just fixed.\n item.changes = item.changes.filter((c) => !isMetadataChange(c));\n if (item.changes.length === 0) item.action = \"unchanged\";\n }\n }\n\n const drift = [...plan.features, ...plan.products, ...plan.coupons].filter(\n (i) => i.action === \"drift\",\n );\n return { plan, created, drift, applied };\n}\n\n/**\n * Create the `action: \"create\"` items of one kind, matching each plan item back to\n * its config definition by id (a Map, not a scan), and return the ids created.\n */\nasync function createMissing<T>(\n items: SyncItem[],\n defs: T[],\n idOf: (def: T) => string,\n label: string,\n create: (def: T) => Promise<unknown>,\n progress: (message: string) => void,\n): Promise<string[]> {\n const byId = new Map(defs.map((d) => [idOf(d), d]));\n const created: string[] = [];\n for (const item of items) {\n if (item.action !== \"create\") continue;\n const def = byId.get(item.id);\n if (!def) continue;\n await create(def);\n created.push(item.id);\n progress(`+ ${label} ${item.id}`);\n }\n return created;\n}\n\n/** A one-line-per-item summary of a plan, for CLI output. */\nexport function formatPlan(plan: SyncPlan): string {\n const mark = { create: \"+\", unchanged: \"=\", drift: \"~\" } as const;\n const lines: string[] = [];\n for (const item of [...plan.features, ...plan.products, ...plan.coupons]) {\n const detail = item.changes?.length ? ` (${item.changes.join(\", \")})` : \"\";\n lines.push(`${mark[item.action]} ${item.kind} ${item.id}${detail}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Per-entity diffing\n// ---------------------------------------------------------------------------\n\nfunction planFeature(def: ConfigFeature, existing: Feature | undefined): SyncItem {\n if (!existing) return { kind: \"feature\", id: def.slug, action: \"create\" };\n const changes = diffFields([\n [\"name\", def.name, existing.name],\n [\"kind\", def.kind, existing.kind],\n // A metered feature with no explicit meter gets a `sum` meter server-side; normalize\n // both sides so an aggregation change is reported as drift, not \"unchanged\".\n [\n \"meter\",\n normMeter(def.kind, def.meter),\n normMeter(existing.kind, existing.meter ?? undefined),\n ],\n ]);\n return { kind: \"feature\", id: def.slug, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\nfunction planProduct(def: ConfigProduct, existing: Product | undefined): SyncItem {\n if (!existing) return { kind: \"product\", id: def.slug, action: \"create\" };\n const changes = diffFields([\n [\"name\", def.name, existing.name],\n [\"type\", def.type, existing.type],\n [\"isAddOn\", def.isAddOn ?? false, existing.isAddOn],\n [\"metadata\", normMeta(def.metadata), normMeta(existing.metadata)],\n ]);\n if (!sameSet(def.prices.map(normPrice), (existing.prices ?? []).map(normPrice))) {\n changes.push(\"prices\");\n }\n if (!sameSet((def.entitlements ?? []).map(normEnt), (existing.entitlements ?? []).map(normEnt))) {\n changes.push(\"entitlements\");\n }\n return { kind: \"product\", id: def.slug, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\nfunction planCoupon(def: ConfigCoupon, existing: Coupon | undefined): SyncItem {\n if (!existing) return { kind: \"coupon\", id: def.code, action: \"create\" };\n const changes = diffFields([\n [\"kind\", def.kind, existing.kind],\n [\"value\", def.value, existing.value],\n [\"currency\", def.currency?.toUpperCase() ?? null, existing.currency],\n [\"duration\", def.duration, existing.duration],\n [\"durationCycles\", def.durationCycles ?? null, existing.durationCycles],\n [\"maxRedemptions\", def.maxRedemptions ?? null, existing.maxRedemptions],\n [\"expiresAt\", epochOrNull(def.expiresAt), epochOrNull(existing.expiresAt)],\n ]);\n if (!sameSet(def.productRestrictions ?? [], existing.productRestrictions ?? [])) {\n changes.push(\"productRestrictions\");\n }\n return { kind: \"coupon\", id: def.code, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\n// ---------------------------------------------------------------------------\n// Normalization helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Canonicalize a feature's meter for drift comparison. A boolean feature has no meter\n * (null); a metered feature with no explicit config gets the server's `sum` default, so\n * an omitted meter and an explicit `sum` meter compare equal. Filter objects are reshaped\n * to a fixed key order (a jsonb round-trip can reorder keys) while list order is preserved.\n */\n/**\n * Canonicalize a product's metadata map for drift comparison: sort keys so a mere\n * key-order difference (jsonb round-trips keys in insertion order) isn't reported as drift.\n * An omitted map and an empty map compare equal.\n */\nfunction normMeta(metadata: Record<string, string> | undefined): string {\n const entries = Object.entries(metadata ?? {}).sort((a, b) =>\n a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0,\n );\n return JSON.stringify(entries);\n}\n\nfunction normMeter(kind: FeatureKind, meter: MeterConfig | undefined): string | null {\n if (kind !== \"metered\") return null;\n return JSON.stringify({\n aggregation: meter?.aggregation ?? \"sum\",\n eventProperty: meter?.eventProperty ?? null,\n // The filter list and each filter's values are unordered sets — sort both so a mere\n // reordering isn't reported as drift (matches the server's no-op semantics).\n filters: (meter?.filters ?? [])\n .map((f) => ({ key: f.key, values: [...f.values].sort() }))\n // Total order: by key, then by (already-sorted) values, so same-key duplicate\n // filters canonicalize too rather than keeping input order.\n .sort((a, b) => {\n if (a.key !== b.key) return a.key < b.key ? -1 : 1;\n const av = JSON.stringify(a.values);\n const bv = JSON.stringify(b.values);\n return av < bv ? -1 : av > bv ? 1 : 0;\n }),\n roundingMode: meter?.roundingMode ?? \"none\",\n roundingScale: meter?.roundingScale ?? null,\n // Reset anchoring (plan 07). Server omits these for cycle meters, so default to cycle/null\n // on both sides — an omitted anchor and an explicit `cycle` compare equal (no false drift).\n resetAnchor: meter?.resetAnchor ?? \"cycle\",\n resetTz: meter?.resetTz ?? null,\n });\n}\n\nfunction normPrice(p: {\n kind: string;\n interval?: string | null;\n intervalCount?: number;\n amount: number;\n currency: string;\n meterFeature?: string | null;\n usageModel?: string | null;\n usageTiers?: unknown;\n packageSize?: number | null;\n rateBps?: number | null;\n taxRateBps?: number | null;\n taxBehavior?: string | null;\n}): string {\n return JSON.stringify({\n kind: p.kind,\n interval: p.interval ?? null,\n intervalCount: p.intervalCount ?? 1,\n amount: p.amount,\n currency: p.currency.toUpperCase(),\n meterFeature: p.meterFeature ?? null,\n // Usage prices default to graduated server-side; normalize so a model change\n // (or package/percentage param change) is detected as drift, not \"unchanged\".\n usageModel: p.kind === \"usage\" ? (p.usageModel ?? \"graduated\") : null,\n usageTiers: canonTiers(p.usageTiers),\n packageSize: p.packageSize ?? null,\n rateBps: p.rateBps ?? null,\n // Tax overrides (Phase E) — null = inherit org default; included so a change\n // is detected as drift.\n taxRateBps: p.taxRateBps ?? null,\n taxBehavior: p.taxBehavior ?? null,\n });\n}\n\n/**\n * Canonicalize usage tiers to a fixed key order before comparison. A jsonb\n * round-trip can reorder object keys, so raw `JSON.stringify` would report drift\n * on an unchanged tier set — normalize each tier to `{ upTo, unitAmount, flatAmount }`.\n */\nfunction canonTiers(\n tiers: unknown,\n): { upTo: unknown; unitAmount: unknown; flatAmount: unknown }[] | null {\n if (!Array.isArray(tiers)) return null;\n return tiers.map((t) => {\n const tier = (t ?? {}) as { upTo?: unknown; unitAmount?: unknown; flatAmount?: unknown };\n return {\n upTo: tier.upTo ?? null,\n unitAmount: tier.unitAmount ?? null,\n flatAmount: tier.flatAmount ?? null,\n };\n });\n}\n\nfunction normEnt(e: {\n feature: string;\n allowance?: number | null;\n resetInterval?: string | null;\n rollover?: boolean;\n}): string {\n return JSON.stringify({\n feature: e.feature,\n allowance: e.allowance ?? null,\n resetInterval: e.resetInterval ?? null,\n rollover: e.rollover ?? false,\n });\n}\n\n/** Field-by-field equality, returning `\"field: a → b\"` for each mismatch. */\nfunction diffFields(fields: [string, unknown, unknown][]): string[] {\n const changes: string[] = [];\n for (const [name, want, have] of fields) {\n if (want !== have) changes.push(`${name}: ${fmt(have)} → ${fmt(want)}`);\n }\n return changes;\n}\n\n/** Order-insensitive equality of two string lists (compared as multisets). */\nfunction sameSet(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sortedA = [...a].sort();\n const sortedB = [...b].sort();\n return sortedA.every((v, i) => v === sortedB[i]);\n}\n\nfunction epochOrNull(value: string | null | undefined): number | null {\n return value ? new Date(value).getTime() : null;\n}\n\nfunction fmt(value: unknown): string {\n if (value === null || value === undefined) return \"∅\";\n return String(value);\n}\n"]}
1
+ {"version":3,"sources":["../src/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["/**\n * Code-as-config sync (ARCHITECTURE §12 \"code-as-config sync\"). Keep your catalog\n * - features, products, coupons - in a typed config file and `syncConfig` it into\n * a billow server. Sync is **declarative and additive**: it creates whatever is\n * missing and *reports drift* on what already exists, but never deletes and never\n * rewrites an existing product (catalog edits are made deliberately through the\n * dashboard/API - ADR-0015 - not silently on every sync).\n *\n * import { defineConfig, syncConfig } from \"@usebillow/sdk/config\";\n * const config = defineConfig({ features: [...], products: [...], coupons: [...] });\n * await syncConfig(billow, config, { onProgress: console.log });\n */\nimport type {\n Billow,\n Coupon,\n CreateCouponInput,\n CreateProductInput,\n Feature,\n FeatureKind,\n MeterConfig,\n Product,\n} from \"./index\";\n\nexport interface ConfigFeature {\n slug: string;\n name: string;\n kind: FeatureKind;\n /**\n * For a `metered` feature: how its usage aggregates (ADR-0011). Omit to get a `sum`\n * meter. Set it here so sync creates the meter you want instead of the default - after\n * usage is recorded the aggregation can no longer change.\n */\n meter?: MeterConfig;\n}\n\n/** A product entry - the same shape as `billow.products.create`. */\nexport type ConfigProduct = CreateProductInput;\n\n/**\n * A coupon entry. Unlike `billow.coupons.create`, the `code` is **required** - it\n * is the stable identity sync diffs against (a code-less coupon can't be matched).\n */\nexport type ConfigCoupon = Omit<CreateCouponInput, \"code\"> & { code: string };\n\nexport interface BillowConfig {\n features?: ConfigFeature[];\n products?: ConfigProduct[];\n coupons?: ConfigCoupon[];\n}\n\n/** Identity helper - returns its argument, typed, for editor completion + checks. */\nexport function defineConfig(config: BillowConfig): BillowConfig {\n return config;\n}\n\nexport type SyncAction = \"create\" | \"unchanged\" | \"drift\";\n\nexport interface SyncItem {\n kind: \"feature\" | \"product\" | \"coupon\";\n /** Slug (features/products) or code (coupons). */\n id: string;\n action: SyncAction;\n /** For `drift`: which aspects differ from the server (human-readable). */\n changes?: string[];\n}\n\nexport interface SyncPlan {\n features: SyncItem[];\n products: SyncItem[];\n coupons: SyncItem[];\n}\n\nexport interface SyncResult {\n plan: SyncPlan;\n created: { features: string[]; products: string[]; coupons: string[] };\n /**\n * Items that still differ from the config after the sync - reported, not applied. With\n * `applyMetadata`, product metadata that was written is removed from here (see `applied`).\n */\n drift: SyncItem[];\n /** Product slugs whose metadata was written this run (only when `applyMetadata` is set). */\n applied: { productMetadata: string[] };\n}\n\n/**\n * Diff a config against the server without writing anything. Each entry is\n * `create` (missing), `unchanged` (matches), or `drift` (exists but differs).\n */\nexport async function planConfig(billow: Billow, config: BillowConfig): Promise<SyncPlan> {\n // Collect the whole catalog in as few round-trips as the server allows (page size caps at 100).\n const [features, products, coupons] = await Promise.all([\n billow.features.list({ limit: 100 }).listAll(),\n billow.products.list(),\n billow.coupons.list({ limit: 100 }).listAll(),\n ]);\n\n const featureBySlug = new Map(features.map((f) => [f.slug, f]));\n const productBySlug = new Map(products.map((p) => [p.slug, p]));\n const couponByCode = new Map(coupons.filter((c) => c.code).map((c) => [c.code as string, c]));\n\n return {\n features: (config.features ?? []).map((f) => planFeature(f, featureBySlug.get(f.slug))),\n products: (config.products ?? []).map((p) => planProduct(p, productBySlug.get(p.slug))),\n coupons: (config.coupons ?? []).map((c) => planCoupon(c, couponByCode.get(c.code))),\n };\n}\n\n/**\n * Apply a config: create everything missing (features → products → coupons, the\n * dependency order, since products reference feature slugs), and return what was\n * created plus any drift it found.\n *\n * Structural drift (prices, entitlements, name, ...) is never auto-applied - those edits are\n * version-bearing (ADR-0015: collected at renewal) and belong to a deliberate `products.update`.\n * With `applyMetadata: true`, product **metadata** drift IS written, because metadata lives on\n * the product row (not the version), so updating it is version-independent and cannot rewrite\n * billed history. This closes the write-side gap where a config's product metadata could only\n * be reported, never pushed. The write is a full-map replace (config is authoritative): a key\n * present on the server but absent from the config is cleared.\n */\nexport async function syncConfig(\n billow: Billow,\n config: BillowConfig,\n opts: { onProgress?: (message: string) => void; applyMetadata?: boolean } = {},\n): Promise<SyncResult> {\n const plan = await planConfig(billow, config);\n const progress = opts.onProgress ?? (() => {});\n\n // Create in dependency order - features → products → coupons - since products\n // reference feature slugs. Each kind is created before the next begins.\n const created = {\n features: await createMissing(\n plan.features,\n config.features ?? [],\n (f) => f.slug,\n \"feature\",\n (f) => billow.features.create(f),\n progress,\n ),\n products: await createMissing(\n plan.products,\n config.products ?? [],\n (p) => p.slug,\n \"product\",\n (p) => billow.products.create(p),\n progress,\n ),\n coupons: await createMissing(\n plan.coupons,\n config.coupons ?? [],\n (c) => c.code,\n \"coupon\",\n (c) => billow.coupons.create(c),\n progress,\n ),\n };\n\n // Version-independent metadata is the one field safe to auto-write (a product-row column,\n // not a version). Apply it before computing drift so a resolved item drops out of the report.\n const applied = { productMetadata: [] as string[] };\n if (opts.applyMetadata) {\n const isMetadataChange = (c: string) => c.startsWith(\"metadata\");\n const productDefBySlug = new Map((config.products ?? []).map((p) => [p.slug, p]));\n for (const item of plan.products) {\n if (item.action !== \"drift\" || !item.changes?.some(isMetadataChange)) continue;\n const def = productDefBySlug.get(item.id);\n if (!def) continue;\n await billow.products.update(item.id, { metadata: def.metadata ?? {} });\n applied.productMetadata.push(item.id);\n progress(`~ product ${item.id} (metadata applied)`);\n // Reflect the write in the plan: drop metadata from this item, and mark it unchanged if\n // it had no other drift, so the returned drift no longer reports what we just fixed.\n item.changes = item.changes.filter((c) => !isMetadataChange(c));\n if (item.changes.length === 0) item.action = \"unchanged\";\n }\n }\n\n const drift = [...plan.features, ...plan.products, ...plan.coupons].filter(\n (i) => i.action === \"drift\",\n );\n return { plan, created, drift, applied };\n}\n\n/**\n * Create the `action: \"create\"` items of one kind, matching each plan item back to\n * its config definition by id (a Map, not a scan), and return the ids created.\n */\nasync function createMissing<T>(\n items: SyncItem[],\n defs: T[],\n idOf: (def: T) => string,\n label: string,\n create: (def: T) => Promise<unknown>,\n progress: (message: string) => void,\n): Promise<string[]> {\n const byId = new Map(defs.map((d) => [idOf(d), d]));\n const created: string[] = [];\n for (const item of items) {\n if (item.action !== \"create\") continue;\n const def = byId.get(item.id);\n if (!def) continue;\n await create(def);\n created.push(item.id);\n progress(`+ ${label} ${item.id}`);\n }\n return created;\n}\n\n/** A one-line-per-item summary of a plan, for CLI output. */\nexport function formatPlan(plan: SyncPlan): string {\n const mark = { create: \"+\", unchanged: \"=\", drift: \"~\" } as const;\n const lines: string[] = [];\n for (const item of [...plan.features, ...plan.products, ...plan.coupons]) {\n const detail = item.changes?.length ? ` (${item.changes.join(\", \")})` : \"\";\n lines.push(`${mark[item.action]} ${item.kind} ${item.id}${detail}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Per-entity diffing\n// ---------------------------------------------------------------------------\n\nfunction planFeature(def: ConfigFeature, existing: Feature | undefined): SyncItem {\n if (!existing) return { kind: \"feature\", id: def.slug, action: \"create\" };\n const changes = diffFields([\n [\"name\", def.name, existing.name],\n [\"kind\", def.kind, existing.kind],\n // A metered feature with no explicit meter gets a `sum` meter server-side; normalize\n // both sides so an aggregation change is reported as drift, not \"unchanged\".\n [\n \"meter\",\n normMeter(def.kind, def.meter),\n normMeter(existing.kind, existing.meter ?? undefined),\n ],\n ]);\n return { kind: \"feature\", id: def.slug, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\nfunction planProduct(def: ConfigProduct, existing: Product | undefined): SyncItem {\n if (!existing) return { kind: \"product\", id: def.slug, action: \"create\" };\n const changes = diffFields([\n [\"name\", def.name, existing.name],\n [\"type\", def.type, existing.type],\n [\"isAddOn\", def.isAddOn ?? false, existing.isAddOn],\n [\"metadata\", normMeta(def.metadata), normMeta(existing.metadata)],\n ]);\n if (!sameSet(def.prices.map(normPrice), (existing.prices ?? []).map(normPrice))) {\n changes.push(\"prices\");\n }\n if (!sameSet((def.entitlements ?? []).map(normEnt), (existing.entitlements ?? []).map(normEnt))) {\n changes.push(\"entitlements\");\n }\n return { kind: \"product\", id: def.slug, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\nfunction planCoupon(def: ConfigCoupon, existing: Coupon | undefined): SyncItem {\n if (!existing) return { kind: \"coupon\", id: def.code, action: \"create\" };\n const changes = diffFields([\n [\"kind\", def.kind, existing.kind],\n [\"value\", def.value, existing.value],\n [\"currency\", def.currency?.toUpperCase() ?? null, existing.currency],\n [\"duration\", def.duration, existing.duration],\n [\"durationCycles\", def.durationCycles ?? null, existing.durationCycles],\n [\"maxRedemptions\", def.maxRedemptions ?? null, existing.maxRedemptions],\n [\"expiresAt\", epochOrNull(def.expiresAt), epochOrNull(existing.expiresAt)],\n ]);\n if (!sameSet(def.productRestrictions ?? [], existing.productRestrictions ?? [])) {\n changes.push(\"productRestrictions\");\n }\n return { kind: \"coupon\", id: def.code, action: changes.length ? \"drift\" : \"unchanged\", changes };\n}\n\n// ---------------------------------------------------------------------------\n// Normalization helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Canonicalize a feature's meter for drift comparison. A boolean feature has no meter\n * (null); a metered feature with no explicit config gets the server's `sum` default, so\n * an omitted meter and an explicit `sum` meter compare equal. Filter objects are reshaped\n * to a fixed key order (a jsonb round-trip can reorder keys) while list order is preserved.\n */\n/**\n * Canonicalize a product's metadata map for drift comparison: sort keys so a mere\n * key-order difference (jsonb round-trips keys in insertion order) isn't reported as drift.\n * An omitted map and an empty map compare equal.\n */\nfunction normMeta(metadata: Record<string, string> | undefined): string {\n const entries = Object.entries(metadata ?? {}).sort((a, b) =>\n a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0,\n );\n return JSON.stringify(entries);\n}\n\nfunction normMeter(kind: FeatureKind, meter: MeterConfig | undefined): string | null {\n if (kind !== \"metered\") return null;\n return JSON.stringify({\n aggregation: meter?.aggregation ?? \"sum\",\n eventProperty: meter?.eventProperty ?? null,\n // The filter list and each filter's values are unordered sets - sort both so a mere\n // reordering isn't reported as drift (matches the server's no-op semantics).\n filters: (meter?.filters ?? [])\n .map((f) => ({ key: f.key, values: [...f.values].sort() }))\n // Total order: by key, then by (already-sorted) values, so same-key duplicate\n // filters canonicalize too rather than keeping input order.\n .sort((a, b) => {\n if (a.key !== b.key) return a.key < b.key ? -1 : 1;\n const av = JSON.stringify(a.values);\n const bv = JSON.stringify(b.values);\n return av < bv ? -1 : av > bv ? 1 : 0;\n }),\n roundingMode: meter?.roundingMode ?? \"none\",\n roundingScale: meter?.roundingScale ?? null,\n // Reset anchoring (plan 07). Server omits these for cycle meters, so default to cycle/null\n // on both sides - an omitted anchor and an explicit `cycle` compare equal (no false drift).\n resetAnchor: meter?.resetAnchor ?? \"cycle\",\n resetTz: meter?.resetTz ?? null,\n });\n}\n\nfunction normPrice(p: {\n kind: string;\n interval?: string | null;\n intervalCount?: number;\n amount: number;\n currency: string;\n meterFeature?: string | null;\n usageModel?: string | null;\n usageTiers?: unknown;\n packageSize?: number | null;\n rateBps?: number | null;\n taxRateBps?: number | null;\n taxBehavior?: string | null;\n}): string {\n return JSON.stringify({\n kind: p.kind,\n interval: p.interval ?? null,\n intervalCount: p.intervalCount ?? 1,\n amount: p.amount,\n currency: p.currency.toUpperCase(),\n meterFeature: p.meterFeature ?? null,\n // Usage prices default to graduated server-side; normalize so a model change\n // (or package/percentage param change) is detected as drift, not \"unchanged\".\n usageModel: p.kind === \"usage\" ? (p.usageModel ?? \"graduated\") : null,\n usageTiers: canonTiers(p.usageTiers),\n packageSize: p.packageSize ?? null,\n rateBps: p.rateBps ?? null,\n // Tax overrides (Phase E) - null = inherit org default; included so a change\n // is detected as drift.\n taxRateBps: p.taxRateBps ?? null,\n taxBehavior: p.taxBehavior ?? null,\n });\n}\n\n/**\n * Canonicalize usage tiers to a fixed key order before comparison. A jsonb\n * round-trip can reorder object keys, so raw `JSON.stringify` would report drift\n * on an unchanged tier set - normalize each tier to `{ upTo, unitAmount, flatAmount }`.\n */\nfunction canonTiers(\n tiers: unknown,\n): { upTo: unknown; unitAmount: unknown; flatAmount: unknown }[] | null {\n if (!Array.isArray(tiers)) return null;\n return tiers.map((t) => {\n const tier = (t ?? {}) as { upTo?: unknown; unitAmount?: unknown; flatAmount?: unknown };\n return {\n upTo: tier.upTo ?? null,\n unitAmount: tier.unitAmount ?? null,\n flatAmount: tier.flatAmount ?? null,\n };\n });\n}\n\nfunction normEnt(e: {\n feature: string;\n allowance?: number | null;\n resetInterval?: string | null;\n rollover?: boolean;\n}): string {\n return JSON.stringify({\n feature: e.feature,\n allowance: e.allowance ?? null,\n resetInterval: e.resetInterval ?? null,\n rollover: e.rollover ?? false,\n });\n}\n\n/** Field-by-field equality, returning `\"field: a → b\"` for each mismatch. */\nfunction diffFields(fields: [string, unknown, unknown][]): string[] {\n const changes: string[] = [];\n for (const [name, want, have] of fields) {\n if (want !== have) changes.push(`${name}: ${fmt(have)} → ${fmt(want)}`);\n }\n return changes;\n}\n\n/** Order-insensitive equality of two string lists (compared as multisets). */\nfunction sameSet(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sortedA = [...a].sort();\n const sortedB = [...b].sort();\n return sortedA.every((v, i) => v === sortedB[i]);\n}\n\nfunction epochOrNull(value: string | null | undefined): number | null {\n return value ? new Date(value).getTime() : null;\n}\n\nfunction fmt(value: unknown): string {\n if (value === null || value === undefined) return \"∅\";\n return String(value);\n}\n"]}
@@ -155,7 +155,11 @@ type CreditTransactionType = "grant" | "hold" | "release" | "consume" | "expire"
155
155
  interface CreditLedgerEntry {
156
156
  id: string;
157
157
  type: CreditTransactionType;
158
- /** Why Billow posted it, as a system code (`api_grant`, `grant_expired`, ...); never free text. */
158
+ /**
159
+ * Why Billow posted it, as a system code (`api_grant`, `grant_expired`, `subscription_period`,
160
+ * `subscription_upgrade`, `subscription_canceled`, `subscription_charged_back`, ...); never free
161
+ * text.
162
+ */
159
163
  reason: string | null;
160
164
  /** How it moved the account's unheld credits (signed). */
161
165
  availableDelta: string;
@@ -399,8 +403,18 @@ interface CreditIncludedWindow {
399
403
  blockedCause: "capacity" | "error" | null;
400
404
  /** For a `pending` or `blocked` window: since when it has been due. */
401
405
  dueSince: string | null;
406
+ /** When an `ended` window ended early; null otherwise. */
402
407
  endedAt: string | null;
408
+ /**
409
+ * Why an `ended` window ended early: `canceled` (the subscription was canceled immediately) or
410
+ * `charged_back` (the payment for its period was lost to a chargeback); null otherwise.
411
+ */
403
412
  endCause: "canceled" | "charged_back" | null;
413
+ /**
414
+ * When a plan upgrade inside the period last raised what the window includes (or added the
415
+ * window, to a period that had none); null for a window as its period was granted.
416
+ */
417
+ raisedAt: string | null;
404
418
  createdAt: string;
405
419
  }
406
420
  /**
@@ -155,7 +155,11 @@ type CreditTransactionType = "grant" | "hold" | "release" | "consume" | "expire"
155
155
  interface CreditLedgerEntry {
156
156
  id: string;
157
157
  type: CreditTransactionType;
158
- /** Why Billow posted it, as a system code (`api_grant`, `grant_expired`, ...); never free text. */
158
+ /**
159
+ * Why Billow posted it, as a system code (`api_grant`, `grant_expired`, `subscription_period`,
160
+ * `subscription_upgrade`, `subscription_canceled`, `subscription_charged_back`, ...); never free
161
+ * text.
162
+ */
159
163
  reason: string | null;
160
164
  /** How it moved the account's unheld credits (signed). */
161
165
  availableDelta: string;
@@ -399,8 +403,18 @@ interface CreditIncludedWindow {
399
403
  blockedCause: "capacity" | "error" | null;
400
404
  /** For a `pending` or `blocked` window: since when it has been due. */
401
405
  dueSince: string | null;
406
+ /** When an `ended` window ended early; null otherwise. */
402
407
  endedAt: string | null;
408
+ /**
409
+ * Why an `ended` window ended early: `canceled` (the subscription was canceled immediately) or
410
+ * `charged_back` (the payment for its period was lost to a chargeback); null otherwise.
411
+ */
403
412
  endCause: "canceled" | "charged_back" | null;
413
+ /**
414
+ * When a plan upgrade inside the period last raised what the window includes (or added the
415
+ * window, to a period that had none); null for a window as its period was granted.
416
+ */
417
+ raisedAt: string | null;
404
418
  createdAt: string;
405
419
  }
406
420
  /**
package/dist/index.cjs CHANGED
@@ -36,19 +36,26 @@ var BillowApiError = class extends Error {
36
36
  code;
37
37
  /** Structured error context from the server (e.g. per-field validation issues), when present. */
38
38
  details;
39
- /** billow's per-request id (`x-request-id`) — quote it in a bug report to trace the server log. */
39
+ /** billow's per-request id (`x-request-id`) - quote it in a bug report to trace the server log. */
40
40
  requestId;
41
- constructor(status, code, message, details, requestId) {
41
+ /**
42
+ * How many seconds to wait before trying again, from the response's `Retry-After` header (sent
43
+ * with a `429 rate_limited`); undefined when the response did not say.
44
+ */
45
+ retryAfterSeconds;
46
+ constructor(status, code, message, details, requestId, retryAfterSeconds) {
42
47
  super(message);
43
48
  this.name = "BillowApiError";
44
49
  this.status = status;
45
50
  this.code = code;
46
51
  this.details = details;
47
52
  this.requestId = requestId;
53
+ this.retryAfterSeconds = retryAfterSeconds;
48
54
  }
49
55
  };
50
56
  var DEFAULT_TIMEOUT_MS = 6e4;
51
57
  var DEFAULT_RETRY_BACKOFF_MS = 500;
58
+ var MAX_RETRY_AFTER_SECONDS = 3600;
52
59
  function makeContext(bearer, opts) {
53
60
  return {
54
61
  fetch: opts.fetch ?? fetch,
@@ -67,7 +74,7 @@ function isRetryable(status, safeToRepeat2) {
67
74
  function backoffMs(base, attempt, retryAfter) {
68
75
  if (retryAfter) {
69
76
  const secs = Number(retryAfter);
70
- if (Number.isFinite(secs) && secs >= 0) return secs * 1e3;
77
+ if (Number.isFinite(secs) && secs >= 0) return Math.min(secs, MAX_RETRY_AFTER_SECONDS) * 1e3;
71
78
  }
72
79
  return base * 2 ** attempt;
73
80
  }
@@ -189,9 +196,25 @@ function errorFrom(res, data) {
189
196
  err.code ?? "error",
190
197
  err.message ?? String(res.status),
191
198
  err.details,
192
- requestIdOf(res)
199
+ requestIdOf(res),
200
+ retryAfterOf(res)
193
201
  );
194
202
  }
203
+ var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
204
+ function retryAfterOf(res) {
205
+ const value = res.headers.get("retry-after")?.trim();
206
+ if (!value) return void 0;
207
+ let seconds;
208
+ if (/^\d+$/.test(value)) {
209
+ seconds = Number(value);
210
+ } else if (IMF_FIXDATE.test(value)) {
211
+ seconds = Math.ceil((Date.parse(value) - Date.now()) / 1e3);
212
+ } else {
213
+ return void 0;
214
+ }
215
+ if (!Number.isFinite(seconds)) return void 0;
216
+ return Math.min(Math.max(0, seconds), MAX_RETRY_AFTER_SECONDS);
217
+ }
195
218
  async function apiRequestBinary(ctx, method, path2, options) {
196
219
  const res = await sendWithResilience(
197
220
  ctx,
@@ -1208,7 +1231,7 @@ function createSettingsResource(ctx) {
1208
1231
  },
1209
1232
  /**
1210
1233
  * Usage settlement grace (hours): defers *collection* of a boundary-adjacent priced
1211
- * calendar-meter renewal past the tz month boundary until late usage settles — the billed
1234
+ * calendar-meter renewal past the tz month boundary until late usage settles - the billed
1212
1235
  * windows/amount are unchanged, only the invoice timing shifts. `0` disables it (the default);
1213
1236
  * a no-op for any subscription without a priced calendar meter. Cap 72h.
1214
1237
  */
@@ -1335,7 +1358,7 @@ function createSubscriptionsResource(ctx) {
1335
1358
  void 0,
1336
1359
  options
1337
1360
  ),
1338
- /** Cancel — immediately, or at period end with `{ atPeriodEnd: true }`. */
1361
+ /** Cancel - immediately, or at period end with `{ atPeriodEnd: true }`. */
1339
1362
  cancel: (id, opts) => apiRequest(
1340
1363
  ctx,
1341
1364
  "POST",
@@ -1368,7 +1391,7 @@ function createSubscriptionsResource(ctx) {
1368
1391
  `/v1/subscriptions/${encodeURIComponent(id)}/coupon`,
1369
1392
  { code }
1370
1393
  ),
1371
- /** Change plan — immediate prorated upgrade, or downgrade scheduled for period end. `productId` accepts the product's id or slug. */
1394
+ /** Change plan - immediate prorated upgrade, or downgrade scheduled for period end. `productId` accepts the product's id or slug. */
1372
1395
  changePlan: (id, productId) => apiRequest(ctx, "POST", `/v1/subscriptions/${encodeURIComponent(id)}/change-plan`, { productId }),
1373
1396
  /** Clear a pending downgrade. Duplicate calls are safe. */
1374
1397
  clearScheduledPlanChange: (id) => apiRequest(
@@ -1442,7 +1465,7 @@ function createWebhookEndpointsResource(ctx) {
1442
1465
  ),
1443
1466
  /** Delete an endpoint and its delivery history. */
1444
1467
  delete: (id) => apiRequest(ctx, "DELETE", `/v1/webhook-endpoints/${encodeURIComponent(id)}`),
1445
- /** Rotate the signing secret — the new plaintext is returned once. */
1468
+ /** Rotate the signing secret - the new plaintext is returned once. */
1446
1469
  rotateSecret: (id) => apiRequest(
1447
1470
  ctx,
1448
1471
  "POST",
@@ -1481,7 +1504,7 @@ var Billow = class {
1481
1504
  /** Custom domains for the hosted customer portal (live keys, behind a rollout flag). */
1482
1505
  hostedDomains;
1483
1506
  /**
1484
- * Organization settings (Phase D3, F) — the configurable dunning schedule, usage settlement grace,
1507
+ * Organization settings (Phase D3, F) - the configurable dunning schedule, usage settlement grace,
1485
1508
  * and the cross-currency reporting currency + FX-rate registry (ADR-0013).
1486
1509
  */
1487
1510
  settings;
@@ -1509,7 +1532,7 @@ var Billow = class {
1509
1532
  coupons = {
1510
1533
  /** Define a coupon (a reusable discount template). */
1511
1534
  create: (input) => this.#request("POST", "/v1/coupons", input),
1512
- /** List coupons (secret key only — enumerates live promo codes). Auto-paginating. */
1535
+ /** List coupons (secret key only - enumerates live promo codes). Auto-paginating. */
1513
1536
  list: (params = {}, options) => makeListPromise(
1514
1537
  (p) => this.#request("GET", `/v1/coupons${toQuery(p)}`, void 0, options),
1515
1538
  params
@@ -1517,13 +1540,13 @@ var Billow = class {
1517
1540
  /** Deactivate a coupon (existing discounts keep running). */
1518
1541
  deactivate: (id) => this.#request("POST", `/v1/coupons/${encodeURIComponent(id)}/deactivate`)
1519
1542
  };
1520
- /** Manage developer webhook endpoints — register, rotate secrets, inspect deliveries.
1543
+ /** Manage developer webhook endpoints - register, rotate secrets, inspect deliveries.
1521
1544
  * (To VERIFY incoming deliveries, import `constructEvent` from `@usebillow/sdk/webhooks`.) */
1522
1545
  features = {
1523
1546
  /** Define a feature (a boolean access gate, or a metered feature with a meter). */
1524
1547
  create: (input) => this.#request("POST", "/v1/features", input),
1525
1548
  /**
1526
- * Edit a feature in place: its `name`, and — for a metered feature — its `meter`
1549
+ * Edit a feature in place: its `name`, and - for a metered feature - its `meter`
1527
1550
  * aggregation. The server refuses a meter change once usage has been recorded (it
1528
1551
  * would rewrite billed history), so set the meter right at create time or before you
1529
1552
  * start tracking. Returns the updated feature.
@@ -1580,7 +1603,7 @@ var Billow = class {
1580
1603
  * choose a credential set and `target` to probe one verify target (Paymob: a
1581
1604
  * method); pass `sampleToken` (SANDBOX ONLY) to run a functional sub-test.
1582
1605
  * This probe hits the provider's live API (mutates nothing here), so it takes a
1583
- * per-call {@link CallOptions} for a timeout / cancellation — `opts` is spread into
1606
+ * per-call {@link CallOptions} for a timeout / cancellation - `opts` is spread into
1584
1607
  * the body, so `options` stays a separate trailing arg (never merged in).
1585
1608
  */
1586
1609
  verify: (provider, opts, options) => this.#request(
@@ -1607,7 +1630,7 @@ var Billow = class {
1607
1630
  )
1608
1631
  }
1609
1632
  };
1610
- /** The merchant's business identity — the seller block on documents and email from-name. */
1633
+ /** The merchant's business identity - the seller block on documents and email from-name. */
1611
1634
  businessProfile = {
1612
1635
  /** The stored profile, or null if one was never saved. */
1613
1636
  get: (options) => this.#request(
@@ -1623,11 +1646,11 @@ var Billow = class {
1623
1646
  };
1624
1647
  /**
1625
1648
  * Hosted customer surfaces (Phase G, ADR-0014). Mint a portal session for one of
1626
- * your signed-in users and redirect them to the returned `url` — the self-serve
1649
+ * your signed-in users and redirect them to the returned `url` - the self-serve
1627
1650
  * portal, or a `checkout` hand-off for `productId`. The Customer's own calls go
1628
1651
  * through {@link BillowPortal}, constructed with the session token.
1629
1652
  */
1630
- /** Identity of this key's tenant — org, environment, configured currencies. */
1653
+ /** Identity of this key's tenant - org, environment, configured currencies. */
1631
1654
  me(options) {
1632
1655
  return this.#request("GET", "/v1/me", void 0, options);
1633
1656
  }
@@ -1647,14 +1670,14 @@ var Billow = class {
1647
1670
  // ── The headline verbs ──────────────────────────────────────────────
1648
1671
  /**
1649
1672
  * Gate access to a feature. `featureId` is the feature slug. Returns
1650
- * `{ allowed, balance }` — `balance` is `null` for unlimited/boolean features.
1673
+ * `{ allowed, balance }` - `balance` is `null` for unlimited/boolean features.
1651
1674
  */
1652
1675
  check(input, options) {
1653
1676
  return this.#request("POST", "/v1/check", input, options);
1654
1677
  }
1655
1678
  /**
1656
1679
  * Record usage of a metered feature (`value` defaults to 1; negative credits
1657
- * back). Pass `idempotencyKey` to make a retried call a no-op — which also makes the call
1680
+ * back). Pass `idempotencyKey` to make a retried call a no-op - which also makes the call
1658
1681
  * safe to retry automatically when `maxRetries` is set.
1659
1682
  */
1660
1683
  track(input, options) {
@@ -1691,7 +1714,7 @@ var BillowPublishable = class {
1691
1714
  this.#ctx = makeContext(publishableKey, opts);
1692
1715
  }
1693
1716
  products = {
1694
- /** The active catalog — products and their prices, for a pricing table. */
1717
+ /** The active catalog - products and their prices, for a pricing table. */
1695
1718
  list: (options) => apiRequest(
1696
1719
  this.#ctx,
1697
1720
  "GET",
@@ -1715,7 +1738,7 @@ var BillowPortal = class {
1715
1738
  if (!sessionToken) throw new Error("billow: a portal session token is required");
1716
1739
  this.#ctx = makeContext(sessionToken, opts);
1717
1740
  }
1718
- /** The portal shell: flow, return URL, merchant brand, and customer identity —
1741
+ /** The portal shell: flow, return URL, merchant brand, and customer identity -
1719
1742
  * the lightweight payload the hosting app frames every page with, and the
1720
1743
  * validate-and-route check at login. */
1721
1744
  session(options) {