Skip to content

Commit 88c4cb6

Browse files
committed
fix: delay InMemoryCollectionStore observeAll emission after setAll
observeAll implementation may not be subscribed to updates$ yet also update WalletRepository methods to take(1) because setAll makes source emit again
1 parent 3175a75 commit 88c4cb6

File tree

4 files changed

+33
-11
lines changed

4 files changed

+33
-11
lines changed

packages/wallet/src/persistence/inMemoryStores/InMemoryCollectionStore.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable brace-style */
22
import { CollectionStore } from '../types';
3-
import { EMPTY, Observable, Subject, of } from 'rxjs';
3+
import { EMPTY, Observable, Subject, delay, of, tap } from 'rxjs';
44
import { InMemoryStore } from './InMemoryStore';
55
import { observeAll } from '../util';
66

@@ -22,8 +22,10 @@ export class InMemoryCollectionStore<T> extends InMemoryStore implements Collect
2222
setAll(docs: T[]): Observable<void> {
2323
if (!this.destroyed) {
2424
this.docs = docs;
25-
this.#updates$.next(this.docs);
26-
return of(void 0);
25+
return of(void 0).pipe(
26+
delay(1),
27+
tap(() => this.#updates$.next(this.docs))
28+
);
2729
}
2830
return EMPTY;
2931
}

packages/wallet/test/persistence/inMemoryStores.test.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
InMemoryUtxoStore
1919
} from '../../src/persistence';
2020
import { assertCompletesWithoutEmitting } from './util';
21-
import { firstValueFrom, share, take, toArray } from 'rxjs';
21+
import { firstValueFrom, mergeMap, share, shareReplay, take, toArray } from 'rxjs';
2222

2323
describe('inMemoryStores', () => {
2424
describe('InMemoryDocumentStore', () => {
@@ -75,6 +75,12 @@ describe('inMemoryStores', () => {
7575
await firstValueFrom(store.setAll(updatedDocs));
7676
await expect(twoEmissions).resolves.toEqual([docs, updatedDocs]);
7777
});
78+
it('observeAll followed by immediate setAll does not skip 2nd emission', async () => {
79+
const store = new InMemoryCollectionStore();
80+
const items$ = store.observeAll().pipe(shareReplay(1));
81+
await firstValueFrom(items$.pipe(mergeMap(() => store.setAll(docs))));
82+
await expect(firstValueFrom(items$)).resolves.toEqual(docs);
83+
});
7884
});
7985
});
8086

packages/wallet/test/persistence/pouchDbStores.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable unicorn/consistent-function-scoping */
22
import { PouchDbCollectionStore, PouchDbDocumentStore, PouchDbKeyValueStore } from '../../src/persistence';
33
import { assertCompletesWithoutEmitting } from './util';
4-
import { combineLatest, firstValueFrom, mergeMap, share, take, timer, toArray } from 'rxjs';
4+
import { combineLatest, firstValueFrom, mergeMap, share, shareReplay, take, timer, toArray } from 'rxjs';
55
import { dummyLogger as logger } from 'ts-log';
66
import PouchDB from 'pouchdb';
77

@@ -115,6 +115,11 @@ describe('pouchDbStores', () => {
115115
await firstValueFrom(store1.setAll([doc1, doc2]));
116116
await expect(twoEmissions).resolves.toEqual([[doc1], [doc1, doc2]]);
117117
});
118+
it('observeAll followed by immediate setAll does not skip 2nd emission', async () => {
119+
const items$ = store1.observeAll().pipe(shareReplay(1));
120+
await firstValueFrom(items$.pipe(mergeMap(() => store1.setAll([doc1]))));
121+
await expect(firstValueFrom(items$)).resolves.toEqual([doc1]);
122+
});
118123
});
119124
});
120125

