Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# error-codes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`error-codes` exports the canonical stable error-code registry used by every other module in `@suaveplan/test-vite`. The registry is built with `@suaveplan/error`'s `defineCodes` so each code value becomes a namespaced string of the form `TEST_VITE.<REASON>`. This namespace survives module boundaries, structured cloning, RPC boundaries, and JSON serialisation, so callers can discriminate on `error.code` rather than relying on prototype chains. The companion `TestViteError` class extends `SuaveplanError<TestViteCode>` so static analysis narrows `err.code` to the literal union of registered codes.
## Features
- 8-code namespaced registry: `INVALID_OPTIONS`, `MISSING_ENTRY`, `TREE_SHAKE_VIOLATION`, `SIZE_BUDGET_EXCEEDED`, `SNAPSHOT_MISSING`, `PORT_EXHAUSTED`, `PORT_NOT_HELD`, `HMR_TIMEOUT`.
- `TestViteError` class extending `SuaveplanError<TestViteCode>` with a public constructor.
- Structured `details` payload propagated as the `SuaveplanError` `context` field.
- Cause-chain support via the standard `Error.cause` constructor option.
- No marketing language — each code names a concrete, reachable failure mode.
## Usage: Basic
```ts
import { TEST_VITE_CODES, TestViteError } from "@suaveplan/test-vite/error-codes";
try {
doSomething();
} catch (err) {
if (err instanceof TestViteError && err.code === TEST_VITE_CODES.INVALID_OPTIONS) {
console.error("bad options", err.details);
}
}
```
## Usage: Advanced
Discriminate on the namespaced code value directly so the handler survives RPC and serialisation:
```ts
import { TEST_VITE_CODES } from "@suaveplan/test-vite/error-codes";
function isTestViteError(err: unknown): err is { code: string; details: Record<string, unknown> } {
return typeof err === "object" && err !== null && typeof (err as { code?: unknown }).code === "string";
}
function classify(err: unknown): "retry" | "abort" {
if (!isTestViteError(err)) return "abort";
switch (err.code) {
case TEST_VITE_CODES.PORT_EXHAUSTED:
case TEST_VITE_CODES.HMR_TIMEOUT:
return "retry";
default:
return "abort";
}
}
```
## API Reference
- `TEST_VITE_CODES` — `Readonly<Record<string, TestViteCode>>` built via `defineCodes("TEST_VITE", [...])`.
- `TestViteCode` — string literal union of every registered code.
- `TestViteError extends SuaveplanError<TestViteCode>` — `new TestViteError(code, message, { cause?, details? })`. Exposes `code: TestViteCode`, `message: string`, `details: Record<string, unknown>`, and inherits `timestamp`, `name`, `cause` from `SuaveplanError` / `Error`.
## Implementation Notes
The constructor passes the `details` payload to `SuaveplanError` as `context` so the OTel span-event adapter picks it up automatically. We mirror the payload on a public `details` field for ergonomic access from non-TypeScript consumers. `cause` is forwarded only when supplied so the standard `Error.prototype.cause` behaviour stays clean.

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { TEST_VITE_CODES, TestViteError } from "./error-codes.js";
describe("TEST_VITE_CODES", () => {
it("every code is namespaced under TEST_VITE.", { timeout: 5000 }, () => {
for (const code of Object.values(TEST_VITE_CODES)) {
expect(code.startsWith("TEST_VITE.")).toBe(true);
}
});
it("TestViteError carries supplied code and details", {
timeout: 5000,
}, () => {
const err = new TestViteError(TEST_VITE_CODES.INVALID_OPTIONS, "bad", {
details: { foo: 1 },
});
expect(err.code).toBe(TEST_VITE_CODES.INVALID_OPTIONS);
expect(err.details["foo"]).toBe(1);
expect(err.name).toBe("TestViteError");
expect(err.message).toBe("bad");
});
it("TestViteError defaults details to empty object", {
timeout: 5000,
}, () => {
const err = new TestViteError(
TEST_VITE_CODES.PORT_EXHAUSTED,
"no ports"
);
expect(err.details).toEqual({});
});
it("TestViteError accepts cause", { timeout: 5000 }, () => {
const cause = new Error("upstream");
const err = new TestViteError(
TEST_VITE_CODES.MISSING_ENTRY,
"no file",
{ cause }
);
expect(err.cause).toBe(cause);
});
});

View File

@@ -0,0 +1,51 @@
import { defineCodes, SuaveplanError } from "@suaveplan/error";
/**
* Stable error-code registry for `@suaveplan/test-vite`.
*
* Codes follow `TEST_VITE.<REASON>` and are produced by
* `@suaveplan/error`'s `defineCodes` so the namespace survives module
* boundaries and runtime serialisation. Consumers SHOULD discriminate on
* `error.code` rather than `instanceof` so the discriminator works across
* worker, RPC, and serialised-error boundaries.
*/
export const TEST_VITE_CODES = defineCodes("TEST_VITE", [
"INVALID_OPTIONS",
"MISSING_ENTRY",
"TREE_SHAKE_VIOLATION",
"SIZE_BUDGET_EXCEEDED",
"SNAPSHOT_MISSING",
"PORT_EXHAUSTED",
"PORT_NOT_HELD",
"HMR_TIMEOUT",
] as const);
export type TestViteCode =
(typeof TEST_VITE_CODES)[keyof typeof TEST_VITE_CODES];
/**
* Typed error class for every `@suaveplan/test-vite` failure path.
*
* Carries the registered code, a human-readable message, and an optional
* structured `details` payload through the `SuaveplanError` `context`
* field so callers can drive diagnostics without re-parsing the message.
*/
export class TestViteError extends SuaveplanError<TestViteCode> {
public readonly details: Record<string, unknown>;
public constructor(
code: TestViteCode,
message: string,
options?: {
cause?: unknown;
details?: Record<string, unknown>;
}
) {
super(code, message, {
...(options?.cause === undefined ? {} : { cause: options.cause }),
context: options?.details ?? {},
});
this.name = "TestViteError";
this.details = options?.details ?? {};
}
}

View File

@@ -0,0 +1,2 @@
export type { TestViteCode } from "./error-codes.js";
export { TEST_VITE_CODES, TestViteError } from "./error-codes.js";