# `@suaveplan/license` — manager _(no submodules)_ ## Purpose High-level `LicenseManager` class that bundles a public-key registry, an optional signing key, default `signingKeyId` / `productId`, injectable `Clock` / `Rng`, and an optional `RevocationStore` into a single object. Most callers will use only the manager — the lower-level `generateLicense` / `validateLicense` functions are exposed for the rare case where the caller wants stateless calls. `LicenseManager.generate` defaults `useFullSignature: false` (compact keys) to match upstream's caller-side default; passing `{ useFullSignature: true }` opts into 64-byte signatures. `revoke` and `isRevoked` reach into the configured `RevocationStore` (lazily allocating an in-memory store on first `revoke` call if none was configured). ## Features - 11 methods covering the full lifecycle: `addPublicKey`, `setSigningKey`, `setRevocationStore`, `generate`, `generateTrial`, `validate`, `isValid`, `parse`, `revoke`, `isRevoked` (+ constructor). - Quick-trial helper `generateTrial({ expirationDays, productId?, tier?, features? })` that sets the `TRIAL` flag and uses `RELATIVE_DAYS` time encoding. - Injectable `Clock` is used both as the default `referenceDate` for `validate` and as the time base for `createPayload` absolute-day computations. - Injectable `Rng` flows through to `createPayload` for deterministic nonces. - Optional `RevocationStore` consulted before signature verification. - Throws `LicenseError(LICENSE.NO_SIGNING_KEY)` when `generate` is called without a configured signing key. ## Usage: Basic ```ts import { secp256k1 } from "@suaveplan/crypto-curve"; import { LicenseManager, PackageTier } from "@suaveplan/license"; const s = secp256k1.randomPrivateKey(); const p = secp256k1.getPublicKey(s); const m = new LicenseManager({ signingKey: { s, p }, publicKeys: { 0: { p } }, }); const r = await m.generate({ productId: 5, tier: PackageTier.PRO }); const v = await m.validate(r.key); // { success: true, details: { ... } } ``` ## Usage: Advanced A manager with deterministic test injection, a revocation store, and a multi-key registry: ```ts import { secp256k1 } from "@suaveplan/crypto-curve"; import type { Clock, Rng } from "@suaveplan/types"; import { createMemoryRevocationStore, fingerprintLicense, LicenseManager, } from "@suaveplan/license"; const kp1 = (() => { const s = secp256k1.randomPrivateKey(); return { s, p: secp256k1.getPublicKey(s) }; })(); const kp2 = (() => { const s = secp256k1.randomPrivateKey(); return { s, p: secp256k1.getPublicKey(s) }; })(); const clock: Clock = () => Date.UTC(2026, 5, 17, 12, 0, 0); const rng: Rng = { nextUint32: () => 0xdead, nextFloat: () => 0 }; const store = createMemoryRevocationStore({ maxEntries: 10_000 }); const m = new LicenseManager({ signingKey: { s: kp1.s }, publicKeys: { 0: { p: kp1.p }, 1: { p: kp2.p } }, defaultSigningKeyId: 0, defaultProductId: 5, clock, rng, revocationStore: store, }); const trial = await m.generateTrial({ expirationDays: 14 }); await m.revoke(fingerprintLicense(trial.key)); const v = await m.validate(trial.key); // → { success: false, error: 'REVOKED', message: 'License has been revoked' } ``` ## API Reference ### `class LicenseManager` | Method | Returns | Notes | |---|---|---| | `constructor(config?)` | `LicenseManager` | All `config` fields optional | | `addPublicKey(keyId, publicKey)` | `void` | Idempotent overwrite | | `setSigningKey(signingKey, keyId?)` | `void` | Updates default signing key id when supplied | | `setRevocationStore(store)` | `void` | Replaces an existing store | | `generate(options, genOptions?)` | `Promise` | Throws `NO_SIGNING_KEY` if unconfigured | | `generateTrial(opts)` | `Promise` | Sets `TRIAL` flag, `RELATIVE_DAYS` encoding | | `validate(licenseKey, options?)` | `Promise` | Consults revocation store first | | `isValid(licenseKey)` | `Promise` | Wraps `validate` | | `parse(licenseKey)` | `LicenseParsedResult` | No verification | | `revoke(fingerprint)` | `Promise` | Lazily allocates store if none | | `isRevoked(fingerprint)` | `Promise` | Returns `false` if no store | ### `interface GenerateTrialOptions` `{ expirationDays: number, productId?: number, tier?: PackageTier, features?: number }`. ## Implementation Notes The lazy-allocation behaviour of `revoke` (auto-creates an in-memory store on first call) means simple consumers can use revocation without constructing a store explicitly, while still letting power users plug in a custom backend. The manager's `clock` injection drives BOTH the generation-time `getDayNumber` (for absolute-day expirations) AND the validation-time `referenceDate` (when none is supplied) — keeping the two sides on the same simulated time line for deterministic tests.