forked from trent_larson/crowd-funder-for-time-pwa
feat(typescript): implement type-safe database error handling and eliminate any types
- Add comprehensive database error interfaces (DatabaseConstraintError, DatabaseStorageError, DexieError) - Implement type guards for database error handling (isDatabaseError, isDatabaseConstraintError, etc.) - Replace any types with proper TypeScript types in ContactsView, ProjectsView, and IdentitySwitcherView - Implement type-safe error handling patterns using new type guards - Fix dynamic property access with keyof operator for type safety Resolves Priority 1 type safety issues in database operations, project management, and identity switching.
This commit is contained in:
@@ -98,3 +98,81 @@ export interface VerifiableCredentialClaim {
|
||||
credentialSubject: ClaimObject;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database constraint error types for consistent error handling
|
||||
*/
|
||||
export interface DatabaseConstraintError extends Error {
|
||||
name: "ConstraintError";
|
||||
message: string;
|
||||
constraint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database storage error types for IndexedDB/SQLite operations
|
||||
*/
|
||||
export interface DatabaseStorageError extends Error {
|
||||
name: "StorageError";
|
||||
message: string;
|
||||
code?: string;
|
||||
constraint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy Dexie error types for migration compatibility
|
||||
*/
|
||||
export interface DexieError extends Error {
|
||||
name: string;
|
||||
message: string;
|
||||
inner?: unknown;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for database constraint errors
|
||||
*/
|
||||
export function isDatabaseConstraintError(
|
||||
error: unknown,
|
||||
): error is DatabaseConstraintError {
|
||||
return error instanceof Error && error.name === "ConstraintError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for database storage errors
|
||||
*/
|
||||
export function isDatabaseStorageError(
|
||||
error: unknown,
|
||||
): error is DatabaseStorageError {
|
||||
return error instanceof Error && error.name === "StorageError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for legacy Dexie errors
|
||||
*/
|
||||
export function isDexieError(error: unknown): error is DexieError {
|
||||
return (
|
||||
error instanceof Error &&
|
||||
(error.name === "DexieError" ||
|
||||
error.message.includes("Key already exists in the object store") ||
|
||||
error.message.includes("ConstraintError"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified error type for database operations
|
||||
*/
|
||||
export type DatabaseError =
|
||||
| DatabaseConstraintError
|
||||
| DatabaseStorageError
|
||||
| DexieError;
|
||||
|
||||
/**
|
||||
* Type guard for any database error
|
||||
*/
|
||||
export function isDatabaseError(error: unknown): error is DatabaseError {
|
||||
return (
|
||||
isDatabaseConstraintError(error) ||
|
||||
isDatabaseStorageError(error) ||
|
||||
isDexieError(error)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ import { logger } from "../utils/logger";
|
||||
// No longer needed - using PlatformServiceMixin methods
|
||||
// import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { isDatabaseError } from "@/interfaces/common";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
import {
|
||||
@@ -376,7 +377,11 @@ export default class ContactsView extends Vue {
|
||||
"",
|
||||
async (name) => {
|
||||
await this.addContact({
|
||||
did: (registration.vc.credentialSubject.agent as any).identifier,
|
||||
did: (
|
||||
registration.vc.credentialSubject.agent as {
|
||||
identifier: string;
|
||||
}
|
||||
).identifier,
|
||||
name: name,
|
||||
registered: true,
|
||||
});
|
||||
@@ -387,7 +392,11 @@ export default class ContactsView extends Vue {
|
||||
async () => {
|
||||
// on cancel, will still add the contact
|
||||
await this.addContact({
|
||||
did: (registration.vc.credentialSubject.agent as any).identifier,
|
||||
did: (
|
||||
registration.vc.credentialSubject.agent as {
|
||||
identifier: string;
|
||||
}
|
||||
).identifier,
|
||||
name: "(person who invited you)",
|
||||
registered: true,
|
||||
});
|
||||
@@ -396,8 +405,7 @@ export default class ContactsView extends Vue {
|
||||
this.showOnboardingInfo();
|
||||
},
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
const fullError = "Error redeeming invite: " + errorStringForLog(error);
|
||||
this.$logAndConsole(fullError, true);
|
||||
let message = "Got an error sending the invite.";
|
||||
@@ -881,20 +889,21 @@ export default class ContactsView extends Vue {
|
||||
/**
|
||||
* Handle errors during contact addition
|
||||
*/
|
||||
private handleContactAddError(err: any): void {
|
||||
private handleContactAddError(err: unknown): void {
|
||||
const fullError =
|
||||
"Error when adding contact to storage: " + errorStringForLog(err);
|
||||
this.$logAndConsole(fullError, true);
|
||||
|
||||
let message = NOTIFY_CONTACT_IMPORT_ERROR.message;
|
||||
if (
|
||||
(err as any).message?.indexOf("Key already exists in the object store.") >
|
||||
-1
|
||||
) {
|
||||
message = NOTIFY_CONTACT_IMPORT_CONFLICT.message;
|
||||
}
|
||||
if ((err as any).name === "ConstraintError") {
|
||||
message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message;
|
||||
|
||||
// Use type-safe error checking with our new type guards
|
||||
if (isDatabaseError(err)) {
|
||||
if (err.message.includes("Key already exists in the object store")) {
|
||||
message = NOTIFY_CONTACT_IMPORT_CONFLICT.message;
|
||||
}
|
||||
if (err.name === "ConstraintError") {
|
||||
message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message;
|
||||
}
|
||||
}
|
||||
|
||||
this.notify.error(message, TIMEOUTS.LONG);
|
||||
|
||||
@@ -234,7 +234,9 @@ export default class IdentitySwitcherView extends Vue {
|
||||
{
|
||||
did,
|
||||
settingsKeys: Object.keys(newSettings).filter(
|
||||
(k) => (newSettings as any)[k] !== undefined,
|
||||
(k) =>
|
||||
k in newSettings &&
|
||||
newSettings[k as keyof typeof newSettings] !== undefined,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -464,8 +464,10 @@ export default class ProjectsView extends Vue {
|
||||
);
|
||||
this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("Got error loading plans:", error.message || error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
logger.error("Got error loading plans:", errorMessage);
|
||||
this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
@@ -578,8 +580,10 @@ export default class ProjectsView extends Vue {
|
||||
);
|
||||
this.notify.error(NOTIFY_OFFERS_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("Got error loading offers:", error.message || error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
logger.error("Got error loading offers:", errorMessage);
|
||||
this.notify.error(NOTIFY_OFFERS_FETCH_ERROR.message, TIMEOUTS.LONG);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
|
||||
Reference in New Issue
Block a user