@velajs/errors 1.0.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 49f72ad: Modernize the package build, validation, and release toolchain.
8
+
3
9
  All notable changes to `@velajs/errors` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
10
 
5
11
  ## 1.0.0
package/dist/index.d.ts CHANGED
@@ -1,11 +1,179 @@
1
- export { VelaError } from './error';
2
- export type { VelaErrorOptions } from './error';
3
- export { CORE_ENTRIES } from './catalog-data';
4
- export type { CoreErrorCode, ErrorCatalogEntry } from './catalog-data';
5
- export { CORE_CATALOG, STATUS_TO_CODE, composeCatalogs, defineErrorCatalog } from './catalog';
6
- export type { Catalog } from './catalog';
7
- export { isVelaError } from './guard';
8
- export type { VelaErrorLike } from './guard';
9
- export { toErrorBody } from './to-error-body';
10
- export type { ErrorBodyResult, ToErrorBodyOptions, WireErrorObject } from './to-error-body';
11
- export { invariant, unreachable } from './invariant';
1
+ //#region src/catalog-data.d.ts
2
+ interface ErrorCatalogEntry {
3
+ status: number;
4
+ title: string;
5
+ hint?: string;
6
+ docsUrl?: string;
7
+ /** Redaction posture: true message/hint/data are never echoed to clients. */
8
+ internal?: boolean;
9
+ }
10
+ declare const CORE_ENTRIES: {
11
+ readonly bad_request: {
12
+ readonly status: 400;
13
+ readonly title: 'Bad Request';
14
+ };
15
+ readonly unauthorized: {
16
+ readonly status: 401;
17
+ readonly title: 'Unauthorized';
18
+ };
19
+ readonly forbidden: {
20
+ readonly status: 403;
21
+ readonly title: 'Forbidden';
22
+ };
23
+ readonly not_found: {
24
+ readonly status: 404;
25
+ readonly title: 'Not Found';
26
+ };
27
+ readonly method_not_allowed: {
28
+ readonly status: 405;
29
+ readonly title: 'Method Not Allowed';
30
+ };
31
+ readonly conflict: {
32
+ readonly status: 409;
33
+ readonly title: 'Conflict';
34
+ };
35
+ readonly gone: {
36
+ readonly status: 410;
37
+ readonly title: 'Gone';
38
+ };
39
+ readonly payload_too_large: {
40
+ readonly status: 413;
41
+ readonly title: 'Payload Too Large';
42
+ };
43
+ readonly unsupported_media_type: {
44
+ readonly status: 415;
45
+ readonly title: 'Unsupported Media Type';
46
+ };
47
+ readonly unprocessable: {
48
+ readonly status: 422;
49
+ readonly title: 'Unprocessable Entity';
50
+ };
51
+ readonly too_many_requests: {
52
+ readonly status: 429;
53
+ readonly title: 'Too Many Requests';
54
+ };
55
+ readonly internal: {
56
+ readonly status: 500;
57
+ readonly title: 'Internal Server Error';
58
+ readonly internal: true;
59
+ };
60
+ readonly not_implemented: {
61
+ readonly status: 501;
62
+ readonly title: 'Not Implemented';
63
+ };
64
+ readonly bad_gateway: {
65
+ readonly status: 502;
66
+ readonly title: 'Bad Gateway';
67
+ };
68
+ readonly service_unavailable: {
69
+ readonly status: 503;
70
+ readonly title: 'Service Unavailable';
71
+ };
72
+ readonly gateway_timeout: {
73
+ readonly status: 504;
74
+ readonly title: 'Gateway Timeout';
75
+ };
76
+ };
77
+ type CoreErrorCode = keyof typeof CORE_ENTRIES;
78
+ //#endregion
79
+ //#region src/error.d.ts
80
+ interface VelaErrorOptions {
81
+ message?: string;
82
+ status?: number;
83
+ hint?: string;
84
+ docsUrl?: string;
85
+ data?: unknown;
86
+ cause?: unknown;
87
+ }
88
+ /**
89
+ * The one Vela error. Every field is an OWN ENUMERABLE property so the error
90
+ * rides any wire codec / structuredClone / DO-RPC prop-copy with no special
91
+ * serialization path. `type` is the brand `isVelaError` checks — it must
92
+ * survive serialization, which own+enumerable guarantees.
93
+ */
94
+ declare class VelaError extends Error {
95
+ readonly type = "VelaError";
96
+ readonly code: string;
97
+ readonly status: number;
98
+ readonly hint?: string;
99
+ readonly docsUrl?: string;
100
+ readonly data?: unknown;
101
+ constructor(code: CoreErrorCode, options?: VelaErrorOptions);
102
+ constructor(code: string, options: VelaErrorOptions & {
103
+ status: number;
104
+ });
105
+ }
106
+ //#endregion
107
+ //#region src/catalog.d.ts
108
+ interface Catalog<C extends string = string> {
109
+ readonly entries: Readonly<Record<C, ErrorCatalogEntry>>;
110
+ /** Typed thrower bound to this catalog's defaults. */
111
+ error(code: C | (string & {}), options?: VelaErrorOptions): VelaError;
112
+ has(code: string): boolean;
113
+ get(code: string): ErrorCatalogEntry | undefined;
114
+ }
115
+ declare const defineErrorCatalog: <const T extends Record<string, ErrorCatalogEntry>>(entries: T) => Catalog<Extract<keyof T, string>>;
116
+ declare const composeCatalogs: (...catalogs: Array<Catalog<string>>) => Catalog<string>;
117
+ declare const CORE_CATALOG: Catalog<CoreErrorCode>;
118
+ declare const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>>;
119
+ //#endregion
120
+ //#region src/guard.d.ts
121
+ /**
122
+ * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable
123
+ * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape
124
+ * check lets foreign driver errors ride the client-echo path. The brand
125
+ * (`type === 'VelaError'`, an own enumerable prop that survives serialization)
126
+ * closes both failure modes. Nothing load-bearing may use `instanceof`.
127
+ */
128
+ interface VelaErrorLike extends Error {
129
+ type: 'VelaError';
130
+ code: string;
131
+ status: number;
132
+ hint?: string;
133
+ docsUrl?: string;
134
+ data?: unknown;
135
+ }
136
+ declare const isVelaError: (error: unknown) => error is VelaErrorLike;
137
+ //#endregion
138
+ //#region src/to-error-body.d.ts
139
+ interface WireErrorObject {
140
+ code: string;
141
+ message: string;
142
+ hint?: string;
143
+ docsUrl?: string;
144
+ details?: unknown;
145
+ }
146
+ interface ErrorBodyResult {
147
+ body: {
148
+ error: WireErrorObject;
149
+ };
150
+ status: number;
151
+ redacted: boolean;
152
+ }
153
+ interface ToErrorBodyOptions {
154
+ /** Composed catalog; defaults to the core catalog. */
155
+ catalog?: Catalog<string>;
156
+ /** Status used for unbranded errors. Default 500. */
157
+ fallbackStatus?: number;
158
+ redactedMessage?: (status: number) => string;
159
+ /** Injectable wire codec for `data` → `details` (bigint/bytes etc.). */
160
+ encodeData?: (data: unknown) => unknown;
161
+ /** Default true. */
162
+ includeHint?: boolean;
163
+ }
164
+ /**
165
+ * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue
166
+ * reporting) builds its client-bound error content here, so the invariant
167
+ * "unbranded or internal-coded errors never echo their message" holds
168
+ * identically everywhere. `redacted: true` is the caller's signal to log the
169
+ * raw error server-side — this function never logs (zero-dep purity).
170
+ */
171
+ declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ErrorBodyResult;
172
+ //#endregion
173
+ //#region src/invariant.d.ts
174
+ /** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */
175
+ declare function invariant(condition: unknown, message: string, data?: unknown): asserts condition;
176
+ declare function unreachable(value: never, message?: string): never;
177
+ //#endregion
178
+ export { CORE_CATALOG, CORE_ENTRIES, type Catalog, type CoreErrorCode, type ErrorBodyResult, type ErrorCatalogEntry, STATUS_TO_CODE, type ToErrorBodyOptions, VelaError, type VelaErrorLike, type VelaErrorOptions, type WireErrorObject, composeCatalogs, defineErrorCatalog, invariant, isVelaError, toErrorBody, unreachable };
179
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,6 +1,197 @@
1
- export { VelaError } from "./error.js";
2
- export { CORE_ENTRIES } from "./catalog-data.js";
3
- export { CORE_CATALOG, STATUS_TO_CODE, composeCatalogs, defineErrorCatalog } from "./catalog.js";
4
- export { isVelaError } from "./guard.js";
5
- export { toErrorBody } from "./to-error-body.js";
6
- export { invariant, unreachable } from "./invariant.js";
1
+ //#region src/catalog-data.ts
2
+ const CORE_ENTRIES = {
3
+ bad_request: {
4
+ status: 400,
5
+ title: "Bad Request"
6
+ },
7
+ unauthorized: {
8
+ status: 401,
9
+ title: "Unauthorized"
10
+ },
11
+ forbidden: {
12
+ status: 403,
13
+ title: "Forbidden"
14
+ },
15
+ not_found: {
16
+ status: 404,
17
+ title: "Not Found"
18
+ },
19
+ method_not_allowed: {
20
+ status: 405,
21
+ title: "Method Not Allowed"
22
+ },
23
+ conflict: {
24
+ status: 409,
25
+ title: "Conflict"
26
+ },
27
+ gone: {
28
+ status: 410,
29
+ title: "Gone"
30
+ },
31
+ payload_too_large: {
32
+ status: 413,
33
+ title: "Payload Too Large"
34
+ },
35
+ unsupported_media_type: {
36
+ status: 415,
37
+ title: "Unsupported Media Type"
38
+ },
39
+ unprocessable: {
40
+ status: 422,
41
+ title: "Unprocessable Entity"
42
+ },
43
+ too_many_requests: {
44
+ status: 429,
45
+ title: "Too Many Requests"
46
+ },
47
+ internal: {
48
+ status: 500,
49
+ title: "Internal Server Error",
50
+ internal: true
51
+ },
52
+ not_implemented: {
53
+ status: 501,
54
+ title: "Not Implemented"
55
+ },
56
+ bad_gateway: {
57
+ status: 502,
58
+ title: "Bad Gateway"
59
+ },
60
+ service_unavailable: {
61
+ status: 503,
62
+ title: "Service Unavailable"
63
+ },
64
+ gateway_timeout: {
65
+ status: 504,
66
+ title: "Gateway Timeout"
67
+ }
68
+ };
69
+ //#endregion
70
+ //#region src/error.ts
71
+ /**
72
+ * The one Vela error. Every field is an OWN ENUMERABLE property so the error
73
+ * rides any wire codec / structuredClone / DO-RPC prop-copy with no special
74
+ * serialization path. `type` is the brand `isVelaError` checks — it must
75
+ * survive serialization, which own+enumerable guarantees.
76
+ */
77
+ var VelaError = class extends Error {
78
+ type = "VelaError";
79
+ code;
80
+ status;
81
+ hint;
82
+ docsUrl;
83
+ data;
84
+ constructor(code, options = {}) {
85
+ const entry = CORE_ENTRIES[code];
86
+ super(options.message ?? entry?.title ?? code, options.cause !== void 0 ? { cause: options.cause } : void 0);
87
+ this.name = "VelaError";
88
+ this.code = code;
89
+ this.status = options.status ?? entry?.status ?? 500;
90
+ const hint = options.hint ?? entry?.hint;
91
+ const docsUrl = options.docsUrl ?? entry?.docsUrl;
92
+ if (hint !== void 0) this.hint = hint;
93
+ if (docsUrl !== void 0) this.docsUrl = docsUrl;
94
+ if (options.data !== void 0) this.data = options.data;
95
+ Object.setPrototypeOf(this, new.target.prototype);
96
+ }
97
+ };
98
+ //#endregion
99
+ //#region src/catalog.ts
100
+ const makeCatalog = (entries) => ({
101
+ entries,
102
+ error(code, options = {}) {
103
+ const entry = entries[code];
104
+ const hint = options.hint ?? entry?.hint;
105
+ const docsUrl = options.docsUrl ?? entry?.docsUrl;
106
+ return new VelaError(code, {
107
+ ...options,
108
+ status: options.status ?? entry?.status ?? 500,
109
+ ...hint === void 0 ? {} : { hint },
110
+ ...docsUrl === void 0 ? {} : { docsUrl }
111
+ });
112
+ },
113
+ has: (code) => Object.hasOwn(entries, code),
114
+ get: (code) => Object.hasOwn(entries, code) ? entries[code] : void 0
115
+ });
116
+ const defineErrorCatalog = (entries) => makeCatalog(entries);
117
+ const composeCatalogs = (...catalogs) => {
118
+ const merged = {};
119
+ for (const catalog of catalogs) for (const [code, entry] of Object.entries(catalog.entries)) {
120
+ if (Object.hasOwn(merged, code)) throw new VelaError("internal", { message: `duplicate error code '${code}' while composing catalogs` });
121
+ merged[code] = entry;
122
+ }
123
+ return makeCatalog(merged);
124
+ };
125
+ const CORE_CATALOG = makeCatalog(CORE_ENTRIES);
126
+ const STATUS_TO_CODE = Object.fromEntries(Object.entries(CORE_ENTRIES).map(([code, e]) => [e.status, code]));
127
+ //#endregion
128
+ //#region src/guard.ts
129
+ const isVelaError = (error) => {
130
+ if (!(error instanceof Error)) return false;
131
+ const candidate = error;
132
+ return typeof candidate.code === "string" && typeof candidate.status === "number" && candidate.type === "VelaError";
133
+ };
134
+ //#endregion
135
+ //#region src/to-error-body.ts
136
+ const defaultRedactedMessage = (status, catalog) => {
137
+ const code = STATUS_TO_CODE[status];
138
+ return code && catalog.get(code)?.title || "Internal Server Error";
139
+ };
140
+ /**
141
+ * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue
142
+ * reporting) builds its client-bound error content here, so the invariant
143
+ * "unbranded or internal-coded errors never echo their message" holds
144
+ * identically everywhere. `redacted: true` is the caller's signal to log the
145
+ * raw error server-side — this function never logs (zero-dep purity).
146
+ */
147
+ const toErrorBody = (error, options = {}) => {
148
+ const catalog = options.catalog ?? CORE_CATALOG;
149
+ const message = options.redactedMessage ?? ((s) => defaultRedactedMessage(s, catalog));
150
+ const redact = (status, code) => ({
151
+ body: { error: {
152
+ code,
153
+ message: message(status)
154
+ } },
155
+ status,
156
+ redacted: true
157
+ });
158
+ if (!isVelaError(error)) {
159
+ const status = options.fallbackStatus ?? 500;
160
+ return redact(status, STATUS_TO_CODE[status] ?? "internal");
161
+ }
162
+ const entry = catalog.get(error.code);
163
+ if (error.code === "internal" || entry?.internal === true) return redact(error.status, error.code);
164
+ const wire = {
165
+ code: error.code,
166
+ message: error.message
167
+ };
168
+ const hint = error.hint ?? entry?.hint;
169
+ if (options.includeHint !== false && hint !== void 0) wire.hint = hint;
170
+ const docsUrl = error.docsUrl ?? entry?.docsUrl;
171
+ if (docsUrl !== void 0) wire.docsUrl = docsUrl;
172
+ if (error.data !== void 0) wire.details = options.encodeData ? options.encodeData(error.data) : error.data;
173
+ return {
174
+ body: { error: wire },
175
+ status: error.status,
176
+ redacted: false
177
+ };
178
+ };
179
+ //#endregion
180
+ //#region src/invariant.ts
181
+ /** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */
182
+ function invariant(condition, message, data) {
183
+ if (!condition) throw new VelaError("internal", {
184
+ message: `Invariant violation: ${message}`,
185
+ data
186
+ });
187
+ }
188
+ function unreachable(value, message = "unreachable code reached") {
189
+ throw new VelaError("internal", {
190
+ message,
191
+ data: { value }
192
+ });
193
+ }
194
+ //#endregion
195
+ export { CORE_CATALOG, CORE_ENTRIES, STATUS_TO_CODE, VelaError, composeCatalogs, defineErrorCatalog, invariant, isVelaError, toErrorBody, unreachable };
196
+
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/catalog-data.ts","../src/error.ts","../src/catalog.ts","../src/guard.ts","../src/to-error-body.ts","../src/invariant.ts"],"sourcesContent":["export interface ErrorCatalogEntry {\n status: number;\n title: string;\n hint?: string;\n docsUrl?: string;\n /** Redaction posture: true → message/hint/data are never echoed to clients. */\n internal?: boolean;\n}\n\nexport const CORE_ENTRIES = {\n bad_request: { status: 400, title: 'Bad Request' },\n unauthorized: { status: 401, title: 'Unauthorized' },\n forbidden: { status: 403, title: 'Forbidden' },\n not_found: { status: 404, title: 'Not Found' },\n method_not_allowed: { status: 405, title: 'Method Not Allowed' },\n conflict: { status: 409, title: 'Conflict' },\n gone: { status: 410, title: 'Gone' },\n payload_too_large: { status: 413, title: 'Payload Too Large' },\n unsupported_media_type: { status: 415, title: 'Unsupported Media Type' },\n unprocessable: { status: 422, title: 'Unprocessable Entity' },\n too_many_requests: { status: 429, title: 'Too Many Requests' },\n internal: { status: 500, title: 'Internal Server Error', internal: true },\n not_implemented: { status: 501, title: 'Not Implemented' },\n bad_gateway: { status: 502, title: 'Bad Gateway' },\n service_unavailable: { status: 503, title: 'Service Unavailable' },\n gateway_timeout: { status: 504, title: 'Gateway Timeout' },\n} as const satisfies Record<string, ErrorCatalogEntry>;\n\nexport type CoreErrorCode = keyof typeof CORE_ENTRIES;\n","import { CORE_ENTRIES, type CoreErrorCode } from './catalog-data';\n\nexport interface VelaErrorOptions {\n message?: string;\n status?: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n cause?: unknown;\n}\n\n/**\n * The one Vela error. Every field is an OWN ENUMERABLE property so the error\n * rides any wire codec / structuredClone / DO-RPC prop-copy with no special\n * serialization path. `type` is the brand `isVelaError` checks — it must\n * survive serialization, which own+enumerable guarantees.\n */\nexport class VelaError extends Error {\n readonly type = 'VelaError';\n readonly code: string;\n readonly status: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n readonly data?: unknown;\n\n constructor(code: CoreErrorCode, options?: VelaErrorOptions);\n constructor(code: string, options: VelaErrorOptions & { status: number });\n constructor(code: string, options: VelaErrorOptions = {}) {\n const entry = (\n CORE_ENTRIES as Record<\n string,\n { status: number; title: string; hint?: string; docsUrl?: string }\n >\n )[code];\n super(\n options.message ?? entry?.title ?? code,\n options.cause !== undefined ? { cause: options.cause } : undefined,\n );\n this.name = 'VelaError';\n this.code = code;\n this.status = options.status ?? entry?.status ?? 500;\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n if (hint !== undefined) this.hint = hint;\n if (docsUrl !== undefined) this.docsUrl = docsUrl;\n if (options.data !== undefined) this.data = options.data;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { CORE_ENTRIES, type CoreErrorCode, type ErrorCatalogEntry } from './catalog-data';\nimport { VelaError, type VelaErrorOptions } from './error';\n\nexport type { CoreErrorCode, ErrorCatalogEntry } from './catalog-data';\nexport { CORE_ENTRIES } from './catalog-data';\n\nexport interface Catalog<C extends string = string> {\n readonly entries: Readonly<Record<C, ErrorCatalogEntry>>;\n /** Typed thrower bound to this catalog's defaults. */\n error(code: C | (string & {}), options?: VelaErrorOptions): VelaError;\n has(code: string): boolean;\n get(code: string): ErrorCatalogEntry | undefined;\n}\n\nconst makeCatalog = <C extends string>(\n entries: Readonly<Record<C, ErrorCatalogEntry>>,\n): Catalog<C> => ({\n entries,\n error(code, options = {}) {\n const entry = (entries as Record<string, ErrorCatalogEntry>)[code];\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n return new VelaError(code, {\n ...options,\n status: options.status ?? entry?.status ?? 500,\n ...(hint === undefined ? {} : { hint }),\n ...(docsUrl === undefined ? {} : { docsUrl }),\n });\n },\n has: (code) => Object.hasOwn(entries, code),\n get: (code) =>\n Object.hasOwn(entries, code) ? (entries as Record<string, ErrorCatalogEntry>)[code] : undefined,\n});\n\nexport const defineErrorCatalog = <const T extends Record<string, ErrorCatalogEntry>>(\n entries: T,\n): Catalog<Extract<keyof T, string>> => makeCatalog(entries);\n\nexport const composeCatalogs = (...catalogs: Array<Catalog<string>>): Catalog<string> => {\n const merged: Record<string, ErrorCatalogEntry> = {};\n for (const catalog of catalogs) {\n for (const [code, entry] of Object.entries<ErrorCatalogEntry>(catalog.entries)) {\n if (Object.hasOwn(merged, code)) {\n throw new VelaError('internal', {\n message: `duplicate error code '${code}' while composing catalogs`,\n });\n }\n merged[code] = entry;\n }\n }\n return makeCatalog(merged);\n};\n\nexport const CORE_CATALOG: Catalog<CoreErrorCode> = makeCatalog(CORE_ENTRIES);\n\nexport const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>> = Object.fromEntries(\n (Object.entries(CORE_ENTRIES) as Array<[CoreErrorCode, ErrorCatalogEntry]>).map(([code, e]) => [\n e.status,\n code,\n ]),\n) as Record<number, CoreErrorCode>;\n","/**\n * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable\n * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape\n * check lets foreign driver errors ride the client-echo path. The brand\n * (`type === 'VelaError'`, an own enumerable prop that survives serialization)\n * closes both failure modes. Nothing load-bearing may use `instanceof`.\n */\nexport interface VelaErrorLike extends Error {\n type: 'VelaError';\n code: string;\n status: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n}\n\nexport const isVelaError = (error: unknown): error is VelaErrorLike => {\n if (!(error instanceof Error)) return false;\n const candidate = error as Partial<VelaErrorLike>;\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.status === 'number' &&\n candidate.type === 'VelaError'\n );\n};\n","import { CORE_CATALOG, STATUS_TO_CODE, type Catalog } from './catalog';\nimport { isVelaError } from './guard';\n\nexport interface WireErrorObject {\n code: string;\n message: string;\n hint?: string;\n docsUrl?: string;\n details?: unknown;\n}\n\nexport interface ErrorBodyResult {\n body: { error: WireErrorObject };\n status: number;\n redacted: boolean;\n}\n\nexport interface ToErrorBodyOptions {\n /** Composed catalog; defaults to the core catalog. */\n catalog?: Catalog<string>;\n /** Status used for unbranded errors. Default 500. */\n fallbackStatus?: number;\n redactedMessage?: (status: number) => string;\n /** Injectable wire codec for `data` → `details` (bigint/bytes etc.). */\n encodeData?: (data: unknown) => unknown;\n /** Default true. */\n includeHint?: boolean;\n}\n\nconst defaultRedactedMessage = (status: number, catalog: Catalog<string>): string => {\n const code = STATUS_TO_CODE[status];\n return (code && catalog.get(code)?.title) || 'Internal Server Error';\n};\n\n/**\n * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue\n * reporting) builds its client-bound error content here, so the invariant\n * \"unbranded or internal-coded errors never echo their message\" holds\n * identically everywhere. `redacted: true` is the caller's signal to log the\n * raw error server-side — this function never logs (zero-dep purity).\n */\nexport const toErrorBody = (error: unknown, options: ToErrorBodyOptions = {}): ErrorBodyResult => {\n const catalog = options.catalog ?? CORE_CATALOG;\n const message = options.redactedMessage ?? ((s: number) => defaultRedactedMessage(s, catalog));\n\n const redact = (status: number, code: string): ErrorBodyResult => ({\n body: { error: { code, message: message(status) } },\n status,\n redacted: true,\n });\n\n if (!isVelaError(error)) {\n const status = options.fallbackStatus ?? 500;\n return redact(status, STATUS_TO_CODE[status] ?? 'internal');\n }\n\n const entry = catalog.get(error.code);\n if (error.code === 'internal' || entry?.internal === true) {\n return redact(error.status, error.code);\n }\n\n const wire: WireErrorObject = { code: error.code, message: error.message };\n const hint = error.hint ?? entry?.hint;\n if (options.includeHint !== false && hint !== undefined) wire.hint = hint;\n const docsUrl = error.docsUrl ?? entry?.docsUrl;\n if (docsUrl !== undefined) wire.docsUrl = docsUrl;\n if (error.data !== undefined)\n wire.details = options.encodeData ? options.encodeData(error.data) : error.data;\n return { body: { error: wire }, status: error.status, redacted: false };\n};\n","import { VelaError } from './error';\n\n/** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */\nexport function invariant(condition: unknown, message: string, data?: unknown): asserts condition {\n if (!condition) {\n throw new VelaError('internal', { message: `Invariant violation: ${message}`, data });\n }\n}\n\nexport function unreachable(value: never, message = 'unreachable code reached'): never {\n throw new VelaError('internal', { message, data: { value } });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,53 +1,65 @@
1
1
  {
2
2
  "name": "@velajs/errors",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Unified error layer for Vela: branded VelaError, composable error catalogs, and the single toErrorBody wire-redaction seam",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist",
16
- "README.md",
17
- "LICENSE",
18
- "CHANGELOG.md"
19
- ],
20
- "sideEffects": false,
21
5
  "keywords": [
22
- "vela",
23
- "errors",
24
6
  "error-catalog",
7
+ "errors",
8
+ "framework",
25
9
  "redaction",
26
- "framework"
10
+ "vela"
27
11
  ],
28
- "author": "ksh",
12
+ "homepage": "https://github.com/velajs/errors#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/velajs/errors/issues"
15
+ },
29
16
  "license": "MIT",
17
+ "author": "ksh",
30
18
  "repository": {
31
19
  "type": "git",
32
20
  "url": "git+https://github.com/velajs/errors.git"
33
21
  },
34
- "homepage": "https://github.com/velajs/errors#readme",
35
- "bugs": {
36
- "url": "https://github.com/velajs/errors/issues"
37
- },
38
- "engines": {
39
- "node": ">=20"
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
40
37
  },
41
38
  "devDependencies": {
42
- "@swc/cli": "^0.8.1",
43
- "@swc/core": "^1.15.43",
44
- "typescript": "^6.0.3",
45
- "unplugin-swc": "^1.5.9",
46
- "vitest": "^4.1.9"
39
+ "@arethetypeswrong/cli": "^0.18.5",
40
+ "@changesets/cli": "^2.31.0",
41
+ "oxfmt": "^0.58.0",
42
+ "oxlint": "^1.73.0",
43
+ "publint": "^0.3.21",
44
+ "tsdown": "^0.22.4",
45
+ "typescript": "^7.0.2",
46
+ "vitest": "^4.1.10"
47
+ },
48
+ "engines": {
49
+ "node": ">=24"
47
50
  },
48
51
  "scripts": {
49
- "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
52
+ "build": "tsdown",
50
53
  "test": "vitest run",
51
- "typecheck": "tsc --noEmit -p tsconfig.test.json"
54
+ "typecheck": "tsc --noEmit -p tsconfig.test.json",
55
+ "lint": "oxlint .",
56
+ "format": "oxfmt .",
57
+ "format:check": "oxfmt --check .",
58
+ "publint": "publint",
59
+ "attw": "attw --pack . --profile esm-only",
60
+ "changeset": "changeset",
61
+ "version-packages": "changeset version",
62
+ "release": "pnpm build && changeset publish",
63
+ "verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
52
64
  }
53
65
  }
