Files
skills/docs/security-stack/security/license/src/error-codes.md
2026-05-29 08:20:56 +01:00

100 lines
3.2 KiB
Markdown

# `@suaveplan/license` — error-codes
## Purpose
This module owns the stable error-code registry for `@suaveplan/license`. It
exposes `LICENSE_CODES` (a frozen `defineCodes("LICENSE", [...])` record),
the `LicenseErrorCode` type union, and the `LicenseError` class. Every
public failure path in the package raises a `LicenseError` carrying one of
these twelve namespaced `LICENSE.*` codes, so consumers can dispatch on the
literal string or `instanceof LicenseError` and let TypeScript narrow the
union exhaustively.
## Features
- 12 codes covering input validation, encoding / parsing, cryptographic
failures, lifecycle (expired / revoked), manager state, feature-mask
bounds, and `AbortSignal` cancellation.
- `LicenseError` extends `SuaveplanError<LicenseErrorCode>` so the
generic `code` type narrows under `instanceof`.
- Constructor widened to `public` so all modules can construct errors
directly without trampolines.
- Codes are namespaced `LICENSE.*` per the suaveplan-wide pattern (see
`@suaveplan/crypto-keys`'s `CHAIN_KEYS.*`), making programmatic dispatch
consistent across packages.
## Usage: Basic
```ts
import { LicenseError, LICENSE_CODES } from "@suaveplan/license/error-codes/error-codes";
throw new LicenseError(
LICENSE_CODES.INVALID_ENCODING,
"Invalid Base32 character: !"
);
```
## Usage: Advanced
Attach a Zod `cause` and structured `context` for an input-validation
boundary failure:
```ts
import { LicenseError, LICENSE_CODES } from "@suaveplan/license/error-codes/error-codes";
import { LicensePayloadSchema } from "@suaveplan/license/schemas/schemas";
try {
LicensePayloadSchema.parse(input);
} catch (zodError) {
throw new LicenseError(
LICENSE_CODES.INVALID_INPUT,
"License payload failed schema validation",
{ cause: zodError, context: { source: "generateLicense" } }
);
}
```
Consumer-side narrowing:
```ts
import { LicenseError } from "@suaveplan/license";
try {
await manager.generate({ productId: 1 });
} catch (error) {
if (error instanceof LicenseError && error.code === "LICENSE.NO_SIGNING_KEY") {
// recover or re-prompt
} else {
throw error;
}
}
```
## API Reference
### `LICENSE_CODES`
Frozen `Readonly<Record<Reason, "LICENSE.<Reason>">>` where `Reason` is
one of the 12 keys: `INVALID_INPUT`, `INVALID_ENCODING`, `MALFORMED`,
`UNSUPPORTED_VERSION`, `UNKNOWN_SIGNING_KEY`, `INVALID_SIGNATURE`,
`EXPIRED`, `REVOKED`, `NO_SIGNING_KEY`, `FEATURE_INDEX_OUT_OF_RANGE`,
`PAYLOAD_OUT_OF_RANGE`, `ABORTED`.
### `type LicenseErrorCode`
String-literal union of all 12 namespaced codes.
### `class LicenseError extends SuaveplanError<LicenseErrorCode>`
Concrete error class. Constructor: `(code, message, options?)` where
`options` is `SuaveplanErrorOptions` (carries optional `cause`,
`context`, `traceId`, `clock`).
## Implementation Notes
The registry uses `defineCodes` from `@suaveplan/error` so the type of
`LICENSE_CODES.INVALID_ENCODING` is the literal `"LICENSE.INVALID_ENCODING"`
(not the broad `string`), giving exhaustiveness checking in `switch`
statements. The list is declared `as const` so TypeScript captures the
tuple shape and the helper's mapped-type return picks every key.