@wrelik/errors 0.2.1 → 2.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrelik/errors",
3
- "version": "0.2.1",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,21 +9,28 @@
9
9
  "url": "https://github.com/lwhite702/wrelik-kit.git",
10
10
  "directory": "packages/errors"
11
11
  },
12
- "main": "./dist/node.js",
13
- "types": "./dist/node.d.ts",
12
+ "sideEffects": false,
14
13
  "exports": {
15
- ".": {
16
- "react-native": "./dist/react-native.js",
17
- "browser": "./dist/browser.js",
18
- "import": "./dist/node.js",
19
- "require": "./dist/node.js"
14
+ "./server": {
15
+ "types": "./dist/server/index.d.ts",
16
+ "import": "./dist/server/index.mjs",
17
+ "require": "./dist/server/index.js"
20
18
  },
21
- "./react-native": "./dist/react-native.js"
19
+ "./client": {
20
+ "types": "./dist/client/index.d.ts",
21
+ "import": "./dist/client/index.mjs",
22
+ "require": "./dist/client/index.js"
23
+ },
24
+ "./shared": {
25
+ "types": "./dist/shared/index.d.ts",
26
+ "import": "./dist/shared/index.mjs",
27
+ "require": "./dist/shared/index.js"
28
+ },
29
+ "./package.json": "./package.json"
22
30
  },
23
31
  "dependencies": {
24
32
  "@sentry/browser": "^10.38.0",
25
- "@sentry/node": "^7.101.1",
26
- "@sentry/react-native": "^5.19.0"
33
+ "@sentry/node": "^7.101.1"
27
34
  },
28
35
  "devDependencies": {
29
36
  "@types/node": "^25.2.0",
@@ -32,9 +39,17 @@
32
39
  "@wrelik/eslint-config": "0.1.1",
33
40
  "@wrelik/tsconfig": "0.1.1"
34
41
  },
42
+ "wrelik": {
43
+ "runtimes": [
44
+ "server",
45
+ "client",
46
+ "shared"
47
+ ]
48
+ },
35
49
  "scripts": {
36
- "build": "tsup src/node.ts src/browser.ts src/react-native.ts --format cjs,esm --dts --clean",
50
+ "build": "tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean",
37
51
  "lint": "eslint src/",
52
+ "typecheck": "tsc --noEmit",
38
53
  "test": "vitest run --passWithNoTests"
39
54
  }
40
55
  }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeError } from './index';
