@nebutra/errors 0.1.1 → 0.1.2

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 CHANGED
@@ -74,7 +74,7 @@ try {
74
74
  } catch (error) {
75
75
  if (isAppError(error)) {
76
76
  // Known error with code and status
77
- console.log(error.code, error.statusCode);
77
+ return handleKnownError(error.code, error.statusCode);
78
78
  } else {
79
79
  // Unknown error
80
80
  throw new InternalError("Unexpected error");
@@ -99,5 +99,5 @@ try {
99
99
 
100
100
  ## Related
101
101
 
102
- - [API Gateway](../../backends/gateway/)
103
- - [Observability](../../infra/ops/observability/)
102
+ - [API Gateway](../../../backends/gateway/)
103
+ - [Observability](../../../infra/ops/observability/)
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Errors - Unified error handling for Nebutra services
3
+ *
4
+ * Provides:
5
+ * - Typed error classes
6
+ * - Consistent API error responses
7
+ * - Error serialization for logging
8
+ */
9
+ export declare const ERROR_CODES: {
10
+ readonly BAD_REQUEST: "BAD_REQUEST";
11
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
12
+ readonly FORBIDDEN: "FORBIDDEN";
13
+ readonly NOT_FOUND: "NOT_FOUND";
14
+ readonly CONFLICT: "CONFLICT";
15
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
16
+ readonly RATE_LIMITED: "RATE_LIMITED";
17
+ readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
18
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
19
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
20
+ readonly EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR";
21
+ readonly DATABASE_ERROR: "DATABASE_ERROR";
22
+ readonly TIMEOUT: "TIMEOUT";
23
+ readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
24
+ readonly SUBSCRIPTION_EXPIRED: "SUBSCRIPTION_EXPIRED";
25
+ readonly FEATURE_DISABLED: "FEATURE_DISABLED";
26
+ readonly TENANT_SUSPENDED: "TENANT_SUSPENDED";
27
+ };
28
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
29
+ export interface AppErrorOptions {
30
+ code: ErrorCode;
31
+ message: string;
32
+ statusCode?: number;
33
+ cause?: Error;
34
+ metadata?: Record<string, unknown>;
35
+ suggestion?: string;
36
+ isOperational?: boolean;
37
+ }
38
+ export declare class AppError extends Error {
39
+ readonly code: ErrorCode;
40
+ readonly statusCode: number;
41
+ readonly isOperational: boolean;
42
+ readonly metadata?: Record<string, unknown>;
43
+ readonly suggestion?: string;
44
+ readonly timestamp: string;
45
+ constructor(options: AppErrorOptions);
46
+ toJSON(): Record<string, unknown>;
47
+ }
48
+ export interface CapabilityErrorOptions {
49
+ statusCode?: number;
50
+ cause?: Error;
51
+ metadata?: Record<string, unknown>;
52
+ suggestion: string;
53
+ code?: ErrorCode;
54
+ }
55
+ export declare class CapabilityError extends AppError {
56
+ readonly capability: string;
57
+ constructor(capability: string, message: string, options: CapabilityErrorOptions);
58
+ }
59
+ export declare class ValidationError extends AppError {
60
+ readonly fields?: Record<string, string[]>;
61
+ constructor(message: string, fields?: Record<string, string[]>);
62
+ }
63
+ export declare class UnauthorizedError extends AppError {
64
+ constructor(message?: string);
65
+ }
66
+ export declare class ForbiddenError extends AppError {
67
+ constructor(message?: string);
68
+ }
69
+ export declare class NotFoundError extends AppError {
70
+ constructor(resource?: string, id?: string);
71
+ }
72
+ export declare class ConflictError extends AppError {
73
+ constructor(message?: string);
74
+ }
75
+ export declare class RateLimitError extends AppError {
76
+ readonly retryAfter?: number;
77
+ constructor(retryAfter?: number);
78
+ }
79
+ export declare class QuotaExceededError extends AppError {
80
+ constructor(quota: string, limit: number, current: number);
81
+ }
82
+ export declare class ExternalServiceError extends AppError {
83
+ constructor(service: string, cause?: Error);
84
+ }
85
+ export declare class DatabaseError extends AppError {
86
+ constructor(operation: string, cause?: Error);
87
+ }
88
+ export interface ApiErrorResponse {
89
+ error: {
90
+ code: ErrorCode;
91
+ message: string;
92
+ details?: Record<string, unknown>;
93
+ };
94
+ requestId?: string;
95
+ }
96
+ export declare function toApiError(error: unknown, requestId?: string): ApiErrorResponse;
97
+ export declare function getStatusCode(error: unknown): number;
98
+ interface HonoContext {
99
+ req: {
100
+ header: (name: string) => string | undefined;
101
+ };
102
+ json: (data: unknown, status?: number) => unknown;
103
+ }
104
+ export interface ErrorHandlerOptions {
105
+ /**
106
+ * Called for every caught error so callers can route it to their structured
107
+ * logger (e.g. @nebutra/logger). Defaults to a no-op — DO NOT rely on the
108
+ * previous process.stderr.write behaviour; pass an onError callback instead.
109
+ */
110
+ onError?: (error: unknown, meta: {
111
+ requestId?: string;
112
+ statusCode: number;
113
+ }) => void;
114
+ }
115
+ export declare function errorHandler(options?: ErrorHandlerOptions): (c: HonoContext, next: () => Promise<void>) => Promise<unknown>;
116
+ /**
117
+ * Wrap async function with error handling
118
+ */
119
+ export declare function tryCatch<T>(fn: () => Promise<T>, errorHandler?: (error: unknown) => T | Promise<T>): Promise<T>;
120
+ /**
121
+ * Assert condition or throw error
122
+ */
123
+ export declare function assert(condition: unknown, message: string): asserts condition;
124
+ /**
125
+ * Assert not null/undefined or throw NotFoundError
126
+ */
127
+ export declare function assertFound<T>(value: T | null | undefined, resource: string, id?: string): asserts value is T;
128
+ export {};
129
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Errors - Unified error handling for Nebutra services
3
+ *
4
+ * Provides:
5
+ * - Typed error classes
6
+ * - Consistent API error responses
7
+ * - Error serialization for logging
8
+ */
9
+ // ============================================
10
+ // Error Codes
11
+ // ============================================
12
+ export const ERROR_CODES = {
13
+ // Client errors (4xx)
14
+ BAD_REQUEST: "BAD_REQUEST",
15
+ UNAUTHORIZED: "UNAUTHORIZED",
16
+ FORBIDDEN: "FORBIDDEN",
17
+ NOT_FOUND: "NOT_FOUND",
18
+ CONFLICT: "CONFLICT",
19
+ VALIDATION_ERROR: "VALIDATION_ERROR",
20
+ RATE_LIMITED: "RATE_LIMITED",
21
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
22
+ // Server errors (5xx)
23
+ INTERNAL_ERROR: "INTERNAL_ERROR",
24
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
25
+ EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR",
26
+ DATABASE_ERROR: "DATABASE_ERROR",
27
+ TIMEOUT: "TIMEOUT",
28
+ // Business errors
29
+ PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
30
+ SUBSCRIPTION_EXPIRED: "SUBSCRIPTION_EXPIRED",
31
+ FEATURE_DISABLED: "FEATURE_DISABLED",
32
+ TENANT_SUSPENDED: "TENANT_SUSPENDED",
33
+ };
34
+ export class AppError extends Error {
35
+ code;
36
+ statusCode;
37
+ isOperational;
38
+ metadata;
39
+ suggestion;
40
+ timestamp;
41
+ constructor(options) {
42
+ super(options.message);
43
+ this.name = "AppError";
44
+ this.code = options.code;
45
+ this.statusCode = options.statusCode || getDefaultStatusCode(options.code);
46
+ this.isOperational = options.isOperational ?? true;
47
+ if (options.metadata !== undefined) {
48
+ this.metadata = options.metadata;
49
+ }
50
+ if (options.suggestion !== undefined) {
51
+ this.suggestion = options.suggestion;
52
+ }
53
+ this.timestamp = new Date().toISOString();
54
+ if (options.cause) {
55
+ this.cause = options.cause;
56
+ }
57
+ Error.captureStackTrace(this, this.constructor);
58
+ }
59
+ toJSON() {
60
+ return {
61
+ name: this.name,
62
+ code: this.code,
63
+ message: this.message,
64
+ statusCode: this.statusCode,
65
+ timestamp: this.timestamp,
66
+ suggestion: this.suggestion,
67
+ metadata: this.metadata,
68
+ };
69
+ }
70
+ }
71
+ export class CapabilityError extends AppError {
72
+ capability;
73
+ constructor(capability, message, options) {
74
+ super({
75
+ code: options.code ?? ERROR_CODES.EXTERNAL_SERVICE_ERROR,
76
+ message,
77
+ statusCode: options.statusCode ?? 502,
78
+ ...(options.cause !== undefined ? { cause: options.cause } : {}),
79
+ suggestion: options.suggestion,
80
+ metadata: {
81
+ capability,
82
+ ...(options.metadata ?? {}),
83
+ },
84
+ });
85
+ this.name = "CapabilityError";
86
+ this.capability = capability;
87
+ }
88
+ }
89
+ // ============================================
90
+ // Specific Error Classes
91
+ // ============================================
92
+ export class ValidationError extends AppError {
93
+ fields;
94
+ constructor(message, fields) {
95
+ super({
96
+ code: ERROR_CODES.VALIDATION_ERROR,
97
+ message,
98
+ statusCode: 400,
99
+ ...(fields !== undefined && { metadata: { fields } }),
100
+ });
101
+ this.name = "ValidationError";
102
+ if (fields !== undefined) {
103
+ this.fields = fields;
104
+ }
105
+ }
106
+ }
107
+ export class UnauthorizedError extends AppError {
108
+ constructor(message = "Unauthorized") {
109
+ super({ code: ERROR_CODES.UNAUTHORIZED, message, statusCode: 401 });
110
+ this.name = "UnauthorizedError";
111
+ }
112
+ }
113
+ export class ForbiddenError extends AppError {
114
+ constructor(message = "Forbidden") {
115
+ super({ code: ERROR_CODES.FORBIDDEN, message, statusCode: 403 });
116
+ this.name = "ForbiddenError";
117
+ }
118
+ }
119
+ export class NotFoundError extends AppError {
120
+ constructor(resource = "Resource", id) {
121
+ const message = id ? `${resource} with id '${id}' not found` : `${resource} not found`;
122
+ super({ code: ERROR_CODES.NOT_FOUND, message, statusCode: 404 });
123
+ this.name = "NotFoundError";
124
+ }
125
+ }
126
+ export class ConflictError extends AppError {
127
+ constructor(message = "Resource already exists") {
128
+ super({ code: ERROR_CODES.CONFLICT, message, statusCode: 409 });
129
+ this.name = "ConflictError";
130
+ }
131
+ }
132
+ export class RateLimitError extends AppError {
133
+ retryAfter;
134
+ constructor(retryAfter) {
135
+ super({
136
+ code: ERROR_CODES.RATE_LIMITED,
137
+ message: "Too many requests",
138
+ statusCode: 429,
139
+ ...(retryAfter !== undefined && { metadata: { retryAfter } }),
140
+ });
141
+ this.name = "RateLimitError";
142
+ if (retryAfter !== undefined) {
143
+ this.retryAfter = retryAfter;
144
+ }
145
+ }
146
+ }
147
+ export class QuotaExceededError extends AppError {
148
+ constructor(quota, limit, current) {
149
+ super({
150
+ code: ERROR_CODES.QUOTA_EXCEEDED,
151
+ message: `${quota} quota exceeded (${current}/${limit})`,
152
+ statusCode: 429,
153
+ metadata: { quota, limit, current },
154
+ });
155
+ this.name = "QuotaExceededError";
156
+ }
157
+ }
158
+ export class ExternalServiceError extends AppError {
159
+ constructor(service, cause) {
160
+ super({
161
+ code: ERROR_CODES.EXTERNAL_SERVICE_ERROR,
162
+ message: `External service '${service}' failed`,
163
+ statusCode: 502,
164
+ ...(cause !== undefined && { cause }),
165
+ metadata: { service },
166
+ });
167
+ this.name = "ExternalServiceError";
168
+ }
169
+ }
170
+ export class DatabaseError extends AppError {
171
+ constructor(operation, cause) {
172
+ super({
173
+ code: ERROR_CODES.DATABASE_ERROR,
174
+ message: `Database operation '${operation}' failed`,
175
+ statusCode: 500,
176
+ ...(cause !== undefined && { cause }),
177
+ isOperational: false,
178
+ metadata: { operation },
179
+ });
180
+ this.name = "DatabaseError";
181
+ }
182
+ }
183
+ export function toApiError(error, requestId) {
184
+ if (error instanceof AppError) {
185
+ return {
186
+ error: {
187
+ code: error.code,
188
+ message: error.message,
189
+ ...((error.metadata !== undefined || error.suggestion !== undefined) && {
190
+ details: {
191
+ ...(error.metadata ?? {}),
192
+ ...(error.suggestion !== undefined && { suggestion: error.suggestion }),
193
+ },
194
+ }),
195
+ },
196
+ ...(requestId !== undefined && { requestId }),
197
+ };
198
+ }
199
+ // Unknown error - don't leak details
200
+ return {
201
+ error: {
202
+ code: ERROR_CODES.INTERNAL_ERROR,
203
+ message: "An unexpected error occurred",
204
+ },
205
+ ...(requestId !== undefined && { requestId }),
206
+ };
207
+ }
208
+ export function getStatusCode(error) {
209
+ if (error instanceof AppError) {
210
+ return error.statusCode;
211
+ }
212
+ return 500;
213
+ }
214
+ export function errorHandler(options = {}) {
215
+ return async (c, next) => {
216
+ try {
217
+ await next();
218
+ }
219
+ catch (error) {
220
+ const requestId = c.req.header("x-request-id");
221
+ const statusCode = getStatusCode(error);
222
+ const response = toApiError(error, requestId);
223
+ options.onError?.(error, requestId !== undefined ? { requestId, statusCode } : { statusCode });
224
+ return c.json(response, statusCode);
225
+ }
226
+ };
227
+ }
228
+ // ============================================
229
+ // Utility Functions
230
+ // ============================================
231
+ function getDefaultStatusCode(code) {
232
+ switch (code) {
233
+ case ERROR_CODES.BAD_REQUEST:
234
+ case ERROR_CODES.VALIDATION_ERROR:
235
+ return 400;
236
+ case ERROR_CODES.UNAUTHORIZED:
237
+ return 401;
238
+ case ERROR_CODES.PAYMENT_REQUIRED:
239
+ case ERROR_CODES.SUBSCRIPTION_EXPIRED:
240
+ return 402;
241
+ case ERROR_CODES.FORBIDDEN:
242
+ case ERROR_CODES.FEATURE_DISABLED:
243
+ case ERROR_CODES.TENANT_SUSPENDED:
244
+ return 403;
245
+ case ERROR_CODES.NOT_FOUND:
246
+ return 404;
247
+ case ERROR_CODES.CONFLICT:
248
+ return 409;
249
+ case ERROR_CODES.RATE_LIMITED:
250
+ case ERROR_CODES.QUOTA_EXCEEDED:
251
+ return 429;
252
+ case ERROR_CODES.INTERNAL_ERROR:
253
+ case ERROR_CODES.DATABASE_ERROR:
254
+ return 500;
255
+ case ERROR_CODES.EXTERNAL_SERVICE_ERROR:
256
+ return 502;
257
+ case ERROR_CODES.SERVICE_UNAVAILABLE:
258
+ return 503;
259
+ case ERROR_CODES.TIMEOUT:
260
+ return 504;
261
+ default:
262
+ return 500;
263
+ }
264
+ }
265
+ /**
266
+ * Wrap async function with error handling
267
+ */
268
+ export function tryCatch(fn, errorHandler) {
269
+ return fn().catch((error) => {
270
+ if (errorHandler) {
271
+ return errorHandler(error);
272
+ }
273
+ throw error;
274
+ });
275
+ }
276
+ /**
277
+ * Assert condition or throw error
278
+ */
279
+ export function assert(condition, message) {
280
+ if (!condition) {
281
+ throw new AppError({
282
+ code: ERROR_CODES.BAD_REQUEST,
283
+ message,
284
+ });
285
+ }
286
+ }
287
+ /**
288
+ * Assert not null/undefined or throw NotFoundError
289
+ */
290
+ export function assertFound(value, resource, id) {
291
+ if (value === null || value === undefined) {
292
+ throw new NotFoundError(resource, id);
293
+ }
294
+ }
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@nebutra/errors",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Unified error handling and API error responses",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "main": "./src/index.ts",
9
- "types": "./src/index.ts",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
10
  "exports": {
11
- ".": "./src/index.ts"
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
12
16
  },
17
+ "files": [
18
+ "dist"
19
+ ],
13
20
  "devDependencies": {
14
21
  "@types/node": "^22.19.15",
15
22
  "typescript": "^5.9.3",
@@ -28,7 +35,8 @@
28
35
  "access": "public"
29
36
  },
30
37
  "scripts": {
38
+ "build": "tsc -p tsconfig.json",
31
39
  "test": "vitest run",
32
- "typecheck": "tsc --noEmit"
40
+ "typecheck": "tsc -p tsconfig.json --noEmit"
33
41
  }
34
42
  }
@@ -1,14 +0,0 @@
1
-
2
- > @nebutra/errors@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/platform/errors
3
- > vitest run
4
-
5
-
6
-  RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/platform/errors
7
-
8
- ✓ src/index.test.ts (1 test) 50ms
9
-
10
-  Test Files  1 passed (1)
11
-  Tests  1 passed (1)
12
-  Start at  08:14:48
13
-  Duration  1.58s (transform 455ms, setup 0ms, import 577ms, tests 50ms, environment 0ms)
14
-
@@ -1,4 +0,0 @@
1
-
2
- > @nebutra/errors@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/platform/errors
3
- > tsc --noEmit
4
-
package/AGENTS.md DELETED
@@ -1,65 +0,0 @@
1
- # AGENTS.md — packages/errors
2
-
3
- Execution contract for Nebutra's shared error semantics package.
4
-
5
- ## Scope
6
-
7
- Applies to everything under `packages/platform/errors/`.
8
-
9
- This package owns canonical application error codes, typed error classes,
10
- API-safe serialization helpers, and the framework-agnostic error middleware
11
- shape. It is the shared error vocabulary layer, not the place for service-local
12
- logging policy or provider-specific exception translation.
13
-
14
- ## Source Of Truth
15
-
16
- - Public package surface: `package.json`, `src/index.ts`
17
- - Canonical error code catalog: `ERROR_CODES`
18
- - Base error class and serialization behavior: `AppError`, `AppErrorOptions`,
19
- `AppError.toJSON()`
20
- - Specific error subclasses and their default status semantics:
21
- `ValidationError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`,
22
- `ConflictError`, `RateLimitError`, `QuotaExceededError`,
23
- `ExternalServiceError`, `DatabaseError`
24
- - API response and status helpers: `toApiError`, `getStatusCode`
25
- - Framework boundary for request-safe error responses: `errorHandler`
26
- - Assertion and wrapper helpers: `tryCatch`, `assert`, `assertFound`
27
-
28
- Treat `README.md` as descriptive only. If docs drift, update `src/index.ts`
29
- instead of preserving outdated examples.
30
-
31
- ## Contract Boundaries
32
-
33
- - Keep `ERROR_CODES` as the canonical shared vocabulary. Additive changes are
34
- safest; renames or removals are compatibility changes for callers, handlers,
35
- and logs.
36
- - Treat `AppError` and `ApiErrorResponse` as the stable serialization boundary.
37
- Do not leak raw unknown errors or provider-specific details through
38
- `toApiError`.
39
- - Preserve default status-code mapping in `getDefaultStatusCode()` unless the
40
- compatibility change is deliberate and coordinated with consumers.
41
- - Keep `errorHandler()` framework-agnostic and request-safe. Its job is to map
42
- errors into structured JSON and invoke the optional `onError` callback, not
43
- to own logging destinations or transport-specific side effects.
44
- - Preserve the distinction between operational and non-operational errors.
45
- `DatabaseError` and other server faults should not be quietly normalized into
46
- benign client semantics.
47
- - Keep assertion helpers thin wrappers over the shared error types. Do not
48
- embed app-specific policy or database lookups here.
49
-
50
- ## Generated And Derived Files
51
-
52
- - This package currently exports source directly and has no checked-in
53
- generated source of truth.
54
- - Do not hand-edit future build output, coverage artifacts, or transient
55
- TypeScript output.
56
- - If packaging changes later, update the source files above rather than derived
57
- artifacts.
58
-
59
- ## Validation
60
-
61
- - Error type, code, or middleware changes:
62
- `pnpm --filter @nebutra/errors exec tsc --noEmit`
63
- - Because this package currently has no package-local tests, verify the
64
- narrowest downstream consumer that exercises the changed error contract when
65
- behavior changes are non-trivial.
package/CHANGELOG.md DELETED
@@ -1,7 +0,0 @@
1
- # @nebutra/errors
2
-
3
- ## 0.1.1
4
-
5
- ### Patch Changes
6
-
7
- - Publish registry package metadata under the MIT license.
package/src/index.test.ts DELETED
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { CapabilityError, toApiError } from "./index";
3
-
4
- describe("CapabilityError", () => {
5
- it("serializes a suggestion for API callers", () => {
6
- const error = new CapabilityError("provider-registry", "Missing local model", {
7
- suggestion: "Run provider-registry:doctor and install a local model.",
8
- metadata: { provider: "ollama" },
9
- });
10
-
11
- expect(error.capability).toBe("provider-registry");
12
- expect(error.suggestion).toContain("provider-registry:doctor");
13
- expect(error.toJSON()).toMatchObject({
14
- code: "EXTERNAL_SERVICE_ERROR",
15
- suggestion: "Run provider-registry:doctor and install a local model.",
16
- metadata: { capability: "provider-registry", provider: "ollama" },
17
- });
18
- expect(toApiError(error).error.details).toMatchObject({
19
- suggestion: "Run provider-registry:doctor and install a local model.",
20
- });
21
- });
22
- });