@@ -6,30 +6,124 @@ import { join } from "node:path"
66import { SETTINGS_STORE } from "./store-keys"
77import { deleteStoreFileIfEmpty } from "./store-cleanup"
88
9- const cache = new Map < string , Store > ( )
9+ const DELETED = Symbol ( "DELETED" )
1010
11- // We cannot instantiate the electron-store at module load time because
12- // module import hoisting causes this to run before app.setPath("userData", ...)
13- // in index.ts has executed, which would result in files being written to the default directory
14- // (e.g. bad: %APPDATA%\@opencode-ai\desktop\opencode.settings vs good: %APPDATA%\ai.opencode.desktop.dev\opencode.settings).
15- export function getStore ( name = SETTINGS_STORE ) {
11+ export class BufferedStore {
12+ private pending = new Map < string , unknown > ( )
13+ private cleared = false
14+ private timer : ReturnType < typeof setTimeout > | null = null
15+
16+ constructor (
17+ private target : {
18+ get ( key : string ) : unknown
19+ set ( key : string , val : unknown ) : void
20+ delete ( key : string ) : void
21+ clear ( ) : void
22+ has ( key : string ) : boolean
23+ store : Record < string , unknown >
24+ } ,
25+ private delay = 150 ,
26+ ) { }
27+
28+ get store ( ) : Record < string , unknown > {
29+ const base = this . cleared ? { } : { ...this . target . store }
30+ for ( const [ key , value ] of this . pending ) {
31+ if ( value === DELETED ) {
32+ delete base [ key ]
33+ } else {
34+ base [ key ] = value
35+ }
36+ }
37+ return base
38+ }
39+
40+ has ( key : string ) : boolean {
41+ if ( this . pending . has ( key ) ) {
42+ return this . pending . get ( key ) !== DELETED
43+ }
44+ if ( this . cleared ) return false
45+ return this . target . has ( key )
46+ }
47+
48+ get ( key : string ) {
49+ if ( this . pending . has ( key ) ) {
50+ const val = this . pending . get ( key )
51+ return val === DELETED ? undefined : val
52+ }
53+ if ( this . cleared ) return undefined
54+ return this . target . get ( key )
55+ }
56+
57+ set ( key : string , value : unknown ) {
58+ this . pending . set ( key , value )
59+ this . scheduleFlush ( )
60+ }
61+
62+ delete ( key : string ) {
63+ this . pending . set ( key , DELETED )
64+ this . scheduleFlush ( )
65+ }
66+
67+ clear ( ) {
68+ this . cleared = true
69+ this . pending . clear ( )
70+ this . scheduleFlush ( )
71+ }
72+
73+ flush ( ) {
74+ if ( this . timer ) {
75+ clearTimeout ( this . timer )
76+ this . timer = null
77+ }
78+ if ( this . cleared ) {
79+ this . target . clear ( )
80+ this . cleared = false
81+ }
82+ for ( const [ key , value ] of this . pending ) {
83+ if ( value === DELETED ) {
84+ this . target . delete ( key )
85+ } else {
86+ this . target . set ( key , value )
87+ }
88+ }
89+ this . pending . clear ( )
90+ }
91+
92+ private scheduleFlush ( ) {
93+ if ( this . timer ) return
94+ this . timer = setTimeout ( ( ) => this . flush ( ) , this . delay )
95+ }
96+ }
97+
98+ const cache = new Map < string , BufferedStore > ( )
99+
100+ export function getStore ( name = SETTINGS_STORE ) : BufferedStore {
16101 const cached = cache . get ( name )
17102 if ( cached ) return cached
18- const next = new Store ( {
103+ const underlying = new Store ( {
19104 name,
20105 cwd : electron . app . getPath ( "userData" ) ,
21106 fileExtension : "" ,
22107 accessPropertiesByDotNotation : false ,
23108 } )
109+ const next = new BufferedStore ( underlying )
24110 cache . set ( name , next )
25111 return next
26112}
27113
114+ export function flushAllStores ( ) {
115+ for ( const store of cache . values ( ) ) {
116+ store . flush ( )
117+ }
118+ }
119+
28120export async function removeStoreFileIfEmpty ( name : string ) {
121+ cache . get ( name ) ?. flush ( )
29122 if ( await deleteStoreFileIfEmpty ( electron . app . getPath ( "userData" ) , name ) ) cache . delete ( name )
30123}
31124
32125export function removeStoreFile ( name : string ) {
126+ cache . get ( name ) ?. flush ( )
33127 rmSync ( join ( electron . app . getPath ( "userData" ) , name ) , { force : true } )
34128 cache . delete ( name )
35129}
0 commit comments