Browse Source

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.
pull/152/head^2
Matthew Raymer 1 day ago
parent
commit
bc618bb13b
  1. 78
      src/interfaces/common.ts
  2. 35
      src/views/ContactsView.vue
  3. 4
      src/views/IdentitySwitcherView.vue
  4. 12
      src/views/ProjectsView.vue

78
src/interfaces/common.ts

@ -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)
);
}

35
src/views/ContactsView.vue

@ -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);

4
src/views/IdentitySwitcherView.vue

@ -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,
),
},
);

12
src/views/ProjectsView.vue

@ -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;

Loading…
Cancel
Save