@adhd/apigen-base-errors 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @adhd/apigen-base-errors
2
+
3
+ Canonical error model for apigen — one set of error codes mapped to each transport's status
4
+ convention (HTTP, gRPC, CLI exit, MCP), plus streaming-error phase helpers. Pure TypeScript
5
+ (**platform: shared**).
6
+
7
+ Part of [apigen](../README.md).
8
+
9
+ ## Public API
10
+
11
+ ```ts
12
+ import { ERROR_CODES, HTTP_STATUS, GRPC_CODE, CLI_EXIT_CODE, MCP_ERROR_KIND, statusMaps, ApiError, toStreamingError, isBeforeFirstChunk, isAfterFirstChunk } from '@adhd/apigen-base-errors';
13
+ ```
14
+
15
+ - **`ERROR_CODES` / `ApiErrorCode`** — the canonical code set.
16
+ - **`HTTP_STATUS` / `GRPC_CODE` / `CLI_EXIT_CODE` / `MCP_ERROR_KIND` / `statusMaps`** —
17
+ per-transport mappings so every surface reports the same error consistently.
18
+ - **`ApiError`** — the error carrier; the **streaming helpers** classify before/after-first-chunk
19
+ errors for correct mid-stream reporting.
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { ERROR_CODES, type ApiErrorCode, HTTP_STATUS, GRPC_CODE, CLI_EXIT_CODE, MCP_ERROR_KIND, statusMaps, ApiError, isApiError, type StreamingPhase, type BeforeFirstChunkError, type AfterFirstChunkError, type StreamingErrorCarrier, toStreamingError, isBeforeFirstChunk, isAfterFirstChunk, } from './lib/errors';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=["invalid_argument","unauthenticated","permission_denied","not_found","internal"],i={invalid_argument:400,unauthenticated:401,permission_denied:403,not_found:404,internal:500},o={invalid_argument:"INVALID_ARGUMENT",unauthenticated:"UNAUTHENTICATED",permission_denied:"PERMISSION_DENIED",not_found:"NOT_FOUND",internal:"INTERNAL"},s={invalid_argument:2,unauthenticated:3,permission_denied:3,not_found:4,internal:1},u={invalid_argument:"error",unauthenticated:"error",permission_denied:"error",not_found:"error",internal:"error"},d={http:i,grpc:o,cli:s,mcp:u};class c extends Error{constructor(n,e,a){super(e),this.name="ApiError",this.code=n,this.details=a,Object.setPrototypeOf(this,new.target.prototype)}toJSON(){const n={code:this.code,message:this.message};return this.details!==void 0&&(n.details=this.details),n}}function _(t){return typeof t=="object"&&t!==null&&t.name==="ApiError"&&r.includes(t.code)&&typeof t.toJSON=="function"}function E(t,n,e=0){return t==="before-first-chunk"?{phase:t,error:n}:{phase:t,error:n,chunksDelivered:e}}function f(t){return t.phase==="before-first-chunk"}function p(t){return t.phase==="after-first-chunk"}exports.ApiError=c;exports.CLI_EXIT_CODE=s;exports.ERROR_CODES=r;exports.GRPC_CODE=o;exports.HTTP_STATUS=i;exports.MCP_ERROR_KIND=u;exports.isAfterFirstChunk=p;exports.isApiError=_;exports.isBeforeFirstChunk=f;exports.statusMaps=d;exports.toStreamingError=E;
package/index.mjs ADDED
@@ -0,0 +1,74 @@
1
+ const r = [
2
+ "invalid_argument",
3
+ "unauthenticated",
4
+ "permission_denied",
5
+ "not_found",
6
+ "internal"
7
+ ], o = {
8
+ invalid_argument: 400,
9
+ unauthenticated: 401,
10
+ permission_denied: 403,
11
+ not_found: 404,
12
+ internal: 500
13
+ }, s = {
14
+ invalid_argument: "INVALID_ARGUMENT",
15
+ unauthenticated: "UNAUTHENTICATED",
16
+ permission_denied: "PERMISSION_DENIED",
17
+ not_found: "NOT_FOUND",
18
+ internal: "INTERNAL"
19
+ }, u = {
20
+ invalid_argument: 2,
21
+ unauthenticated: 3,
22
+ permission_denied: 3,
23
+ not_found: 4,
24
+ internal: 1
25
+ }, a = {
26
+ invalid_argument: "error",
27
+ unauthenticated: "error",
28
+ permission_denied: "error",
29
+ not_found: "error",
30
+ internal: "error"
31
+ }, d = {
32
+ http: o,
33
+ grpc: s,
34
+ cli: u,
35
+ mcp: a
36
+ };
37
+ class c extends Error {
38
+ constructor(n, e, i) {
39
+ super(e), this.name = "ApiError", this.code = n, this.details = i, Object.setPrototypeOf(this, new.target.prototype);
40
+ }
41
+ /** Serialise to a plain object suitable for JSON transport. */
42
+ toJSON() {
43
+ const n = {
44
+ code: this.code,
45
+ message: this.message
46
+ };
47
+ return this.details !== void 0 && (n.details = this.details), n;
48
+ }
49
+ }
50
+ function _(t) {
51
+ return typeof t == "object" && t !== null && t.name === "ApiError" && r.includes(t.code) && typeof t.toJSON == "function";
52
+ }
53
+ function f(t, n, e = 0) {
54
+ return t === "before-first-chunk" ? { phase: t, error: n } : { phase: t, error: n, chunksDelivered: e };
55
+ }
56
+ function p(t) {
57
+ return t.phase === "before-first-chunk";
58
+ }
59
+ function h(t) {
60
+ return t.phase === "after-first-chunk";
61
+ }
62
+ export {
63
+ c as ApiError,
64
+ u as CLI_EXIT_CODE,
65
+ r as ERROR_CODES,
66
+ s as GRPC_CODE,
67
+ o as HTTP_STATUS,
68
+ a as MCP_ERROR_KIND,
69
+ h as isAfterFirstChunk,
70
+ _ as isApiError,
71
+ p as isBeforeFirstChunk,
72
+ d as statusMaps,
73
+ f as toStreamingError
74
+ };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @adhd/apigen-base-errors
3
+ *
4
+ * Canonical error taxonomy for apigen — §9.1 of the apigen SPEC.
5
+ *
6
+ * Exports:
7
+ * - `ApiErrorCode` — the gRPC-style canonical code set (string union + const object)
8
+ * - `ApiError` — the thrown error class carrying code + message + details
9
+ * - `HTTP_STATUS` — code → HTTP status code map
10
+ * - `GRPC_CODE` — code → gRPC status code name map
11
+ * - `CLI_EXIT_CODE` — code → CLI process exit code map
12
+ * - `MCP_ERROR_KIND` — code → MCP error shape indicator ('error')
13
+ * - `statusMaps` — convenience bundle of all four maps
14
+ * - `StreamingPhase` — 'before-first-chunk' | 'after-first-chunk' discriminant
15
+ * - `StreamingErrorCarrier` — discriminated union: how an in-flight stream delivers a terminal error
16
+ * - `toStreamingError` — factory that wraps an ApiError into the correct carrier for the phase
17
+ */
18
+ /** The five gRPC-style canonical error codes recognised by apigen. */
19
+ export declare const ERROR_CODES: readonly ["invalid_argument", "unauthenticated", "permission_denied", "not_found", "internal"];
20
+ /** String-union type for the canonical error codes. */
21
+ export type ApiErrorCode = (typeof ERROR_CODES)[number];
22
+ /** Maps each canonical code to its HTTP status code. */
23
+ export declare const HTTP_STATUS: Record<ApiErrorCode, number>;
24
+ /** Maps each canonical code to its gRPC status name. */
25
+ export declare const GRPC_CODE: Record<ApiErrorCode, string>;
26
+ /** Maps each canonical code to its CLI process exit code. */
27
+ export declare const CLI_EXIT_CODE: Record<ApiErrorCode, number>;
28
+ /**
29
+ * MCP surfaces all apigen errors as the MCP `error` result kind.
30
+ * Maps each canonical code to the MCP error shape indicator.
31
+ */
32
+ export declare const MCP_ERROR_KIND: Record<ApiErrorCode, 'error'>;
33
+ /** Convenience bundle: all four transport maps keyed by transport name. */
34
+ export declare const statusMaps: {
35
+ readonly http: Record<"invalid_argument" | "unauthenticated" | "permission_denied" | "not_found" | "internal", number>;
36
+ readonly grpc: Record<"invalid_argument" | "unauthenticated" | "permission_denied" | "not_found" | "internal", string>;
37
+ readonly cli: Record<"invalid_argument" | "unauthenticated" | "permission_denied" | "not_found" | "internal", number>;
38
+ readonly mcp: Record<"invalid_argument" | "unauthenticated" | "permission_denied" | "not_found" | "internal", "error">;
39
+ };
40
+ /**
41
+ * The canonical apigen error. Every transport adapter catches this and maps
42
+ * `code` to the native status using the tables above.
43
+ */
44
+ export declare class ApiError extends Error {
45
+ /** gRPC-style canonical error code. */
46
+ readonly code: ApiErrorCode;
47
+ /** Optional structured details (passed through to MCP / HTTP body). */
48
+ readonly details?: unknown;
49
+ constructor(code: ApiErrorCode, message: string, details?: unknown);
50
+ /** Serialise to a plain object suitable for JSON transport. */
51
+ toJSON(): {
52
+ code: ApiErrorCode;
53
+ message: string;
54
+ details?: unknown;
55
+ };
56
+ }
57
+ /**
58
+ * Structural (duck-typed) `ApiError` check — deliberately NOT `instanceof
59
+ * ApiError`.
60
+ *
61
+ * `apigen-engine-runtime` (and every other `platform:node`/`platform:shared`
62
+ * `@adhd/*` package built via the shared vite config, see
63
+ * `tools/vite-plugins/externalize.mjs`) BUNDLES its `@adhd/*` dependencies —
64
+ * including this package — straight into its own `dist` output instead of
65
+ * externalizing them, so `ApiError` gets re-compiled as a private, separately
66
+ * identified class inside every consumer's bundle. `err instanceof ApiError`
67
+ * only succeeds when the checking code and the throwing code share the exact
68
+ * same loaded copy of this class; the moment one side loads a *different*
69
+ * bundle of `@adhd/apigen-base-errors` than the other (e.g. a transport
70
+ * adapter importing this package directly while the error was thrown from
71
+ * inside another package's bundled copy of `makeValidateLayer` — this is
72
+ * exactly what happens once real `node_modules` linking, e.g. under pnpm,
73
+ * makes a package resolve to its pre-built `dist` instead of source), the
74
+ * classes are structurally identical but referentially distinct and
75
+ * `instanceof` silently returns `false`. See BACKLOG.md
76
+ * `BUG-APIGEN-PLUGIN-IN-PROCESS-VALIDATE-500-001` (validate-layer 500-vs-400
77
+ * misclassification) and `BUG-APIGEN-STREAM-ERROR-CODE-MISCLASSIFY-001`
78
+ * (mcp-plugin stream error code misclassification) — same root cause,
79
+ * independently confirmed from two different call sites.
80
+ *
81
+ * This guard is immune to that hazard: it only inspects the plain,
82
+ * cross-realm-safe properties every `ApiError` instance carries (`name`,
83
+ * `code`, `toJSON`), which survive bundling/inlining identically regardless
84
+ * of which physical class definition constructed the object.
85
+ */
86
+ export declare function isApiError(err: unknown): err is ApiError;
87
+ /**
88
+ * Discriminant that records whether the first chunk was already flushed.
89
+ * Determines the carrier shape used to deliver a terminal stream error.
90
+ */
91
+ export type StreamingPhase = 'before-first-chunk' | 'after-first-chunk';
92
+ /**
93
+ * Carrier for a terminal streaming error **before** the first chunk was sent.
94
+ * Adapters handle this identically to a normal (non-streaming) §9 error.
95
+ */
96
+ export interface BeforeFirstChunkError {
97
+ readonly phase: 'before-first-chunk';
98
+ /** The underlying canonical error. */
99
+ readonly error: ApiError;
100
+ }
101
+ /**
102
+ * Carrier for a terminal streaming error **after** the first chunk was flushed.
103
+ * The status line is already gone; error must be delivered in-band per transport:
104
+ *
105
+ * | Transport | Mechanism |
106
+ * |------------------|------------------------------------------------------|
107
+ * | HTTP SSE/chunked | terminal `event: error` frame carrying the ApiError |
108
+ * | gRPC | trailing status (native gRPC trailers) |
109
+ * | MCP | progressive error notification |
110
+ * | CLI | flush partial stdout, write ApiError to stderr, exit |
111
+ */
112
+ export interface AfterFirstChunkError {
113
+ readonly phase: 'after-first-chunk';
114
+ /** The underlying canonical error, to be delivered in-band. */
115
+ readonly error: ApiError;
116
+ /**
117
+ * The number of chunks successfully delivered before the error occurred.
118
+ * Informational — lets CLI adapters decide whether stdout has content.
119
+ */
120
+ readonly chunksDelivered: number;
121
+ }
122
+ /** Discriminated union of the two streaming error carrier shapes. */
123
+ export type StreamingErrorCarrier = BeforeFirstChunkError | AfterFirstChunkError;
124
+ /**
125
+ * Factory: wrap an `ApiError` in the appropriate streaming carrier.
126
+ *
127
+ * @param phase - whether the first chunk was already flushed
128
+ * @param error - the terminal error
129
+ * @param chunksDelivered - (after-first-chunk only) chunks sent before failure
130
+ */
131
+ export declare function toStreamingError(phase: 'before-first-chunk', error: ApiError): BeforeFirstChunkError;
132
+ export declare function toStreamingError(phase: 'after-first-chunk', error: ApiError, chunksDelivered: number): AfterFirstChunkError;
133
+ /**
134
+ * Type guard: narrows a `StreamingErrorCarrier` to `BeforeFirstChunkError`.
135
+ */
136
+ export declare function isBeforeFirstChunk(c: StreamingErrorCarrier): c is BeforeFirstChunkError;
137
+ /**
138
+ * Type guard: narrows a `StreamingErrorCarrier` to `AfterFirstChunkError`.
139
+ */
140
+ export declare function isAfterFirstChunk(c: StreamingErrorCarrier): c is AfterFirstChunkError;
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@adhd/apigen-base-errors",
3
+ "version": "0.1.1",
4
+ "main": "./index.js",
5
+ "module": "./index.mjs",
6
+ "typings": "./index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ }
10
+ }