@ -50,7 +50,7 @@ import {
type SettingsWithJsonStrings ,
} from "@/db/tables/settings" ;
import { logger } from "@/utils/logger" ;
import { Contact } from "@/db/tables/contacts" ;
import { Contact , ContactMaybeWithJsonStrings } from "@/db/tables/contacts" ;
import { Account } from "@/db/tables/accounts" ;
import { Temp } from "@/db/tables/temp" ;
import { QueryExecResult , DatabaseExecResult } from "@/interfaces/database" ;
@ -642,15 +642,81 @@ export const PlatformServiceMixin = {
// CACHED SPECIALIZED SHORTCUTS (massive performance boost)
// =================================================
/ * *
* Normalize contact data by parsing JSON strings into proper objects
* Handles the contactMethods field which can be either a JSON string or an array
* @param rawContacts Raw contact data from database
* @returns Normalized Contact [ ] array
* /
$normalizeContacts ( rawContacts : ContactMaybeWithJsonStrings [ ] ) : Contact [ ] {
return rawContacts . map ( ( contact ) = > {
// Create a new contact object with proper typing
const normalizedContact : Contact = {
did : contact.did ,
iViewContent : contact.iViewContent ,
name : contact.name ,
nextPubKeyHashB64 : contact.nextPubKeyHashB64 ,
notes : contact.notes ,
profileImageUrl : contact.profileImageUrl ,
publicKeyBase64 : contact.publicKeyBase64 ,
seesMe : contact.seesMe ,
registered : contact.registered ,
} ;
// Handle contactMethods field which can be a JSON string or an array
if ( contact . contactMethods !== undefined ) {
if ( typeof contact . contactMethods === "string" ) {
// Parse JSON string into array
normalizedContact . contactMethods = this . _parseJsonField (
contact . contactMethods ,
[ ] ,
) ;
} else if ( Array . isArray ( contact . contactMethods ) ) {
// Validate that each item in the array is a proper ContactMethod object
normalizedContact . contactMethods = contact . contactMethods . filter (
( method ) = > {
const isValid =
method &&
typeof method === "object" &&
typeof method . label === "string" &&
typeof method . type === "string" &&
typeof method . value === "string" ;
if ( ! isValid && method !== undefined ) {
console . warn (
"[ContactNormalization] Invalid contact method:" ,
method ,
) ;
}
return isValid ;
} ,
) ;
} else {
// Invalid data, use empty array
normalizedContact . contactMethods = [ ] ;
}
} else {
// No contactMethods, use empty array
normalizedContact . contactMethods = [ ] ;
}
return normalizedContact ;
} ) ;
} ,
/ * *
* Load all contacts ( always fresh ) - $contacts ( )
* Always fetches fresh data from database for consistency
* @returns Promise < Contact [ ] > Array of contact objects
* Handles JSON string / object duality for contactMethods field
* @returns Promise < Contact [ ] > Array of normalized contact objects
* /
async $contacts ( ) : Promise < Contact [ ] > {
return ( await this . $query (
const rawContacts = ( await this . $query (
"SELECT * FROM contacts ORDER BY name" ,
) ) as Contact [ ] ;
) ) as ContactMaybeWithJsonStrings [ ] ;
return this . $normalizeContacts ( rawContacts ) ;
} ,
/ * *
@ -1026,7 +1092,13 @@ export const PlatformServiceMixin = {
Object . entries ( changes ) . forEach ( ( [ key , value ] ) = > {
if ( value !== undefined ) {
setParts . push ( ` ${ key } = ? ` ) ;
params . push ( value ) ;
// Handle contactMethods field - convert array to JSON string
if ( key === "contactMethods" && Array . isArray ( value ) ) {
params . push ( JSON . stringify ( value ) ) ;
} else {
params . push ( value ) ;
}
}
} ) ;
@ -1048,45 +1120,36 @@ export const PlatformServiceMixin = {
/ * *
* Get all contacts as typed objects - $getAllContacts ( )
* Eliminates verbose query + mapping patterns
* @returns Promise < Contact [ ] > Array of contact objects
* Handles JSON string / object duality for contactMethods field
* @returns Promise < Contact [ ] > Array of normalized contact objects
* /
async $getAllContacts ( ) : Promise < Contact [ ] > {
const results = await this . $dbQ uery (
"SELECT did, name, publicKeyBase64, seesMe, registered, nextPubKeyHashB64, profileImageUrl FROM contacts ORDER BY name" ,
) ;
const rawContacts = ( await this . $q uery (
"SELECT * FROM contacts ORDER BY name" ,
) ) as ContactMaybeWithJsonStrings [ ] ;
return this . $mapResults ( results , ( row : unknown [ ] ) = > ( {
did : row [ 0 ] as string ,
name : row [ 1 ] as string ,
publicKeyBase64 : row [ 2 ] as string ,
seesMe : Boolean ( row [ 3 ] ) ,
registered : Boolean ( row [ 4 ] ) ,
nextPubKeyHashB64 : row [ 5 ] as string ,
profileImageUrl : row [ 6 ] as string ,
} ) ) ;
return this . $normalizeContacts ( rawContacts ) ;
} ,
/ * *
* Get single contact by DID - $getContact ( )
* Eliminates verbose single contact query patterns
* Handles JSON string / object duality for contactMethods field
* @param did Contact DID to retrieve
* @returns Promise < Contact | null > C ontact object or null if not found
* @returns Promise < Contact | null > Normalized contact object or null if not found
* /
async $getContact ( did : string ) : Promise < Contact | null > {
const results = await this . $dbQ uery (
const rawContacts = ( await this . $q uery (
"SELECT * FROM contacts WHERE did = ?" ,
[ did ] ,
) ;
) ) as ContactMaybeWithJsonStrings [ ] ;
if ( ! results || ! results . values || results . value s. length === 0 ) {
if ( rawContact s. length === 0 ) {
return null ;
}
const contactData = this . _mapColumnsToValues (
results . columns ,
results . values ,
) ;
return contactData . length > 0 ? ( contactData [ 0 ] as Contact ) : null ;
const normalizedContacts = this . $normalizeContacts ( rawContacts ) ;
return normalizedContacts [ 0 ] ;
} ,
/ * *
@ -1681,6 +1744,7 @@ declare module "@vue/runtime-core" {
$contactCount ( ) : Promise < number > ;
$settings ( defaults? : Settings ) : Promise < Settings > ;
$accountSettings ( did? : string , defaults? : Settings ) : Promise < Settings > ;
$normalizeContacts ( rawContacts : ContactMaybeWithJsonStrings [ ] ) : Contact [ ] ;
// Settings update shortcuts (eliminate 90% boilerplate)
$saveSettings ( changes : Partial < Settings > ) : Promise < boolean > ;