first commit
This commit is contained in:
79
docs/devtools/tooling/installer/src/error-codes.md
Normal file
79
docs/devtools/tooling/installer/src/error-codes.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# `error-codes`
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the canonical error-code registry for `@suaveplan/installer` and the
|
||||
`InstallerError` class that carries those codes. Every failure path inside
|
||||
the host — manifest validation, plugin discovery, prompt validation,
|
||||
plan validation, conflict resolution, apply, and rollback — surfaces as an
|
||||
`InstallerError` with a literal-typed `code` drawn from this registry. That
|
||||
contract makes failures machine-classifiable: callers can write exhaustive
|
||||
`switch` statements on `code` and add new codes only by extending the
|
||||
registry, never by string-matching free-form messages.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen registry built via `defineCodes("INSTALLER", [...])` from
|
||||
`@suaveplan/error`, so `Object.isFrozen(INSTALLER_CODES)` is `true` at
|
||||
module load time.
|
||||
- All codes are namespaced as `INSTALLER.<REASON>` strings.
|
||||
- `InstallerError` extends `SuaveplanError<InstallerErrorCode>` so
|
||||
`instanceof` plus `.code` narrowing remains exhaustive.
|
||||
- Public widened constructor — callers can `throw new InstallerError(code, key, options)` directly without going through a factory.
|
||||
- Stable code surface: nine reasons (`PLUGIN_NOT_FOUND`, `PLUGIN_LOAD_FAILED`,
|
||||
`PLUGIN_INVALID_MANIFEST`, `PROMPT_VALIDATION_FAILED`, `PLAN_FAILED`,
|
||||
`APPLY_CONFLICT`, `APPLY_FAILED`, `ROLLBACK_FAILED`, `ABORTED`).
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { INSTALLER_CODES, InstallerError } from "@suaveplan/installer";
|
||||
|
||||
throw new InstallerError(
|
||||
INSTALLER_CODES.PLUGIN_NOT_FOUND,
|
||||
"installer.plugin.not_found",
|
||||
{ context: { id: "agent-skills" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { InstallerError } from "@suaveplan/installer";
|
||||
|
||||
function classify(err: unknown): string {
|
||||
if (!(err instanceof InstallerError)) return "unknown";
|
||||
switch (err.code) {
|
||||
case "INSTALLER.PLUGIN_NOT_FOUND":
|
||||
return "missing-plugin";
|
||||
case "INSTALLER.APPLY_CONFLICT":
|
||||
return "conflict";
|
||||
case "INSTALLER.ROLLBACK_FAILED":
|
||||
return "fatal-rollback";
|
||||
default:
|
||||
return "other";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `INSTALLER_CODES` — frozen `{ [K]: "INSTALLER.K" }` map. Read-only at
|
||||
runtime and at the type level.
|
||||
- `type InstallerErrorCode` — union of every `INSTALLER.*` literal in the
|
||||
registry.
|
||||
- `class InstallerError extends SuaveplanError<InstallerErrorCode>` —
|
||||
carries `code`, `message`, optional `cause`, `context`, `timestamp`, and
|
||||
`traceId` from the base class.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` freezes the returned object so consumers cannot mutate the
|
||||
registry — this matches the conformance assertion in
|
||||
`error-codes.spec-conformance.test.ts` (`INSTALLER-09`). The
|
||||
`InstallerError` constructor sets `this.name = "InstallerError"` because
|
||||
`SuaveplanError`'s default `new.target.name` is reset by some bundlers and
|
||||
the explicit assignment guarantees stable serialised names. Every throw
|
||||
inside this package should pick a code from this registry; throwing a bare
|
||||
`Error` from internal code is treated as a regression by the conformance
|
||||
suite.
|
||||
Reference in New Issue
Block a user