v0.3.0 — entitlements catalog in PublicPoliciesResponse

Mirrors the Keysat migration 0014 server-side change. The product
object on listPublicPolicies() now includes entitlementsCatalog —
the closed list of {slug, name, description} the operator declared
on the product. SDK consumers' in-app tier pickers can render the
human-readable name ("AI summaries") with the description as a
tooltip instead of the raw slug ("ai_summaries").

New exports:
- EntitlementDef type
- PublicPoliciesResponse.product.entitlementsCatalog: EntitlementDef[]

Empty array on legacy products that don't have a catalog yet —
SDK consumers fall back to rendering the raw policy.entitlements
slugs directly. No breaking change to existing consumers.

Bumps to 0.3.0 minor since the surface is purely additive.
15/15 crosscheck tests still pass.
This commit is contained in:
Keysat
2026-05-10 07:58:44 -05:00
parent c3a57a043e
commit 6822cb01ea
3 changed files with 32 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@keysat/licensing-client",
"version": "0.2.0",
"version": "0.3.0",
"description": "Client library for Keysat. Verifies signed license keys offline and wraps the HTTP API for purchase and revocation checks.",
"type": "module",
"main": "./dist/index.cjs",
+1
View File
@@ -16,6 +16,7 @@ export {
type StartPurchaseOptions,
type PublicPolicy,
type PublicPoliciesResponse,
type EntitlementDef,
type RedeemFreeOptions,
type RedeemFreeResponse,
} from './online.js'
+30
View File
@@ -143,12 +143,35 @@ export interface PublicPolicy {
trialDays: number
}
/**
* One entry in a product's entitlements catalog. Defined by the
* operator in admin (Keysat migration 0014). Use
* {@link EntitlementDef.name} as the human-readable label when
* rendering an in-app tier picker — e.g. "AI summaries" instead
* of the raw `ai_summaries` slug. {@link EntitlementDef.description}
* is a one-sentence tooltip; may be empty.
*/
export interface EntitlementDef {
slug: string
name: string
description: string
}
export interface PublicPoliciesResponse {
product: {
slug: string
name: string
description: string
basePriceSats: number
/**
* Closed list of entitlements this product offers, with display
* names + descriptions for buyer-facing rendering. Empty array
* when the operator hasn't defined a catalog (legacy "free-text"
* mode — render the raw slugs from {@link PublicPolicy.entitlements}
* directly, or replace underscores with spaces for a quick
* fallback).
*/
entitlementsCatalog: EntitlementDef[]
}
policies: PublicPolicy[]
}
@@ -296,6 +319,13 @@ export class Client {
name: product.name as string,
description: (product.description as string) ?? '',
basePriceSats: product.base_price_sats as number,
entitlementsCatalog: (
(product.entitlements_catalog as Array<Record<string, unknown>>) ?? []
).map((c) => ({
slug: c.slug as string,
name: (c.name as string) ?? (c.slug as string),
description: (c.description as string) ?? '',
})),
},
policies: policies.map((p) => ({
slug: p.slug as string,