3
+
4
+ describe('@wrelik/errors/client', () => {
5
+ it('exposes error normalization but not server capture helpers', async () => {
6
+ const mod = await import('./index.js');
7
+
8
+ expect(typeof mod.normalizeError).toBe('function');
9
+ expect('captureError' in mod).toBe(false);
10
+ expect('initErrors' in mod).toBe(false);
11
+ });
12
+
13
+ it('normalizes errors with a client-safe entrypoint', () => {
14
+ const normalized = normalizeError(new Error('client-boom'));
15
+ expect(normalized.code).toBe('INTERNAL_ERROR');
16
+ expect(normalized.message).toBe('client-boom');
17
+ });
18
+ });
@@ -0,0 +1,2 @@
1
+ export { normalizeError } from '../shared';
2
+ export type { ErrorContext, NormalizedError } from '../shared';
@@ -0,0 +1,40 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ afterEach(() => {
4
+ vi.resetModules();
5
+ vi.clearAllMocks();
6
+ });
7
+
8
+ describe('@wrelik/errors/server', () => {
9
+ it('captures unexpected errors through the server Sentry SDK', async () => {
10
+ const captureException = vi.fn();
11
+ vi.doMock('@sentry/node', () => ({
12
+ default: {},
13
+ captureException,
14
+ init: vi.fn(),
15
+ setUser: vi.fn(),
16
+ }));
17
+
18
+ const mod = await import('./index.js');
19
+ mod.captureError(new Error('boom'), { requestId: 'r1' });
20
+
21
+ expect(captureException).toHaveBeenCalledTimes(1);
22
+ expect(captureException.mock.calls[0][1]).toEqual({ extra: { requestId: 'r1' } });
23
+ });
24
+
25
+ it('does not capture handled 4xx AppError instances', async () => {
26
+ const captureException = vi.fn();
27
+ vi.doMock('@sentry/node', () => ({
28
+ default: {},
29
+ captureException,
30
+ init: vi.fn(),
31
+ setUser: vi.fn(),
32
+ }));
33
+
34
+ const mod = await import('./index.js');
35
+ const { ValidationError } = await import('../shared/index.js');
36
+ mod.captureError(new ValidationError('bad input'));
37
+
38
+ expect(captureException).not.toHaveBeenCalled();
39
+ });
40
+ });
@@ -0,0 +1,26 @@
1
+ import * as Sentry from '@sentry/node';
2
+ import { AppError, normalizeError, type ErrorContext } from '../shared';
3
+
4
+ export function initErrors(config: { dsn: string; tracesSampleRate?: number }): void {
5
+ Sentry.init({
6
+ dsn: config.dsn,
7
+ tracesSampleRate: config.tracesSampleRate ?? 1,
8
+ });
9
+ }
10
+
11
+ export function setUserContext(user: { id: string; email?: string } | null): void {
12
+ Sentry.setUser(user);
13
+ }
14
+
15
+ export function captureError(err: unknown, context?: ErrorContext): void {
16
+ const normalized = normalizeError(err, context);
17
+
18
+ if (err instanceof AppError) {
19
+ if (normalized.statusCode >= 500) {
20
+ Sentry.captureException(err, { extra: normalized.context });
21
+ }
22
+ return;
23
+ }
24
+
25
+ Sentry.captureException(err, { extra: normalized.context });
26
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeError, ValidationError } from './index';
3
+
4
+ describe('@wrelik/errors/shared', () => {
5
+ it('normalizes AppError shapes and merges context', () => {
6
+ const err = new ValidationError('Bad input', { field: 'email' });
7
+ const normalized = normalizeError(err, { requestId: 'r1' });
8
+
9
+ expect(normalized).toEqual({
10
+ name: 'AppError',
11
+ message: 'Bad input',
12
+ code: 'VALIDATION_ERROR',
13
+ statusCode: 400,
14
+ context: { field: 'email', requestId: 'r1' },
15
+ });
16
+ });
17
+
18
+ it('normalizes unknown errors to an internal error shape', () => {
19
+ const normalized = normalizeError(new Error('boom'));
20
+
21
+ expect(normalized).toEqual({
22
+ name: 'Error',
23
+ message: 'boom',
24
+ code: 'INTERNAL_ERROR',
25
+ statusCode: 500,
26
+ context: undefined,
27
+ });
28
+ });
29
+
30
+ it('normalizes non-error throwables with original payload', () => {
31
+ const normalized = normalizeError('boom', { requestId: 'r2' });
32
+
33
+ expect(normalized.code).toBe('INTERNAL_ERROR');
34
+ expect(normalized.statusCode).toBe(500);
35
+ expect(normalized.context).toEqual({ requestId: 'r2', originalError: 'boom' });
36
+ });
37
+ });
@@ -0,0 +1,9 @@
1
+ export {
2
+ AppError,
3
+ AuthRequiredError,
4
+ normalizeError,
5
+ PermissionDeniedError,
6
+ TenantRequiredError,
7
+ ValidationError,
8
+ } from './types';
9
+ export type { ErrorContext, NormalizedError } from './types';
@@ -0,0 +1,78 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export type ErrorContext = Record<string, any>;
3
+
4
+ export interface NormalizedError {
5
+ name: string;
6
+ message: string;
7
+ code: string;
8
+ statusCode: number;
9
+ context?: ErrorContext;
10
+ }
11
+
12
+ export class AppError extends Error {
13
+ public readonly code: string;
14
+ public readonly statusCode: number;
15
+ public readonly context?: ErrorContext;
16
+
17
+ constructor(message: string, code: string, statusCode = 500, context?: ErrorContext) {
18
+ super(message);
19
+ this.name = 'AppError';
20
+ this.code = code;
21
+ this.statusCode = statusCode;
22
+ this.context = context;
23
+ }
24
+ }
25
+
26
+ export class AuthRequiredError extends AppError {
27
+ constructor(message = 'Authentication required', context?: ErrorContext) {
28
+ super(message, 'AUTH_REQUIRED', 401, context);
29
+ }
30
+ }
31
+
32
+ export class TenantRequiredError extends AppError {
33
+ constructor(message = 'Tenant context required', context?: ErrorContext) {
34
+ super(message, 'TENANT_REQUIRED', 400, context);
35
+ }
36
+ }
37
+
38
+ export class PermissionDeniedError extends AppError {
39
+ constructor(message = 'Permission denied', context?: ErrorContext) {
40
+ super(message, 'PERMISSION_DENIED', 403, context);
41
+ }
42
+ }
43
+
44
+ export class ValidationError extends AppError {
45
+ constructor(message: string, context?: ErrorContext) {
46
+ super(message, 'VALIDATION_ERROR', 400, context);
47
+ }
48
+ }
49
+
50
+ export function normalizeError(err: unknown, context?: ErrorContext): NormalizedError {
51
+ if (err instanceof AppError) {
52
+ return {
53
+ name: err.name,
54
+ message: err.message,
55
+ code: err.code,
56
+ statusCode: err.statusCode,
57
+ context: context ? { ...err.context, ...context } : err.context,
58
+ };
59
+ }
60
+
61
+ if (err instanceof Error) {
62
+ return {
63
+ name: err.name,
64
+ message: err.message,
65
+ code: 'INTERNAL_ERROR',
66
+ statusCode: 500,
67
+ context,
68
+ };
69
+ }
70
+
71
+ return {
72
+ name: 'Error',
73
+ message: 'Unknown error',
74
+ code: 'INTERNAL_ERROR',
75
+ statusCode: 500,
76
+ context: context ? { ...context, originalError: err } : { originalError: err },
77
+ };
78
+ }
package/tsconfig.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "extends": "@wrelik/tsconfig/node.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "dist",
5
- "skipLibCheck": true
5
+ "skipLibCheck": true,
6
+ "lib": ["DOM", "DOM.Iterable", "ES2022"]
6
7
  },