packages/web-extension/src/walletManager/WalletRepository/WalletRepository.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AddAccountProps, AddWalletProps, RemoveAccountProps, UpdateMetadataProp
22
import { AnyWallet, ScriptWallet, WalletId, WalletType } from '../types';
33
import { Bip32PublicKey, Hash28ByteBase16 } from '@cardano-sdk/crypto';
44
import { Logger } from 'ts-log';
5-
import { Observable, defer, firstValueFrom, map, shareReplay, switchMap } from 'rxjs';
5+
import { Observable, defer, firstValueFrom, map, shareReplay, switchMap, take } from 'rxjs';
66
import { Serialization } from '@cardano-sdk/core';
77
import { WalletConflictError } from '../errors';
88
import { contextLogger } from '@cardano-sdk/util';
@@ -48,14 +48,22 @@ export class WalletRepository<AccountMetadata extends {}> implements WalletRepos
4848
this.wallets$ = defer(() => store.observeAll()).pipe(shareReplay(1));
4949
}
5050

51+
#getWallets() {
52+
return this.wallets$.pipe(
53+
// `setAll` makes the store.observeAll source emit
54+
// so the pipes are triggered twice otherwise
55+
take(1)
56+
);
57+
}
58+
5159
async addWallet(props: AddWalletProps<AccountMetadata>): Promise<WalletId> {
5260
this.#logger.debug('addWallet', props.type);
5361
const walletId =
5462
props.type === WalletType.Script
5563
? Serialization.Script.fromCore(props.script).hash()
5664
: Hash28ByteBase16(await Bip32PublicKey.fromHex(props.extendedAccountPublicKey).hash());
5765
return firstValueFrom(
58-
this.wallets$.pipe(
66+
this.#getWallets().pipe(
5967
switchMap((wallets) => {
6068
if (wallets.some((wallet) => wallet.walletId === walletId)) {
6169
throw new WalletConflictError(`Wallet '${walletId}' already exists`);
@@ -90,7 +98,7 @@ export class WalletRepository<AccountMetadata extends {}> implements WalletRepos
9098
const { walletId, accountIndex, metadata } = props;
9199
this.#logger.debug('addAccount', walletId, accountIndex, metadata);
92100
return firstValueFrom(
93-
this.wallets$.pipe(
101+
this.#getWallets().pipe(
94102
switchMap((wallets) => {
95103
const walletIndex = wallets.findIndex((w) => w.walletId === walletId);
96104
if (walletIndex < 0) {
@@ -126,7 +134,7 @@ export class WalletRepository<AccountMetadata extends {}> implements WalletRepos
126134
const { walletId, accountIndex, metadata } = props;
127135
this.#logger.debug('updateMetadata', walletId, accountIndex, metadata);
128136
return firstValueFrom(
129-
this.wallets$.pipe(
137+
this.#getWallets().pipe(
130138
switchMap((wallets) => {
131139
if (typeof accountIndex !== 'undefined') {
132140
const bip32Account = findAccount(wallets, walletId, accountIndex);
@@ -165,7 +173,7 @@ export class WalletRepository<AccountMetadata extends {}> implements WalletRepos
165173
const { walletId, accountIndex } = props;
166174
this.#logger.debug('removeAccount', walletId, accountIndex);
167175
return firstValueFrom(
168-
this.wallets$.pipe(
176+
this.#getWallets().pipe(
169177
switchMap((wallets) => {
170178
const bip32Account = findAccount(wallets, walletId, accountIndex);
171179
if (!bip32Account) {
@@ -196,7 +204,8 @@ export class WalletRepository<AccountMetadata extends {}> implements WalletRepos
196204
removeWallet(walletId: WalletId): Promise<WalletId> {
197205
this.#logger.debug('removeWallet', walletId);
198206
return firstValueFrom(
199-
this.wallets$.pipe(
207+
this.#getWallets().pipe(
208+
take(1),
200209
switchMap((wallets) => {
201210
const walletIndex = wallets.findIndex((w) => w.walletId === walletId);
202211
if (walletIndex < 0) {

0 commit comments

Comments
 (0)