@@ -1,76 +0,0 @@
1
- export interface ErrorCatalogEntry {
2
- status: number;
3
- title: string;
4
- hint?: string;
5
- docsUrl?: string;
6
- /** Redaction posture: true → message/hint/data are never echoed to clients. */
7
- internal?: boolean;
8
- }
9
- export declare const CORE_ENTRIES: {
10
- readonly bad_request: {
11
- readonly status: 400;
12
- readonly title: "Bad Request";
13
- };
14
- readonly unauthorized: {
15
- readonly status: 401;
16
- readonly title: "Unauthorized";
17
- };
18
- readonly forbidden: {
19
- readonly status: 403;
20
- readonly title: "Forbidden";
21
- };
22
- readonly not_found: {
23
- readonly status: 404;
24
- readonly title: "Not Found";
25
- };
26
- readonly method_not_allowed: {
27
- readonly status: 405;
28
- readonly title: "Method Not Allowed";
29
- };
30
- readonly conflict: {
31
- readonly status: 409;
32
- readonly title: "Conflict";
33
- };
34
- readonly gone: {
35
- readonly status: 410;
36
- readonly title: "Gone";
37
- };
38
- readonly payload_too_large: {
39
- readonly status: 413;
40
- readonly title: "Payload Too Large";
41
- };
42
- readonly unsupported_media_type: {
43
- readonly status: 415;
44
- readonly title: "Unsupported Media Type";
45
- };
46
- readonly unprocessable: {
47
- readonly status: 422;
48
- readonly title: "Unprocessable Entity";
49
- };
50
- readonly too_many_requests: {
51
- readonly status: 429;
52
- readonly title: "Too Many Requests";
53
- };
54
- readonly internal: {
55
- readonly status: 500;
56
- readonly title: "Internal Server Error";
57
- readonly internal: true;
58
- };
59
- readonly not_implemented: {
60
- readonly status: 501;
61
- readonly title: "Not Implemented";
62
- };
63
- readonly bad_gateway: {
64
- readonly status: 502;
65
- readonly title: "Bad Gateway";
66
- };
67
- readonly service_unavailable: {
68
- readonly status: 503;
69
- readonly title: "Service Unavailable";
70
- };
71
- readonly gateway_timeout: {
72
- readonly status: 504;
73
- readonly title: "Gateway Timeout";
74
- };
75
- };
76
- export type CoreErrorCode = keyof typeof CORE_ENTRIES;
@@ -1,67 +0,0 @@
1
- export const CORE_ENTRIES = {
2
- bad_request: {
3
- status: 400,
4
- title: 'Bad Request'
5
- },
6
- unauthorized: {
7
- status: 401,
8
- title: 'Unauthorized'
9
- },
10
- forbidden: {
11
- status: 403,
12
- title: 'Forbidden'
13
- },
14
- not_found: {
15
- status: 404,
16
- title: 'Not Found'
17
- },
18
- method_not_allowed: {
19
- status: 405,
20
- title: 'Method Not Allowed'
21
- },
22
- conflict: {
23
- status: 409,
24
- title: 'Conflict'
25
- },
26
- gone: {
27
- status: 410,
28
- title: 'Gone'
29
- },
30
- payload_too_large: {
31
- status: 413,
32
- title: 'Payload Too Large'
33
- },
34
- unsupported_media_type: {
35
- status: 415,
36
- title: 'Unsupported Media Type'
37
- },
38
- unprocessable: {
39
- status: 422,
40
- title: 'Unprocessable Entity'
41
- },
42
- too_many_requests: {
43
- status: 429,
44
- title: 'Too Many Requests'
45
- },
46
- internal: {
47
- status: 500,
48
- title: 'Internal Server Error',
49
- internal: true
50
- },
51
- not_implemented: {
52
- status: 501,
53
- title: 'Not Implemented'
54
- },
55
- bad_gateway: {
56
- status: 502,
57
- title: 'Bad Gateway'
58
- },
59
- service_unavailable: {
60
- status: 503,
61
- title: 'Service Unavailable'
62
- },
63
- gateway_timeout: {
64
- status: 504,
65
- title: 'Gateway Timeout'
66
- }
67
- };
package/dist/catalog.d.ts DELETED
@@ -1,15 +0,0 @@
1
- import { type CoreErrorCode, type ErrorCatalogEntry } from './catalog-data';
2
- import { VelaError, type VelaErrorOptions } from './error';
3
- export type { CoreErrorCode, ErrorCatalogEntry } from './catalog-data';
4
- export { CORE_ENTRIES } from './catalog-data';
5
- export interface Catalog<C extends string = string> {
6
- readonly entries: Readonly<Record<C, ErrorCatalogEntry>>;
7
- /** Typed thrower bound to this catalog's defaults. */
8
- error(code: C | (string & {}), options?: VelaErrorOptions): VelaError;
9
- has(code: string): boolean;
10
- get(code: string): ErrorCatalogEntry | undefined;
11
- }
12
- export declare const defineErrorCatalog: <const T extends Record<string, ErrorCatalogEntry>>(entries: T) => Catalog<Extract<keyof T, string>>;
13
- export declare const composeCatalogs: (...catalogs: Array<Catalog<string>>) => Catalog<string>;
14
- export declare const CORE_CATALOG: Catalog<CoreErrorCode>;
15
- export declare const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>>;
package/dist/catalog.js DELETED
@@ -1,37 +0,0 @@
1
- import { CORE_ENTRIES } from "./catalog-data.js";
2
- import { VelaError } from "./error.js";
3
- export { CORE_ENTRIES } from "./catalog-data.js";
4
- const makeCatalog = (entries)=>({
5
- entries,
6
- error (code, options = {}) {
7
- const entry = entries[code];
8
- return new VelaError(code, {
9
- ...options,
10
- status: options.status ?? entry?.status ?? 500,
11
- hint: options.hint ?? entry?.hint,
12
- docsUrl: options.docsUrl ?? entry?.docsUrl
13
- });
14
- },
15
- has: (code)=>Object.hasOwn(entries, code),
16
- get: (code)=>Object.hasOwn(entries, code) ? entries[code] : undefined
17
- });
18
- export const defineErrorCatalog = (entries)=>makeCatalog(entries);
19
- export const composeCatalogs = (...catalogs)=>{
20
- const merged = {};
21
- for (const catalog of catalogs){
22
- for (const [code, entry] of Object.entries(catalog.entries)){
23
- if (Object.hasOwn(merged, code)) {
24
- throw new VelaError('internal', {
25
- message: `duplicate error code '${code}' while composing catalogs`
26
- });
27
- }
28
- merged[code] = entry;
29
- }
30
- }
31
- return makeCatalog(merged);
32
- };
33
- export const CORE_CATALOG = makeCatalog(CORE_ENTRIES);
34
- export const STATUS_TO_CODE = Object.fromEntries(Object.entries(CORE_ENTRIES).map(([code, e])=>[
35
- e.status,
36
- code
37
- ]));
package/dist/error.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { type CoreErrorCode } from './catalog-data';
2
- export interface VelaErrorOptions {
3
- message?: string;
4
- status?: number;
5
- hint?: string;
6
- docsUrl?: string;
7
- data?: unknown;
8
- cause?: unknown;
9
- }
10
- /**
11
- * The one Vela error. Every field is an OWN ENUMERABLE property so the error
12
- * rides any wire codec / structuredClone / DO-RPC prop-copy with no special
13
- * serialization path. `type` is the brand `isVelaError` checks — it must
14
- * survive serialization, which own+enumerable guarantees.
15
- */
16
- export declare class VelaError extends Error {
17
- readonly type = "VelaError";
18
- readonly code: string;
19
- readonly status: number;
20
- readonly hint?: string;
21
- readonly docsUrl?: string;
22
- readonly data?: unknown;
23
- constructor(code: CoreErrorCode, options?: VelaErrorOptions);
24
- constructor(code: string, options: VelaErrorOptions & {
25
- status: number;
26
- });
27
- }
package/dist/error.js DELETED
@@ -1,27 +0,0 @@
1
- import { CORE_ENTRIES } from "./catalog-data.js";
2
- /**
3
- * The one Vela error. Every field is an OWN ENUMERABLE property so the error
4
- * rides any wire codec / structuredClone / DO-RPC prop-copy with no special
5
- * serialization path. `type` is the brand `isVelaError` checks — it must
6
- * survive serialization, which own+enumerable guarantees.
7
- */ export class VelaError extends Error {
8
- type = 'VelaError';
9
- code;
10
- status;
11
- hint;
12
- docsUrl;
13
- data;
14
- constructor(code, options = {}){
15
- const entry = CORE_ENTRIES[code];
16
- super(options.message ?? entry?.title ?? code, options.cause !== undefined ? {
17
- cause: options.cause
18
- } : undefined);
19
- this.name = 'VelaError';
20
- this.code = code;
21
- this.status = options.status ?? entry?.status ?? 500;
22
- if (options.hint ?? entry?.hint) this.hint = options.hint ?? entry?.hint;
23
- if (options.docsUrl ?? entry?.docsUrl) this.docsUrl = options.docsUrl ?? entry?.docsUrl;
24
- if (options.data !== undefined) this.data = options.data;
25
- Object.setPrototypeOf(this, new.target.prototype);
26
- }
27
- }
package/dist/guard.d.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable
3
- * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape
4
- * check lets foreign driver errors ride the client-echo path. The brand
5
- * (`type === 'VelaError'`, an own enumerable prop that survives serialization)
6
- * closes both failure modes. Nothing load-bearing may use `instanceof`.
7
- */
8
- export interface VelaErrorLike extends Error {
9
- type: 'VelaError';
10
- code: string;
11
- status: number;
12
- hint?: string;
13
- docsUrl?: string;
14
- data?: unknown;
15
- }
16
- export declare const isVelaError: (error: unknown) => error is VelaErrorLike;
package/dist/guard.js DELETED
@@ -1,11 +0,0 @@
1
- /**
2
- * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable
3
- * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape
4
- * check lets foreign driver errors ride the client-echo path. The brand
5
- * (`type === 'VelaError'`, an own enumerable prop that survives serialization)
6
- * closes both failure modes. Nothing load-bearing may use `instanceof`.
7
- */ export const isVelaError = (error)=>{
8
- if (!(error instanceof Error)) return false;
9
- const candidate = error;
10
- return typeof candidate.code === 'string' && typeof candidate.status === 'number' && candidate.type === 'VelaError';
11
- };
@@ -1,3 +0,0 @@
1
- /** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */
2
- export declare function invariant(condition: unknown, message: string, data?: unknown): asserts condition;
3
- export declare function unreachable(value: never, message?: string): never;
package/dist/invariant.js DELETED
@@ -1,17 +0,0 @@
1
- import { VelaError } from "./error.js";
2
- /** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */ export function invariant(condition, message, data) {
3
- if (!condition) {
4
- throw new VelaError('internal', {
5
- message: `Invariant violation: ${message}`,
6
- data
7
- });
8
- }
9
- }
10
- export function unreachable(value, message = 'unreachable code reached') {
11
- throw new VelaError('internal', {
12
- message,
13
- data: {
14
- value
15
- }
16
- });
17
- }
@@ -1,34 +0,0 @@
1
- import { type Catalog } from './catalog';
2
- export interface WireErrorObject {
3
- code: string;
4
- message: string;
5
- hint?: string;
6
- docsUrl?: string;
7
- details?: unknown;
8
- }
9
- export interface ErrorBodyResult {
10
- body: {
11
- error: WireErrorObject;
12
- };
13
- status: number;
14
- redacted: boolean;
15
- }
16
- export interface ToErrorBodyOptions {
17
- /** Composed catalog; defaults to the core catalog. */
18
- catalog?: Catalog<string>;
19
- /** Status used for unbranded errors. Default 500. */
20
- fallbackStatus?: number;
21
- redactedMessage?: (status: number) => string;
22
- /** Injectable wire codec for `data` → `details` (bigint/bytes etc.). */
23
- encodeData?: (data: unknown) => unknown;
24
- /** Default true. */
25
- includeHint?: boolean;
26
- }
27
- /**
28
- * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue
29
- * reporting) builds its client-bound error content here, so the invariant
30
- * "unbranded or internal-coded errors never echo their message" holds
31
- * identically everywhere. `redacted: true` is the caller's signal to log the
32
- * raw error server-side — this function never logs (zero-dep purity).
33
- */
34
- export declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ErrorBodyResult;
@@ -1,50 +0,0 @@
1
- import { CORE_CATALOG, STATUS_TO_CODE } from "./catalog.js";
2
- import { isVelaError } from "./guard.js";
3
- const defaultRedactedMessage = (status, catalog)=>{
4
- const code = STATUS_TO_CODE[status];
5
- return code && catalog.get(code)?.title || 'Internal Server Error';
6
- };
7
- /**
8
- * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue
9
- * reporting) builds its client-bound error content here, so the invariant
10
- * "unbranded or internal-coded errors never echo their message" holds
11
- * identically everywhere. `redacted: true` is the caller's signal to log the
12
- * raw error server-side — this function never logs (zero-dep purity).
13
- */ export const toErrorBody = (error, options = {})=>{
14
- const catalog = options.catalog ?? CORE_CATALOG;
15
- const message = options.redactedMessage ?? ((s)=>defaultRedactedMessage(s, catalog));
16
- const redact = (status, code)=>({
17
- body: {
18
- error: {
19
- code,
20
- message: message(status)
21
- }
22
- },
23
- status,
24
- redacted: true
25
- });
26
- if (!isVelaError(error)) {
27
- const status = options.fallbackStatus ?? 500;
28
- return redact(status, STATUS_TO_CODE[status] ?? 'internal');
29
- }
30
- const entry = catalog.get(error.code);
31
- if (error.code === 'internal' || entry?.internal === true) {
32
- return redact(error.status, error.code);
33
- }
34
- const wire = {
35
- code: error.code,
36
- message: error.message
37
- };
38
- const hint = error.hint ?? entry?.hint;
39
- if (options.includeHint !== false && hint !== undefined) wire.hint = hint;
40
- const docsUrl = error.docsUrl ?? entry?.docsUrl;
41
- if (docsUrl !== undefined) wire.docsUrl = docsUrl;
42
- if (error.data !== undefined) wire.details = options.encodeData ? options.encodeData(error.data) : error.data;
43
- return {
44
- body: {
45
- error: wire
46
- },
47
- status: error.status,
48
- redacted: false
49
- };
50
- };