@crewhaus/errors 0.1.0

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/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@crewhaus/errors",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Typed error hierarchy shared across factory layers",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts"
10
+ },
11
+ "scripts": {
12
+ "test": "bun test src"
13
+ },
14
+ "license": "Apache-2.0",
15
+ "author": {
16
+ "name": "Max Meier",
17
+ "email": "max@studiomax.io",
18
+ "url": "https://studiomax.io"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/crewhaus/factory.git",
23
+ "directory": "packages/errors"
24
+ },
25
+ "homepage": "https://github.com/crewhaus/factory/tree/main/packages/errors#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/crewhaus/factory/issues"
28
+ },
29
+ "publishConfig": {
30
+ "access": "restricted"
31
+ },
32
+ "files": [
33
+ "src",
34
+ "README.md",
35
+ "LICENSE",
36
+ "NOTICE"
37
+ ]
38
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ CompilerError,
4
+ ConfigError,
5
+ CrewhausError,
6
+ McpConnectionError,
7
+ McpError,
8
+ McpProtocolError,
9
+ RuntimeError,
10
+ SpecParseError,
11
+ } from "./index";
12
+
13
+ describe("CrewhausError", () => {
14
+ test("extends Error and exposes a code", () => {
15
+ const err = new CrewhausError("config", "boom");
16
+ expect(err).toBeInstanceOf(Error);
17
+ expect(err).toBeInstanceOf(CrewhausError);
18
+ expect(err.code).toBe("config");
19
+ expect(err.message).toBe("boom");
20
+ expect(err.name).toBe("CrewhausError");
21
+ });
22
+
23
+ test("toJSON serializes message and code", () => {
24
+ const err = new CrewhausError("runtime", "oops");
25
+ expect(err.toJSON()).toEqual({
26
+ name: "CrewhausError",
27
+ code: "runtime",
28
+ message: "oops",
29
+ cause: undefined,
30
+ });
31
+ });
32
+
33
+ test("preserves cause via Error.cause", () => {
34
+ const root = new Error("root");
35
+ const err = new CompilerError("could not lower", root);
36
+ expect(err.cause).toBe(root);
37
+ });
38
+
39
+ test("toJSON serializes a plain Error cause", () => {
40
+ const root = new Error("root");
41
+ const err = new CompilerError("compile failed", root);
42
+ expect(err.toJSON().cause).toEqual({ name: "Error", message: "root" });
43
+ });
44
+
45
+ test("toJSON recursively serializes a CrewhausError cause", () => {
46
+ const inner = new SpecParseError("bad yaml");
47
+ const outer = new CompilerError("compile failed", inner);
48
+ expect(outer.toJSON().cause).toEqual({
49
+ name: "SpecParseError",
50
+ code: "spec_parse",
51
+ message: "bad yaml",
52
+ cause: undefined,
53
+ });
54
+ });
55
+
56
+ test("toJSON stringifies non-Error causes", () => {
57
+ const err = new RuntimeError("x", "string-cause");
58
+ expect(err.toJSON().cause).toEqual({ message: "string-cause" });
59
+ });
60
+ });
61
+
62
+ describe("subclasses", () => {
63
+ test("each subclass tags itself with the right code", () => {
64
+ expect(new SpecParseError("x").code).toBe("spec_parse");
65
+ expect(new CompilerError("x").code).toBe("compiler");
66
+ expect(new RuntimeError("x").code).toBe("runtime");
67
+ expect(new ConfigError("x").code).toBe("config");
68
+ expect(new McpError("x").code).toBe("mcp");
69
+ expect(new McpConnectionError("x").code).toBe("mcp");
70
+ expect(new McpProtocolError("x").code).toBe("mcp");
71
+ });
72
+
73
+ test("each subclass has a stable name (for instanceof / log filtering)", () => {
74
+ expect(new SpecParseError("x").name).toBe("SpecParseError");
75
+ expect(new CompilerError("x").name).toBe("CompilerError");
76
+ expect(new RuntimeError("x").name).toBe("RuntimeError");
77
+ expect(new ConfigError("x").name).toBe("ConfigError");
78
+ expect(new McpError("x").name).toBe("McpError");
79
+ expect(new McpConnectionError("x").name).toBe("McpConnectionError");
80
+ expect(new McpProtocolError("x").name).toBe("McpProtocolError");
81
+ });
82
+
83
+ test("subclasses are instanceof their parent", () => {
84
+ expect(new SpecParseError("x")).toBeInstanceOf(CrewhausError);
85
+ expect(new CompilerError("x")).toBeInstanceOf(CrewhausError);
86
+ expect(new RuntimeError("x")).toBeInstanceOf(CrewhausError);
87
+ expect(new ConfigError("x")).toBeInstanceOf(CrewhausError);
88
+ expect(new McpError("x")).toBeInstanceOf(CrewhausError);
89
+ expect(new McpConnectionError("x")).toBeInstanceOf(McpError);
90
+ expect(new McpProtocolError("x")).toBeInstanceOf(McpError);
91
+ });
92
+
93
+ test("McpConnectionError serializes via toJSON", () => {
94
+ const err = new McpConnectionError("connection lost", new Error("ECONNREFUSED"));
95
+ expect(err.toJSON()).toEqual({
96
+ name: "McpConnectionError",
97
+ code: "mcp",
98
+ message: "connection lost",
99
+ cause: { name: "Error", message: "ECONNREFUSED" },
100
+ });
101
+ });
102
+ });
package/src/index.ts ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Typed error hierarchy for factory.
3
+ * Catalog F-foundations (`error-types`) — every later layer imports this
4
+ * for structured error reporting instead of throwing raw `Error`s.
5
+ *
6
+ * Each error carries a stable `code` for programmatic dispatch and serializes
7
+ * its full cause chain via `toJSON()` for the logging layer.
8
+ */
9
+
10
+ export type ErrorCode =
11
+ | "spec_parse"
12
+ | "compiler"
13
+ | "runtime"
14
+ | "config"
15
+ | "tool"
16
+ | "mcp"
17
+ | "adapter"
18
+ | "channel";
19
+
20
+ export type SerializedError = {
21
+ name: string;
22
+ code: ErrorCode;
23
+ message: string;
24
+ cause?: SerializedError | { name?: string; message: string };
25
+ };
26
+
27
+ export class CrewhausError extends Error {
28
+ override readonly name: string = "CrewhausError";
29
+ readonly code: ErrorCode;
30
+
31
+ constructor(code: ErrorCode, message: string, cause?: unknown) {
32
+ super(message, cause === undefined ? undefined : { cause });
33
+ this.code = code;
34
+ }
35
+
36
+ toJSON(): SerializedError {
37
+ return {
38
+ name: this.name,
39
+ code: this.code,
40
+ message: this.message,
41
+ cause: serializeCause(this.cause),
42
+ };
43
+ }
44
+ }
45
+
46
+ function serializeCause(cause: unknown): SerializedError["cause"] {
47
+ if (cause === undefined) return undefined;
48
+ if (cause instanceof CrewhausError) return cause.toJSON();
49
+ if (cause instanceof Error) return { name: cause.name, message: cause.message };
50
+ return { message: String(cause) };
51
+ }
52
+
53
+ export class SpecParseError extends CrewhausError {
54
+ override readonly name = "SpecParseError";
55
+ constructor(message: string, cause?: unknown) {
56
+ super("spec_parse", message, cause);
57
+ }
58
+ }
59
+
60
+ export class CompilerError extends CrewhausError {
61
+ override readonly name = "CompilerError";
62
+ constructor(message: string, cause?: unknown) {
63
+ super("compiler", message, cause);
64
+ }
65
+ }
66
+
67
+ export class RuntimeError extends CrewhausError {
68
+ override readonly name = "RuntimeError";
69
+ constructor(message: string, cause?: unknown) {
70
+ super("runtime", message, cause);
71
+ }
72
+ }
73
+
74
+ export class ConfigError extends CrewhausError {
75
+ override readonly name = "ConfigError";
76
+ constructor(message: string, cause?: unknown) {
77
+ super("config", message, cause);
78
+ }
79
+ }
80
+
81
+ export class McpError extends CrewhausError {
82
+ override readonly name: string = "McpError";
83
+ constructor(message: string, cause?: unknown) {
84
+ super("mcp", message, cause);
85
+ }
86
+ }
87
+
88
+ export class McpConnectionError extends McpError {
89
+ override readonly name = "McpConnectionError";
90
+ }
91
+
92
+ export class McpProtocolError extends McpError {
93
+ override readonly name = "McpProtocolError";
94
+ }
95
+
96
+ /**
97
+ * Section 17 — base for all model-provider adapter failures (auth, request
98
+ * marshalling, stream parsing, network). Carries `providerId` so callers can
99
+ * branch on which adapter raised; `cause` keeps the underlying SDK error
100
+ * accessible via `toJSON()` for the trace/log layer.
101
+ */
102
+ export class AdapterError extends CrewhausError {
103
+ override readonly name: string = "AdapterError";
104
+ readonly providerId: string;
105
+ constructor(providerId: string, message: string, cause?: unknown) {
106
+ super("adapter", message, cause);
107
+ this.providerId = providerId;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Section 17 — missing or invalid credentials for a model provider. Surfaces
113
+ * with a setup hint pointing at the relevant env var.
114
+ */
115
+ export class ProviderAuthError extends AdapterError {
116
+ override readonly name = "ProviderAuthError";
117
+ }