7
8
  "include": ["src"]
8
9
  }
@@ -1,10 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.mjs';
2
-
3
- declare function captureError(err: unknown, context?: Record<string, any>): void;
4
- declare function setUserContext(user: {
5
- id: string;
6
- email?: string;
7
- } | null): void;
8
- declare function initErrors(dsn: string): void;
9
-
10
- export { captureError, initErrors, setUserContext };
package/dist/browser.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.js';
2
-
3
- declare function captureError(err: unknown, context?: Record<string, any>): void;
4
- declare function setUserContext(user: {
5
- id: string;
6
- email?: string;
7
- } | null): void;
8
- declare function initErrors(dsn: string): void;
9
-
10
- export { captureError, initErrors, setUserContext };
package/dist/browser.js DELETED
@@ -1,115 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/browser.ts
31
- var browser_exports = {};
32
- __export(browser_exports, {
33
- AppError: () => AppError,
34
- AuthRequiredError: () => AuthRequiredError,
35
- PermissionDeniedError: () => PermissionDeniedError,
36
- TenantRequiredError: () => TenantRequiredError,
37
- ValidationError: () => ValidationError,
38
- captureError: () => captureError,
39
- initErrors: () => initErrors,
40
- setUserContext: () => setUserContext
41
- });
42
- module.exports = __toCommonJS(browser_exports);
43
- var Sentry = __toESM(require("@sentry/browser"));
44
-
45
- // src/shared.ts
46
- var AppError = class extends Error {
47
- code;
48
- statusCode;
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- context;
51
- constructor(message, code, statusCode = 500, context) {
52
- super(message);
53
- this.name = "AppError";
54
- this.code = code;
55
- this.statusCode = statusCode;
56
- this.context = context;
57
- }
58
- };
59
- var AuthRequiredError = class extends AppError {
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- constructor(message = "Authentication required", context) {
62
- super(message, "AUTH_REQUIRED", 401, context);
63
- }
64
- };
65
- var TenantRequiredError = class extends AppError {
66
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
- constructor(message = "Tenant context required", context) {
68
- super(message, "TENANT_REQUIRED", 400, context);
69
- }
70
- };
71
- var PermissionDeniedError = class extends AppError {
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- constructor(message = "Permission denied", context) {
74
- super(message, "PERMISSION_DENIED", 403, context);
75
- }
76
- };
77
- var ValidationError = class extends AppError {
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- constructor(message, context) {
80
- super(message, "VALIDATION_ERROR", 400, context);
81
- }
82
- };
83
-
84
- // src/browser.ts
85
- function captureError(err, context) {
86
- if (err instanceof AppError) {
87
- console.warn(`[${err.code}] ${err.message}`, { context: err.context, ...context });
88
- if (err.statusCode >= 500) {
89
- Sentry.captureException(err, { extra: { ...err.context, ...context } });
90
- }
91
- } else {
92
- console.error("Unexpected error:", err);
93
- Sentry.captureException(err, { extra: context });
94
- }
95
- }
96
- function setUserContext(user) {
97
- Sentry.setUser(user);
98
- }
99
- function initErrors(dsn) {
100
- Sentry.init({
101
- dsn,
102
- tracesSampleRate: 1
103
- });
104
- }
105
- // Annotate the CommonJS export names for ESM import in node:
106
- 0 && (module.exports = {
107
- AppError,
108
- AuthRequiredError,
109
- PermissionDeniedError,
110
- TenantRequiredError,
111
- ValidationError,
112
- captureError,
113
- initErrors,
114
- setUserContext
115
- });
package/dist/browser.mjs DELETED
@@ -1,40 +0,0 @@
1
- import {
2
- AppError,
3
- AuthRequiredError,
4
- PermissionDeniedError,
5
- TenantRequiredError,
6
- ValidationError
7
- } from "./chunk-EARDCOC4.mjs";
8
-
9
- // src/browser.ts
10
- import * as Sentry from "@sentry/browser";
11
- function captureError(err, context) {
12
- if (err instanceof AppError) {
13
- console.warn(`[${err.code}] ${err.message}`, { context: err.context, ...context });
14
- if (err.statusCode >= 500) {
15
- Sentry.captureException(err, { extra: { ...err.context, ...context } });
16
- }
17
- } else {
18
- console.error("Unexpected error:", err);
19
- Sentry.captureException(err, { extra: context });
20
- }
21
- }
22
- function setUserContext(user) {
23
- Sentry.setUser(user);
24
- }
25
- function initErrors(dsn) {
26
- Sentry.init({
27
- dsn,
28
- tracesSampleRate: 1
29
- });
30
- }
31
- export {
32
- AppError,
33
- AuthRequiredError,
34
- PermissionDeniedError,
35
- TenantRequiredError,
36
- ValidationError,
37
- captureError,
38
- initErrors,
39
- setUserContext
40
- };
package/dist/node.d.mts DELETED
@@ -1,10 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.mjs';
2
-
3
- declare function captureError(err: unknown, context?: Record<string, any>): void;
4
- declare function setUserContext(user: {
5
- id: string;
6
- email?: string;
7
- } | null): void;
8
- declare function initErrors(dsn: string): void;
9
-
10
- export { captureError, initErrors, setUserContext };
package/dist/node.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.js';
2
-
3
- declare function captureError(err: unknown, context?: Record<string, any>): void;
4
- declare function setUserContext(user: {
5
- id: string;
6
- email?: string;
7
- } | null): void;
8
- declare function initErrors(dsn: string): void;
9
-
10
- export { captureError, initErrors, setUserContext };
package/dist/node.mjs DELETED
@@ -1,40 +0,0 @@
1
- import {
2
- AppError,
3
- AuthRequiredError,
4
- PermissionDeniedError,
5
- TenantRequiredError,
6
- ValidationError
7
- } from "./chunk-EARDCOC4.mjs";
8
-
9
- // src/node.ts
10
- import * as Sentry from "@sentry/node";
11
- function captureError(err, context) {
12
- if (err instanceof AppError) {
13
- console.warn(`[${err.code}] ${err.message}`, { context: err.context, ...context });
14
- if (err.statusCode >= 500) {
15
- Sentry.captureException(err, { extra: { ...err.context, ...context } });
16
- }
17
- } else {
18
- console.error("Unexpected error:", err);
19
- Sentry.captureException(err, { extra: context });
20
- }
21
- }
22
- function setUserContext(user) {
23
- Sentry.setUser(user);
24
- }
25
- function initErrors(dsn) {
26
- Sentry.init({
27
- dsn,
28
- tracesSampleRate: 1
29
- });
30
- }
31
- export {
32
- AppError,
33
- AuthRequiredError,
34
- PermissionDeniedError,
35
- TenantRequiredError,
36
- ValidationError,
37
- captureError,
38
- initErrors,
39
- setUserContext
40
- };
@@ -1,13 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.mjs';
2
-
3
- declare function initErrors(config: {
4
- dsn: string;
5
- environment?: string;
6
- }): void;
7
- declare function setUserContext(user: {
8
- id: string;
9
- email?: string;
10
- } | null): void;
11
- declare function captureError(err: unknown, context?: Record<string, any>): void;
12
-
13
- export { captureError, initErrors, setUserContext };
@@ -1,13 +0,0 @@
1
- export { A as AppError, a as AuthRequiredError, P as PermissionDeniedError, T as TenantRequiredError, V as ValidationError } from './shared-DXFP3J32.js';
2
-
3
- declare function initErrors(config: {
4
- dsn: string;
5
- environment?: string;
6
- }): void;
7
- declare function setUserContext(user: {
8
- id: string;
9
- email?: string;
10
- } | null): void;
11
- declare function captureError(err: unknown, context?: Record<string, any>): void;
12
-
13
- export { captureError, initErrors, setUserContext };
@@ -1,40 +0,0 @@
1
- import {
2
- AppError,
3
- AuthRequiredError,
4
- PermissionDeniedError,
5
- TenantRequiredError,
6
- ValidationError
7
- } from "./chunk-EARDCOC4.mjs";
8
-
9
- // src/react-native.ts
10
- import * as Sentry from "@sentry/react-native";
11
- function initErrors(config) {
12
- Sentry.init({
13
- dsn: config.dsn,
14
- environment: config.environment || "production"
15
- });
16
- }
17
- function setUserContext(user) {
18
- Sentry.setUser(user);
19
- }
20
- function captureError(err, context) {
21
- if (err instanceof AppError) {
22
- if (err.statusCode >= 500) {
23
- Sentry.captureException(err, { extra: { ...err.context, ...context } });
24
- } else {
25
- console.warn(`[${err.code}] ${err.message}`, { context: err.context, ...context });
26
- }
27
- } else {
28
- Sentry.captureException(err, { extra: context });
29
- }
30
- }
31
- export {
32
- AppError,
33
- AuthRequiredError,
34
- PermissionDeniedError,
35
- TenantRequiredError,
36
- ValidationError,
37
- captureError,
38
- initErrors,
39
- setUserContext
40
- };
@@ -1,20 +0,0 @@
1
- declare class AppError extends Error {
2
- readonly code: string;
3
- readonly statusCode: number;
4
- readonly context?: Record<string, any>;
5
- constructor(message: string, code: string, statusCode?: number, context?: Record<string, any>);
6
- }
7
- declare class AuthRequiredError extends AppError {
8
- constructor(message?: string, context?: Record<string, any>);
9
- }
10
- declare class TenantRequiredError extends AppError {
11
- constructor(message?: string, context?: Record<string, any>);
12
- }
13
- declare class PermissionDeniedError extends AppError {
14
- constructor(message?: string, context?: Record<string, any>);
15
- }
16
- declare class ValidationError extends AppError {
17
- constructor(message: string, context?: Record<string, any>);
18
- }
19
-
20
- export { AppError as A, PermissionDeniedError as P, TenantRequiredError as T, ValidationError as V, AuthRequiredError as a };
@@ -1,20 +0,0 @@
1
- declare class AppError extends Error {
2
- readonly code: string;
3
- readonly statusCode: number;
4
- readonly context?: Record<string, any>;
5
- constructor(message: string, code: string, statusCode?: number, context?: Record<string, any>);
6
- }
7
- declare class AuthRequiredError extends AppError {
8
- constructor(message?: string, context?: Record<string, any>);
9
- }
10
- declare class TenantRequiredError extends AppError {
11
- constructor(message?: string, context?: Record<string, any>);
12
- }
13
- declare class PermissionDeniedError extends AppError {
14
- constructor(message?: string, context?: Record<string, any>);
15
- }
16
- declare class ValidationError extends AppError {
17
- constructor(message: string, context?: Record<string, any>);
18
- }
19
-
20
- export { AppError as A, PermissionDeniedError as P, TenantRequiredError as T, ValidationError as V, AuthRequiredError as a };