@codenhub/error 0.0.1 → 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.
@@ -1,322 +0,0 @@
1
- //#region src/registry.ts
2
- const ERROR_IDENTIFIER_TRAILING_PUNCTUATION_PATTERN = /[.!?]+$/;
3
- const normalizeErrorIdentifier = (identifier) => {
4
- return identifier.trim().replace(ERROR_IDENTIFIER_TRAILING_PUNCTUATION_PATTERN, "");
5
- };
6
- const assertNonEmptyIdentifier = (identifier, label) => {
7
- if (typeof identifier !== "string" || normalizeErrorIdentifier(identifier).length === 0) throw new TypeError(`Error registry ${label} must be a non-empty string.`);
8
- };
9
- const assertFeedback = (feedback) => {
10
- if (typeof feedback !== "object" || feedback === null) throw new TypeError("Error registry feedback must be an object.");
11
- if (typeof feedback.message !== "string" || feedback.message.trim().length === 0) throw new TypeError("Error registry feedback.message must be a non-empty string.");
12
- if (feedback.messageKey !== void 0 && typeof feedback.messageKey !== "string") throw new TypeError("Error registry feedback.messageKey must be a string when provided.");
13
- if (feedback.source !== void 0 && typeof feedback.source !== "string") throw new TypeError("Error registry feedback.source must be a string when provided.");
14
- if (feedback.retryable !== void 0 && typeof feedback.retryable !== "boolean") throw new TypeError("Error registry feedback.retryable must be a boolean when provided.");
15
- };
16
- const cloneFeedback = (feedback) => ({ ...feedback });
17
- const createFeedbackMapBucket = () => {
18
- const entries = /* @__PURE__ */ new Map();
19
- const add = (identifier, feedback) => {
20
- assertNonEmptyIdentifier(identifier, "identifier");
21
- assertFeedback(feedback);
22
- entries.set(normalizeErrorIdentifier(identifier), cloneFeedback(feedback));
23
- };
24
- return {
25
- add,
26
- addList(errorEntries) {
27
- for (const [identifier, feedback] of errorEntries) add(identifier, feedback);
28
- },
29
- clear() {
30
- entries.clear();
31
- },
32
- get(identifier) {
33
- const feedback = entries.get(normalizeErrorIdentifier(identifier));
34
- return feedback === void 0 ? void 0 : cloneFeedback(feedback);
35
- },
36
- values() {
37
- return Array.from(entries.entries(), ([identifier, feedback]) => [identifier, cloneFeedback(feedback)]).values();
38
- }
39
- };
40
- };
41
- const createPrefixBucket = () => {
42
- const entries = [];
43
- const add = (prefix, feedback) => {
44
- assertNonEmptyIdentifier(prefix, "prefix");
45
- assertFeedback(feedback);
46
- entries.push({
47
- ...cloneFeedback(feedback),
48
- prefix: normalizeErrorIdentifier(prefix)
49
- });
50
- };
51
- return {
52
- add,
53
- addList(errorEntries) {
54
- for (const [prefix, feedback] of errorEntries) add(prefix, feedback);
55
- },
56
- clear() {
57
- entries.length = 0;
58
- },
59
- values() {
60
- return entries.map((entry) => ({ ...entry }));
61
- }
62
- };
63
- };
64
- const createPatternBucket = () => {
65
- const entries = [];
66
- const add = (pattern, feedback) => {
67
- if (!(pattern instanceof RegExp)) throw new TypeError("Error registry pattern must be a RegExp.");
68
- assertFeedback(feedback);
69
- entries.push({
70
- ...cloneFeedback(feedback),
71
- pattern: new RegExp(pattern.source, pattern.flags)
72
- });
73
- };
74
- return {
75
- add,
76
- addList(errorEntries) {
77
- for (const [pattern, feedback] of errorEntries) add(pattern, feedback);
78
- },
79
- clear() {
80
- entries.length = 0;
81
- },
82
- values() {
83
- return entries.map((entry) => ({
84
- ...entry,
85
- pattern: new RegExp(entry.pattern.source, entry.pattern.flags)
86
- }));
87
- }
88
- };
89
- };
90
- /** Creates an empty, isolated registry for classifying unknown errors. */
91
- const createErrorRegistry = () => {
92
- const codes = createFeedbackMapBucket();
93
- const names = createFeedbackMapBucket();
94
- const messages = createFeedbackMapBucket();
95
- const prefixes = createPrefixBucket();
96
- const patterns = createPatternBucket();
97
- return {
98
- codes,
99
- names,
100
- messages,
101
- prefixes,
102
- patterns,
103
- clear() {
104
- codes.clear();
105
- names.clear();
106
- messages.clear();
107
- prefixes.clear();
108
- patterns.clear();
109
- },
110
- merge(sourceRegistry) {
111
- for (const [identifier, feedback] of sourceRegistry.codes.values()) codes.add(identifier, feedback);
112
- for (const [identifier, feedback] of sourceRegistry.names.values()) names.add(identifier, feedback);
113
- for (const [identifier, feedback] of sourceRegistry.messages.values()) messages.add(identifier, feedback);
114
- for (const { prefix, ...feedback } of sourceRegistry.prefixes.values()) prefixes.add(prefix, feedback);
115
- for (const { pattern, ...feedback } of sourceRegistry.patterns.values()) patterns.add(pattern, feedback);
116
- }
117
- };
118
- };
119
- //#endregion
120
- //#region src/normalize.ts
121
- const ERROR_UNWRAP_MAX_DEPTH = 3;
122
- const ERROR_WRAPPER_FIELD_NAMES = [
123
- "cause",
124
- "originalError",
125
- "error"
126
- ];
127
- const isRecord = (value) => {
128
- return typeof value === "object" && value !== null;
129
- };
130
- const getRecordField = (source, key) => {
131
- try {
132
- return source[key];
133
- } catch {
134
- return;
135
- }
136
- };
137
- const getStringField = (source, key) => {
138
- const value = getRecordField(source, key);
139
- return typeof value === "string" ? value : null;
140
- };
141
- const toKnownClassification = (feedback) => ({
142
- type: "known",
143
- message: feedback.message,
144
- messageKey: feedback.messageKey ?? null,
145
- source: feedback.source ?? null,
146
- retryable: feedback.retryable ?? false
147
- });
148
- const toUnexpectedClassification = (feedback) => ({
149
- type: "unexpected",
150
- message: feedback.message,
151
- messageKey: feedback.messageKey ?? null,
152
- source: feedback.source ?? null,
153
- retryable: feedback.retryable ?? false
154
- });
155
- const normalizeError = (error) => {
156
- if (typeof error === "string") return {
157
- code: null,
158
- message: error,
159
- name: null
160
- };
161
- if (!isRecord(error)) return {
162
- code: null,
163
- message: null,
164
- name: null
165
- };
166
- return {
167
- code: getStringField(error, "code"),
168
- message: getStringField(error, "message"),
169
- name: getStringField(error, "name")
170
- };
171
- };
172
- const getWrappedErrorCandidates = (error) => {
173
- if (!isRecord(error)) return [];
174
- return ERROR_WRAPPER_FIELD_NAMES.map((fieldName) => getRecordField(error, fieldName)).filter((value) => value !== void 0 && value !== null);
175
- };
176
- const getErrorCandidates = (error) => {
177
- const candidates = [];
178
- const visitedObjects = /* @__PURE__ */ new Set();
179
- if (isRecord(error)) visitedObjects.add(error);
180
- const pendingCandidates = [{
181
- depth: 0,
182
- value: error
183
- }];
184
- for (let index = 0; index < pendingCandidates.length; index += 1) {
185
- const candidate = pendingCandidates[index];
186
- candidates.push(candidate.value);
187
- if (candidate.depth >= ERROR_UNWRAP_MAX_DEPTH) continue;
188
- for (const wrappedErrorCandidate of getWrappedErrorCandidates(candidate.value)) {
189
- if (isRecord(wrappedErrorCandidate)) {
190
- if (visitedObjects.has(wrappedErrorCandidate)) continue;
191
- visitedObjects.add(wrappedErrorCandidate);
192
- }
193
- pendingCandidates.push({
194
- depth: candidate.depth + 1,
195
- value: wrappedErrorCandidate
196
- });
197
- }
198
- }
199
- return candidates;
200
- };
201
- const getKnownMessageFeedback = (registry, message) => {
202
- const normalizedMessage = normalizeErrorIdentifier(message);
203
- for (const [registeredMessage, feedback] of registry.messages.values()) if (normalizeErrorIdentifier(registeredMessage) === normalizedMessage) return toKnownClassification(feedback);
204
- const prefixDefinition = registry.prefixes.values().filter((definition) => normalizedMessage.startsWith(definition.prefix)).sort((firstDefinition, secondDefinition) => secondDefinition.prefix.length - firstDefinition.prefix.length)[0];
205
- if (prefixDefinition === void 0) return null;
206
- return toKnownClassification(prefixDefinition);
207
- };
208
- const resolveDeterministicKnownError = (registry, { code, message, name }) => {
209
- if (code !== null) {
210
- const feedback = registry.codes.get(code);
211
- if (feedback !== void 0) return toKnownClassification(feedback);
212
- }
213
- if (name !== null) {
214
- const feedback = registry.names.get(name);
215
- if (feedback !== void 0) return toKnownClassification(feedback);
216
- }
217
- if (message !== null) {
218
- const feedback = getKnownMessageFeedback(registry, message);
219
- if (feedback !== null) return feedback;
220
- }
221
- return null;
222
- };
223
- const resolveHeuristicUnexpectedError = (registry, { message }) => {
224
- if (message === null) return null;
225
- const definition = registry.patterns.values().find((currentDefinition) => {
226
- return new RegExp(currentDefinition.pattern.source, currentDefinition.pattern.flags).test(message);
227
- });
228
- if (definition === void 0) return null;
229
- return toUnexpectedClassification(definition);
230
- };
231
- const classifyErrorCandidate = (registry, error) => {
232
- const normalizedError = normalizeError(error);
233
- const knownError = resolveDeterministicKnownError(registry, normalizedError);
234
- if (knownError !== null) return knownError;
235
- return resolveHeuristicUnexpectedError(registry, normalizedError);
236
- };
237
- //#endregion
238
- //#region src/app-error.ts
239
- /** Default message used when no registry entry or fallback message can describe an error. */
240
- const DEFAULT_APP_ERROR_MESSAGE = "An unexpected error occurred.";
241
- /** Error class that normalizes unknown thrown or returned values into a predictable shape. */
242
- var AppError = class AppError extends Error {
243
- /** App-level registry used when no registry is passed to the constructor. */
244
- static registry = createErrorRegistry();
245
- /** Creates an isolated registry without modifying the app-level registry. */
246
- static createRegistry() {
247
- return createErrorRegistry();
248
- }
249
- /** Classification assigned after registry lookup and fallback handling. */
250
- type;
251
- /** Optional localization key from matched registry feedback. */
252
- messageKey;
253
- /** Optional source label from matched registry feedback. */
254
- source;
255
- /** Original value passed to the constructor, or the original value from a wrapped `AppError`. */
256
- originalError;
257
- /** Whether retrying the failed operation is likely to help. */
258
- retryable;
259
- /**
260
- * Normalizes an unknown error value. Construction does not throw; unmatched values become `unknown` errors.
261
- */
262
- constructor(error, options = {}) {
263
- const resolved = AppError.resolve(error, options);
264
- super(resolved.message);
265
- Object.setPrototypeOf(this, new.target.prototype);
266
- this.name = "AppError";
267
- this.type = resolved.type;
268
- this.messageKey = resolved.messageKey;
269
- this.source = resolved.source;
270
- this.originalError = resolved.originalError;
271
- this.retryable = resolved.retryable;
272
- }
273
- static resolve(error, options) {
274
- if (error instanceof AppError) return {
275
- type: error.type,
276
- message: error.message,
277
- messageKey: error.messageKey,
278
- source: error.source,
279
- originalError: error.originalError,
280
- retryable: error.retryable
281
- };
282
- const fallbackMessage = options.fallbackMessage ?? "An unexpected error occurred.";
283
- const errorCandidates = getErrorCandidates(error);
284
- const registry = options.registry ?? AppError.registry;
285
- for (const errorCandidate of errorCandidates) {
286
- const classification = classifyErrorCandidate(registry, errorCandidate);
287
- if (classification !== null) return {
288
- type: classification.type,
289
- message: classification.message,
290
- messageKey: classification.messageKey,
291
- source: classification.source,
292
- originalError: error,
293
- retryable: classification.retryable
294
- };
295
- }
296
- return {
297
- type: "unknown",
298
- message: fallbackMessage,
299
- messageKey: null,
300
- source: null,
301
- originalError: error,
302
- retryable: false
303
- };
304
- }
305
- };
306
- //#endregion
307
- //#region src/result.ts
308
- /** Wraps a value in a successful result. */
309
- const ok = (value) => ({
310
- ok: true,
311
- value
312
- });
313
- /** Normalizes an unknown error value and wraps it in a failed result. */
314
- const err = (error, options = {}) => ({
315
- ok: false,
316
- error: new AppError(error, typeof error === "string" ? {
317
- fallbackMessage: error,
318
- ...options
319
- } : options)
320
- });
321
- //#endregion
322
- export { createErrorRegistry as a, DEFAULT_APP_ERROR_MESSAGE as i, ok as n, AppError as r, err as t };
@@ -1,87 +0,0 @@
1
- //#region src/types.d.ts
2
- /** Options that control how unknown errors are normalized. */
3
- interface AppErrorOptions {
4
- /** Message used when the error cannot be matched by the selected registry. */
5
- fallbackMessage?: string;
6
- /** Registry used for classification. Defaults to the app-level `AppError.registry`. */
7
- registry?: ErrorRegistry;
8
- }
9
- /** Classification assigned to a normalized application error. */
10
- type AppErrorType = "known" | "unexpected" | "unknown";
11
- /** Optional source label for the system, package, or integration that produced an error. */
12
- type AppErrorSource = string | null;
13
- /** Consumer-facing feedback returned when a registry entry matches an error. */
14
- interface ErrorFeedback {
15
- /** Safe user-facing message for the matched error. */
16
- message: string;
17
- /** Optional localization key for the message. */
18
- messageKey?: string;
19
- /** Optional source label, such as `auth`, `browser.network`, or `supabase.database`. */
20
- source?: string;
21
- /** Whether retrying the failed operation is likely to help. Defaults to `false`. */
22
- retryable?: boolean;
23
- }
24
- /** Registry bucket for exact string identifiers such as error codes, names, or messages. */
25
- interface ErrorRegistryBucket {
26
- /** Adds or replaces feedback for one normalized non-empty identifier, throwing `TypeError` for invalid input. */
27
- add(identifier: string, feedback: ErrorFeedback): void;
28
- /** Adds or replaces feedback for multiple normalized identifiers, throwing `TypeError` when any entry is invalid. */
29
- addList(entries: readonly (readonly [identifier: string, feedback: ErrorFeedback])[]): void;
30
- /** Removes all entries from this bucket. */
31
- clear(): void;
32
- /** Returns a defensive copy of feedback for a normalized identifier when present. */
33
- get(identifier: string): ErrorFeedback | undefined;
34
- /** Returns defensive copies of all bucket entries. */
35
- values(): IterableIterator<[string, ErrorFeedback]>;
36
- }
37
- /** Stored prefix mapping returned by prefix registry bucket inspection. */
38
- interface ErrorPrefixDefinition extends ErrorFeedback {
39
- /** Message prefix that is matched after trimming trailing sentence punctuation. */
40
- prefix: string;
41
- }
42
- /** Stored pattern mapping returned by pattern registry bucket inspection. */
43
- interface ErrorPatternDefinition extends ErrorFeedback {
44
- /** Regular expression used for heuristic message matching. */
45
- pattern: RegExp;
46
- }
47
- /** Registry bucket for known errors matched by longest normalized message prefix. */
48
- interface ErrorPrefixRegistryBucket {
49
- /** Adds prefix feedback after normalization, throwing `TypeError` for empty prefixes or invalid feedback. */
50
- add(prefix: string, feedback: ErrorFeedback): void;
51
- /** Adds multiple prefix feedback entries, throwing `TypeError` when any entry is invalid. */
52
- addList(entries: readonly (readonly [prefix: string, feedback: ErrorFeedback])[]): void;
53
- /** Removes all prefix entries. */
54
- clear(): void;
55
- /** Returns defensive copies of all prefix definitions. */
56
- values(): readonly ErrorPrefixDefinition[];
57
- }
58
- /** Registry bucket for unexpected errors matched heuristically by message pattern. */
59
- interface ErrorPatternRegistryBucket {
60
- /** Adds pattern feedback, throwing `TypeError` for non-`RegExp` patterns or invalid feedback. */
61
- add(pattern: RegExp, feedback: ErrorFeedback): void;
62
- /** Adds multiple pattern feedback entries, throwing `TypeError` when any entry is invalid. */
63
- addList(entries: readonly (readonly [pattern: RegExp, feedback: ErrorFeedback])[]): void;
64
- /** Removes all pattern entries. */
65
- clear(): void;
66
- /** Returns defensive copies of all pattern definitions. */
67
- values(): readonly ErrorPatternDefinition[];
68
- }
69
- /** Mutable collection of exact and heuristic mappings used to classify unknown errors. */
70
- interface ErrorRegistry {
71
- /** Exact error-code mappings, usually from APIs or databases. */
72
- codes: ErrorRegistryBucket;
73
- /** Exact error-name mappings, usually from `Error.name` or `DOMException.name`. */
74
- names: ErrorRegistryBucket;
75
- /** Exact error-message mappings after light punctuation normalization. */
76
- messages: ErrorRegistryBucket;
77
- /** Known-error mappings by normalized message prefix. */
78
- prefixes: ErrorPrefixRegistryBucket;
79
- /** Heuristic mappings by message pattern, classified as `unexpected`. */
80
- patterns: ErrorPatternRegistryBucket;
81
- /** Removes all mappings from every bucket. */
82
- clear(): void;
83
- /** Copies all mappings from another registry into this registry. */
84
- merge(registry: ErrorRegistry): void;
85
- }
86
- //#endregion
87
- export { ErrorPatternDefinition as a, ErrorPrefixRegistryBucket as c, ErrorFeedback as i, ErrorRegistry as l, AppErrorSource as n, ErrorPatternRegistryBucket as o, AppErrorType as r, ErrorPrefixDefinition as s, AppErrorOptions as t, ErrorRegistryBucket as u };