refactor(web): decouple repositories and services from QueryClient#1163
refactor(web): decouple repositories and services from QueryClient#1163pmilic021 wants to merge 1 commit into
Conversation
Repositories and services no longer take a TanStack QueryClient; they run their underlying async actions directly and return Promises. This is a prerequisite for the stateless @agicash/wallet-sdk extraction, where the SDK holds no read cache and the web keeps TanStack Query. The Breez wallet connection is the one piece that must still be held: connect() opens a stateful, resource-owning session (disconnect/free, event listeners, background loops, a storageDir DB), so there must be exactly one instance per wallet, not a fresh one per account load. It moves to a process-wide singleton (getSparkWallet, keyed by mnemonic/network/storageDir), cleared on signOut alongside queryClient.clear(). Everything else is a plain read fetched directly: - Mint metadata (info/keysets/keys): getInitializedCashuWallet and account-service call the mint over HTTP directly (idempotent, nothing to leak). The web's mint *QueryOptions are untouched and still cache those reads for the settings form / token decode. - Accounts / exchange rate: claim-cashu-token-service reads via accountRepository.getAllActive and ExchangeRateService directly. claim-cashu-token-service no longer writes to the cache either: it returns the changed accounts + updated user, and the token route's clientLoader applies them. Behavior note: the account-load path (getInitializedCashuWallet via toAccount) no longer shares the QueryClient mint cache, so it refetches mint metadata on each load; only the web's own mint reads stay cached. typecheck + biome clean; web/bolt11/ecies/cashu unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
| const { keysets } = await this.queryClient.fetchQuery( | ||
| allMintKeysetsQueryOptions(account.mintUrl), | ||
| ); | ||
| const { keysets } = await new Mint(account.mintUrl).getKeySets(); |
There was a problem hiding this comment.
Mint details throughout this PR are left uncached, and if we see issues with it we can try optimize it then. Makes sense @jbojcic1 ?
| queryClient.clear(); | ||
| // The spark wallet connection memo is framework-free (not in the query | ||
| // cache), so clear it alongside so a next session reconnects fresh. | ||
| clearSparkWallets(); |
There was a problem hiding this comment.
This call would be part of sdk-exposed clear method in the future (like .stop).
| destinationAccount: Pick<Account, 'id' | 'purpose'>; | ||
| /** | ||
| * Accounts created or updated while claiming, for the caller to write into | ||
| * its cache (the service no longer owns one). |
There was a problem hiding this comment.
nit but (the service no longer owns one) should be removed. comments shouldn't reflect old state of the code
| const changedAccounts = new Map<string, Account>(); | ||
| let updatedUser: User | null = null; | ||
|
|
||
| const accounts = await this.accountRepository.getAllActive(user.id); |
There was a problem hiding this comment.
i would rather pass accounts as a new param. that way web won't be making one new db call
| accountRepository: this.accountRepository, | ||
| }), | ||
| ); | ||
| const changedAccounts = new Map<string, Account>(); |
There was a problem hiding this comment.
why is this a map and not a simple array?
| } | ||
|
|
||
| if ( | ||
| receiveAccount.currency !== user.defaultCurrency || |
There was a problem hiding this comment.
i wonder if we should take this out of this service now and do it before calling it in the loader? the whole purpose of this is that when user is receiving to the account that is not the default, we set this one to default for ui benefits (if user is joining for the first time and don't have a balance on regular default account it's better to make this one default). this is probably irrelevant for mcp
| } else { | ||
| const exchangeRate = await getExchangeRate( | ||
| this.queryClient, | ||
| const exchangeRate = await new ExchangeRateService().getRate( |
There was a problem hiding this comment.
so we have one additional network request here right? because it was reading from the cache before. i wonder if we should change the service to just take the rate as param or change ExchangeRateService to handle caching
| ]), | ||
| new Promise<never>((_, reject) => { | ||
| setTimeout(() => { | ||
| queryClient.cancelQueries({ |
| }): Promise<BreezSdk> { | ||
| const key = `${computeSHA256(mnemonic)}:${network}:${storageDir}`; | ||
| const existing = sparkWalletPromises.get(key); | ||
| if (existing) return existing; |
There was a problem hiding this comment.
hm not sure if we should even do this. is calling connect/disconnect multiple times "expensive"? remind me when would we even call it multiple times?
| { 'spark.network': network }, | ||
| ); | ||
| sparkWalletPromises.set(key, walletPromise); | ||
| void walletPromise.catch(() => { |
There was a problem hiding this comment.
why is this void needed?
| ); | ||
| sparkWalletPromises.set(key, walletPromise); | ||
| void walletPromise.catch(() => { | ||
| if (sparkWalletPromises.get(key) === walletPromise) { |
There was a problem hiding this comment.
is this if check needed? can't you just do sparkWalletPromises.delete(key);?
Repositories and services no longer take a TanStack QueryClient; they run their underlying async actions directly and return Promises. This is a prerequisite for the stateless @agicash/wallet-sdk extraction, where the SDK holds no read cache and the web keeps TanStack Query.
The Breez wallet connection is the one piece that must still be held: connect() opens a stateful, resource-owning session (disconnect/free, event listeners, background loops, a storageDir DB), so there must be exactly one instance per wallet, not a fresh one per account load. It moves to a process-wide singleton (getSparkWallet, keyed by mnemonic/network/storageDir), cleared on signOut alongside queryClient.clear().
Everything else is a plain read fetched directly:
claim-cashu-token-service no longer writes to the cache either: it returns the changed accounts + updated user, and the token route's clientLoader applies them.
Behavior note: the account-load path (getInitializedCashuWallet via toAccount) no longer shares the QueryClient mint cache, so it refetches mint metadata on each load; only the web's own mint reads stay cached. typecheck + biome clean; web/bolt11/ecies/cashu unit tests pass.