@@ -126,6 +126,7 @@ import {
126126 clearFlaggedAccounts ,
127127 StorageError ,
128128 formatStorageErrorHint ,
129+ withAccountAndFlaggedStorageTransaction ,
129130 withFlaggedAccountsTransaction ,
130131 type AccountStorageV3 ,
131132 type FlaggedAccountMetadataV1 ,
@@ -3444,12 +3445,12 @@ while (attempted.size < Math.max(1, accountCount)) {
34443445 if ( ! normalizedAccounts ) {
34453446 throw new Error ( "Prune backup account snapshot failed validation." ) ;
34463447 }
3447- await withAccountStorageTransaction ( async ( _current , persist ) => {
3448- await persist ( normalizedAccounts ) ;
3448+ await withAccountAndFlaggedStorageTransaction ( async ( _current , persist ) => {
3449+ await persist . accounts ( normalizedAccounts ) ;
3450+ await persist . flagged (
3451+ restoreFlaggedSnapshot as { version : 1 ; accounts : FlaggedAccountMetadataV1 [ ] } ,
3452+ ) ;
34493453 } ) ;
3450- await saveFlaggedAccounts (
3451- restoreFlaggedSnapshot as { version : 1 ; accounts : FlaggedAccountMetadataV1 [ ] } ,
3452- ) ;
34533454 invalidateAccountManagerCache ( ) ;
34543455 } ,
34553456 } ;
@@ -3465,132 +3466,137 @@ while (attempted.size < Math.max(1, accountCount)) {
34653466 index : number ;
34663467 account : AccountStorageV3 [ "accounts" ] [ number ] ;
34673468 } > = [ ] ;
3468- let rollbackStorage : AccountStorageV3 | null = null ;
3469- await withAccountStorageTransaction ( async ( loadedStorage , persist ) => {
3470- const currentStorage =
3471- loadedStorage ??
3469+ await withAccountAndFlaggedStorageTransaction (
3470+ async ( { accounts : loadedStorage , flagged : currentFlaggedStorage } , persist ) => {
3471+ const currentStorage =
3472+ loadedStorage ??
34723473 ( {
34733474 version : 3 ,
34743475 accounts : [ ] ,
34753476 activeIndex : 0 ,
34763477 activeIndexByFamily : { } ,
34773478 } satisfies AccountStorageV3 ) ;
3478- removedTargets = currentStorage . accounts
3479- . map ( ( account , index ) => ( { index, account } ) )
3480- . filter ( ( entry ) =>
3481- targetKeySet . has (
3479+ removedTargets = currentStorage . accounts
3480+ . map ( ( account , index ) => ( { index, account } ) )
3481+ . filter ( ( entry ) =>
3482+ targetKeySet . has (
3483+ getSyncRemovalTargetKey ( {
3484+ refreshToken : entry . account . refreshToken ,
3485+ organizationId : entry . account . organizationId ,
3486+ accountId : entry . account . accountId ,
3487+ } ) ,
3488+ ) ,
3489+ ) ;
3490+ if ( removedTargets . length === 0 ) {
3491+ return ;
3492+ }
3493+
3494+ const removedFlaggedKeys = new Set (
3495+ removedTargets . map ( ( entry ) =>
34823496 getSyncRemovalTargetKey ( {
34833497 refreshToken : entry . account . refreshToken ,
34843498 organizationId : entry . account . organizationId ,
34853499 accountId : entry . account . accountId ,
34863500 } ) ,
34873501 ) ,
34883502 ) ;
3489- if ( removedTargets . length === 0 ) {
3490- return ;
3491- }
3492- rollbackStorage = structuredClone ( currentStorage ) ;
3493-
3494- const activeAccountIdentity = {
3495- refreshToken :
3496- currentStorage . accounts [ currentStorage . activeIndex ] ?. refreshToken ?? "" ,
3497- organizationId :
3498- currentStorage . accounts [ currentStorage . activeIndex ] ?. organizationId ,
3499- accountId : currentStorage . accounts [ currentStorage . activeIndex ] ?. accountId ,
3500- } satisfies SyncRemovalTarget ;
3501- const familyActiveIdentities = Object . fromEntries (
3502- MODEL_FAMILIES . map ( ( family ) => {
3503- const familyIndex = currentStorage . activeIndexByFamily ?. [ family ] ?? currentStorage . activeIndex ;
3504- const familyAccount = currentStorage . accounts [ familyIndex ] ;
3505- return [
3506- family ,
3507- familyAccount
3508- ? ( {
3509- refreshToken : familyAccount . refreshToken ,
3510- organizationId : familyAccount . organizationId ,
3511- accountId : familyAccount . accountId ,
3512- } satisfies SyncRemovalTarget )
3513- : null ,
3514- ] ;
3515- } ) ,
3516- ) as Partial < Record < ModelFamily , SyncRemovalTarget | null > > ;
3517-
3518- currentStorage . accounts = currentStorage . accounts . filter (
3519- ( account ) =>
3520- ! targetKeySet . has (
3521- getSyncRemovalTargetKey ( {
3522- refreshToken : account . refreshToken ,
3523- organizationId : account . organizationId ,
3524- accountId : account . accountId ,
3525- } ) ,
3503+ const nextFlaggedStorage = {
3504+ version : 1 as const ,
3505+ accounts : currentFlaggedStorage . accounts . filter (
3506+ ( flagged ) =>
3507+ ! removedFlaggedKeys . has (
3508+ getSyncRemovalTargetKey ( {
3509+ refreshToken : flagged . refreshToken ,
3510+ organizationId : flagged . organizationId ,
3511+ accountId : flagged . accountId ,
3512+ } ) ,
3513+ ) ,
35263514 ) ,
3527- ) ;
3528- const remappedActiveIndex = findAccountIndexByExactIdentity (
3529- currentStorage . accounts ,
3530- activeAccountIdentity ,
3531- ) ;
3532- currentStorage . activeIndex =
3533- remappedActiveIndex >= 0
3534- ? remappedActiveIndex
3535- : Math . min ( currentStorage . activeIndex , Math . max ( 0 , currentStorage . accounts . length - 1 ) ) ;
3536- currentStorage . activeIndexByFamily = currentStorage . activeIndexByFamily ?? { } ;
3537- for ( const family of MODEL_FAMILIES ) {
3538- const remappedFamilyIndex = findAccountIndexByExactIdentity (
3539- currentStorage . accounts ,
3540- familyActiveIdentities [ family ] ?? null ,
3541- ) ;
3542- currentStorage . activeIndexByFamily [ family ] =
3543- remappedFamilyIndex >= 0 ? remappedFamilyIndex : currentStorage . activeIndex ;
3544- }
3545- clampActiveIndices ( currentStorage ) ;
3546- await persist ( currentStorage ) ;
3547- } ) ;
3515+ } ;
35483516
3549- if ( removedTargets . length > 0 ) {
3550- const removedFlaggedKeys = new Set (
3551- removedTargets . map ( ( entry ) =>
3552- getSyncRemovalTargetKey ( {
3553- refreshToken : entry . account . refreshToken ,
3554- organizationId : entry . account . organizationId ,
3555- accountId : entry . account . accountId ,
3517+ const activeAccountIdentity = {
3518+ refreshToken :
3519+ currentStorage . accounts [ currentStorage . activeIndex ] ?. refreshToken ?? "" ,
3520+ organizationId :
3521+ currentStorage . accounts [ currentStorage . activeIndex ] ?. organizationId ,
3522+ accountId : currentStorage . accounts [ currentStorage . activeIndex ] ?. accountId ,
3523+ } satisfies SyncRemovalTarget ;
3524+ const familyActiveIdentities = Object . fromEntries (
3525+ MODEL_FAMILIES . map ( ( family ) => {
3526+ const familyIndex =
3527+ currentStorage . activeIndexByFamily ?. [ family ] ?? currentStorage . activeIndex ;
3528+ const familyAccount = currentStorage . accounts [ familyIndex ] ;
3529+ return [
3530+ family ,
3531+ familyAccount
3532+ ? ( {
3533+ refreshToken : familyAccount . refreshToken ,
3534+ organizationId : familyAccount . organizationId ,
3535+ accountId : familyAccount . accountId ,
3536+ } satisfies SyncRemovalTarget )
3537+ : null ,
3538+ ] ;
35563539 } ) ,
3557- ) ,
3558- ) ;
3559- try {
3560- await withFlaggedAccountsTransaction ( async ( currentFlaggedStorage , persist ) => {
3561- await persist ( {
3562- version : 1 ,
3563- accounts : currentFlaggedStorage . accounts . filter (
3564- ( flagged ) =>
3565- ! removedFlaggedKeys . has (
3566- getSyncRemovalTargetKey ( {
3567- refreshToken : flagged . refreshToken ,
3568- organizationId : flagged . organizationId ,
3569- accountId : flagged . accountId ,
3570- } ) ,
3571- ) ,
3540+ ) as Partial < Record < ModelFamily , SyncRemovalTarget | null > > ;
3541+
3542+ currentStorage . accounts = currentStorage . accounts . filter (
3543+ ( account ) =>
3544+ ! targetKeySet . has (
3545+ getSyncRemovalTargetKey ( {
3546+ refreshToken : account . refreshToken ,
3547+ organizationId : account . organizationId ,
3548+ accountId : account . accountId ,
3549+ } ) ,
35723550 ) ,
3573- } ) ;
3574- } ) ;
3575- } catch ( flaggedError ) {
3576- if ( rollbackStorage ) {
3551+ ) ;
3552+ const remappedActiveIndex = findAccountIndexByExactIdentity (
3553+ currentStorage . accounts ,
3554+ activeAccountIdentity ,
3555+ ) ;
3556+ currentStorage . activeIndex =
3557+ remappedActiveIndex >= 0
3558+ ? remappedActiveIndex
3559+ : Math . min (
3560+ currentStorage . activeIndex ,
3561+ Math . max ( 0 , currentStorage . accounts . length - 1 ) ,
3562+ ) ;
3563+ currentStorage . activeIndexByFamily = currentStorage . activeIndexByFamily ?? { } ;
3564+ for ( const family of MODEL_FAMILIES ) {
3565+ const remappedFamilyIndex = findAccountIndexByExactIdentity (
3566+ currentStorage . accounts ,
3567+ familyActiveIdentities [ family ] ?? null ,
3568+ ) ;
3569+ currentStorage . activeIndexByFamily [ family ] =
3570+ remappedFamilyIndex >= 0 ? remappedFamilyIndex : currentStorage . activeIndex ;
3571+ }
3572+ clampActiveIndices ( currentStorage ) ;
3573+
3574+ // Persist flagged cleanup before account removal so a crash cannot leave
3575+ // flagged entries pointing at deleted accounts.
3576+ await persist . flagged ( nextFlaggedStorage ) ;
3577+ try {
3578+ await persist . accounts ( currentStorage ) ;
3579+ } catch ( accountsError ) {
35773580 try {
3578- await withAccountStorageTransaction ( async ( _current , persist ) => {
3579- await persist ( rollbackStorage as AccountStorageV3 ) ;
3580- } ) ;
3581- } catch ( restoreError ) {
3582- const flaggedMessage =
3583- flaggedError instanceof Error ? flaggedError . message : String ( flaggedError ) ;
3584- const restoreMessage =
3585- restoreError instanceof Error ? restoreError . message : String ( restoreError ) ;
3581+ await persist . flagged ( currentFlaggedStorage ) ;
3582+ } catch ( restoreFlaggedError ) {
3583+ const accountsMessage =
3584+ accountsError instanceof Error ? accountsError . message : String ( accountsError ) ;
3585+ const restoreFlaggedMessage =
3586+ restoreFlaggedError instanceof Error
3587+ ? restoreFlaggedError . message
3588+ : String ( restoreFlaggedError ) ;
35863589 throw new Error (
3587- `Failed to remove flagged sync entries after account removal : ${ flaggedMessage } ; ` +
3588- `failed to restore removed accounts : ${ restoreMessage } ` ,
3590+ `Failed to remove sync accounts after flagged cleanup : ${ accountsMessage } ; ` +
3591+ `failed to restore flagged storage : ${ restoreFlaggedMessage } ` ,
35893592 ) ;
35903593 }
3594+ throw accountsError ;
35913595 }
3592- throw flaggedError ;
3593- }
3596+ } ,
3597+ ) ;
3598+
3599+ if ( removedTargets . length > 0 ) {
35943600 invalidateAccountManagerCache ( ) ;
35953601 }
35963602 } ;
0 commit comments