Skip to content

Commit a803541

Browse files
committed
feat(utils): add structured error handling for storage operations
Create comprehensive error class hierarchy for storage operations: - StorageError base class with structured error information - IndexedDBError, QuotaExceededError, NotFoundError, ValidationError - ConnectionError, OperationAbortedError for specific failure modes - Utility functions for error type checking and IndexedDB conversion Provides consistent error handling, better debugging info, and improved UX.
1 parent f1532ae commit a803541

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* Custom error classes for storage operations
3+
* Provides structured error handling for IndexedDB and storage provider operations
4+
*/
5+
6+
/**
7+
* Base error class for storage operations
8+
*/
9+
export abstract class StorageError extends Error {
10+
abstract readonly code: string;
11+
abstract readonly userMessage: string;
12+
13+
constructor(
14+
message: string,
15+
public readonly cause?: Error,
16+
public readonly context?: Record<string, unknown>
17+
) {
18+
super(message);
19+
this.name = this.constructor.name;
20+
}
21+
}
22+
23+
/**
24+
* Error when IndexedDB operations fail
25+
*/
26+
export class IndexedDBError extends StorageError {
27+
readonly code = 'INDEXED_DB_ERROR';
28+
readonly userMessage = 'Storage operation failed. Please try again.';
29+
30+
constructor(
31+
operation: string,
32+
cause?: Error,
33+
context?: Record<string, unknown>
34+
) {
35+
super(`IndexedDB operation failed: ${operation}`, cause, context);
36+
}
37+
}
38+
39+
/**
40+
* Error when quota is exceeded
41+
*/
42+
export class QuotaExceededError extends StorageError {
43+
readonly code = 'QUOTA_EXCEEDED';
44+
readonly userMessage = 'Storage space full. Please clear some data and try again.';
45+
46+
constructor(cause?: Error) {
47+
super('Storage quota exceeded', cause);
48+
}
49+
}
50+
51+
/**
52+
* Error when data is not found
53+
*/
54+
export class NotFoundError extends StorageError {
55+
readonly code = 'NOT_FOUND';
56+
readonly userMessage = 'The requested data was not found.';
57+
58+
constructor(resourceType: string, identifier: string, cause?: Error) {
59+
super(`${resourceType} not found: ${identifier}`, cause);
60+
}
61+
}
62+
63+
/**
64+
* Error when data validation fails
65+
*/
66+
export class ValidationError extends StorageError {
67+
readonly code = 'VALIDATION_ERROR';
68+
readonly userMessage = 'Invalid data format. Please check your input and try again.';
69+
70+
constructor(
71+
field: string,
72+
value: unknown,
73+
reason: string,
74+
cause?: Error
75+
) {
76+
super(`Validation failed for ${field}: ${reason}`, cause, { field, value });
77+
}
78+
}
79+
80+
/**
81+
* Error when network/connexion issues prevent storage operations
82+
*/
83+
export class ConnectionError extends StorageError {
84+
readonly code = 'CONNECTION_ERROR';
85+
readonly userMessage = 'Connection to storage failed. Please check your browser settings.';
86+
87+
constructor(cause?: Error) {
88+
super('Storage connection failed', cause);
89+
}
90+
}
91+
92+
/**
93+
* Error when operations are aborted or interrupted
94+
*/
95+
export class OperationAbortedError extends StorageError {
96+
readonly code = 'OPERATION_ABORTED';
97+
readonly userMessage = 'Operation was cancelled. Please try again.';
98+
99+
constructor(operation: string, cause?: Error) {
100+
super(`Operation aborted: ${operation}`, cause);
101+
}
102+
}
103+
104+
/**
105+
* Utility function to determine if an error is a storage error
106+
*/
107+
export function isStorageError(error: unknown): error is StorageError {
108+
return error instanceof StorageError;
109+
}
110+
111+
/**
112+
* Utility function to convert IndexedDB errors to structured storage errors
113+
*/
114+
export function convertIndexedDBError(
115+
operation: string,
116+
error: unknown
117+
): StorageError {
118+
if (isStorageError(error)) {
119+
return error;
120+
}
121+
122+
if (error instanceof DOMException) {
123+
switch (error.name) {
124+
case 'QuotaExceededError':
125+
return new QuotaExceededError(error);
126+
case 'NotFoundError':
127+
return new NotFoundError('Resource', 'unknown', error);
128+
case 'InvalidStateError':
129+
case 'DataError':
130+
return new ValidationError('operation', operation, error.message, error);
131+
case 'AbortError':
132+
return new OperationAbortedError(operation, error);
133+
case 'VersionError':
134+
return new ConnectionError(error);
135+
default:
136+
return new IndexedDBError(operation, error);
137+
}
138+
}
139+
140+
if (error instanceof Error) {
141+
if (error.message.includes('quota') || error.message.includes('storage')) {
142+
return new QuotaExceededError(error);
143+
}
144+
if (error.message.includes('abort') || error.message.includes('cancel')) {
145+
return new OperationAbortedError(operation, error);
146+
}
147+
return new IndexedDBError(operation, error);
148+
}
149+
150+
return new IndexedDBError(operation, new Error(String(error)));
151+
}

0 commit comments

Comments
 (0)