Skip to content

Commit ba39583

Browse files
authored
feat!: Expose clientID on ReadTransaction (#629)
Exposes clientID on ReadTransaction (and subclasses, e.g. WriteTransaction, IndexTransaction, etc). This change was previously reverted due to it causing perf test regressions (976bf98, 4da3f52). The underlying cause of those regressions was addressed in commit cc2bbeb. BREAKING CHANGE: Due to the new property on ReadTransaction the Replicache class no longer duck-types as a ReadTransaction (prior to 52bab5d Replicache explicitly implemented ReadTransaction). Any place where Replicache is assigned or passed as a ReadTransaction will need to be updated, most likely to use Replicache.query. Closes #541
1 parent 02310a4 commit ba39583

File tree

3 files changed

+62
-18
lines changed

3 files changed

+62
-18
lines changed

src/replicache.test.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ testWithBothStores('get, has, scan on empty db', async () => {
151151
expect(scanItems).to.have.length(0);
152152
}
153153

154-
await t(rep);
154+
await rep.query(t);
155155
});
156156

157157
testWithBothStores('put, get, has, del inside tx', async () => {
@@ -2986,3 +2986,24 @@ testWithBothStores('subscribe perf test regression', async () => {
29862986
expect(data[i]).to.equal(i < mut ? i ** 2 + rand : i);
29872987
}
29882988
});
2989+
2990+
testWithBothStores('client ID is set correctly on transactions', async () => {
2991+
const rep = await replicacheForTesting(
2992+
'client-id-is-set-correctly-on-transactions',
2993+
{
2994+
mutators: {
2995+
async expectClientID(tx, expectedClientID: string) {
2996+
expect(tx.clientID).to.equal(expectedClientID);
2997+
},
2998+
},
2999+
},
3000+
);
3001+
3002+
const repClientID = await rep.clientID;
3003+
3004+
await rep.query(tx => {
3005+
expect(tx.clientID).to.equal(repClientID);
3006+
});
3007+
3008+
await rep.mutate.expectClientID(repClientID);
3009+
});

src/replicache.ts

+27-14
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ export class Replicache<MD extends MutatorDefs = {}> {
148148
private _online = true;
149149
private readonly _logger: Logger;
150150
private readonly _ready: Promise<void>;
151-
private readonly _resolveReady: () => void;
152151
private readonly _clientIDPromise: Promise<string>;
153152
private _root: Promise<string | undefined> = Promise.resolve(undefined);
154153
private readonly _mutatorRegistry = new Map<
@@ -295,9 +294,8 @@ export class Replicache<MD extends MutatorDefs = {}> {
295294

296295
// Use a promise-resolve pair so that we have a promise to use even before
297296
// we call the Open RPC.
298-
const {promise, resolve} = resolver<void>();
299-
this._ready = promise;
300-
this._resolveReady = resolve;
297+
const readyResolver = resolver<void>();
298+
this._ready = readyResolver.promise;
301299

302300
const {minDelayMs = MIN_DELAY_MS, maxDelayMs = MAX_DELAY_MS} =
303301
requestOptions;
@@ -331,23 +329,31 @@ export class Replicache<MD extends MutatorDefs = {}> {
331329

332330
this._lc = new LogContext(logLevel).addContext('db', name);
333331

334-
this._clientIDPromise = this._open();
332+
const clientIDResolver = resolver<string>();
333+
this._clientIDPromise = clientIDResolver.promise;
334+
335+
void this._open(clientIDResolver.resolve, readyResolver.resolve);
335336
}
336337

337-
private async _open(): Promise<string> {
338+
private async _open(
339+
resolveClientID: (clientID: string) => void,
340+
resolveReady: () => void,
341+
): Promise<void> {
338342
// If we are currently closing a Replicache instance with the same name,
339343
// wait for it to finish closing.
340344
await closingInstances.get(this.name);
341345

342346
await Promise.all([initHasher(), migrate(this._kvStore, this._lc)]);
343347

344-
const [clientID] = await Promise.all([
345-
sync.initClientID(this._kvStore),
348+
await Promise.all([
349+
sync.initClientID(this._kvStore).then(clientID => {
350+
resolveClientID(clientID);
351+
}),
346352
db.maybeInitDefaultDB(this._dagStore),
347353
]);
348354

349355
// Now we have both a clientID and DB!
350-
this._resolveReady();
356+
resolveReady();
351357

352358
if (hasBroadcastChannel) {
353359
this._broadcastChannel = new BroadcastChannel(storageKeyName(this.name));
@@ -361,7 +367,6 @@ export class Replicache<MD extends MutatorDefs = {}> {
361367

362368
this.pull();
363369
this._push();
364-
return clientID;
365370
}
366371

367372
/**
@@ -585,13 +590,15 @@ export class Replicache<MD extends MutatorDefs = {}> {
585590
f: (tx: IndexTransactionImpl) => Promise<void>,
586591
): Promise<void> {
587592
await this._ready;
593+
// clientID must be awaited ouside dag transaction to avoid a premature
594+
// auto-commit of the idb transaction.
595+
const clientID = await this._clientIDPromise;
588596
await this._dagStore.withWrite(async dagWrite => {
589597
const dbWrite = await db.Write.newIndexChange(
590598
db.whenceHead(db.DEFAULT_HEAD_NAME),
591599
dagWrite,
592600
);
593-
594-
const tx = new IndexTransactionImpl(dbWrite, this._lc);
601+
const tx = new IndexTransactionImpl(clientID, dbWrite, this._lc);
595602
await f(tx);
596603
await tx.commit();
597604
});
@@ -994,9 +1001,12 @@ export class Replicache<MD extends MutatorDefs = {}> {
9941001
*/
9951002
async query<R>(body: (tx: ReadTransaction) => Promise<R> | R): Promise<R> {
9961003
await this._ready;
1004+
// clientID must be awaited ouside dag transaction to avoid a premature
1005+
// auto-commit of the idb transaction.
1006+
const clientID = await this._clientIDPromise;
9971007
return await this._dagStore.withRead(async dagRead => {
9981008
const dbRead = await db.readFromDefaultHead(dagRead);
999-
const tx = new ReadTransactionImpl(dbRead, this._lc);
1009+
const tx = new ReadTransactionImpl(clientID, dbRead, this._lc);
10001010
return await body(tx);
10011011
});
10021012
}
@@ -1079,6 +1089,9 @@ export class Replicache<MD extends MutatorDefs = {}> {
10791089
}
10801090

10811091
await this._ready;
1092+
// clientID must be awaited ouside dag transaction to avoid a premature
1093+
// auto-commit of the idb transaction.
1094+
const clientID = await this._clientIDPromise;
10821095
return await this._dagStore.withWrite(async dagWrite => {
10831096
let whence: db.Whence | undefined;
10841097
let originalHash: string | null = null;
@@ -1098,7 +1111,7 @@ export class Replicache<MD extends MutatorDefs = {}> {
10981111
dagWrite,
10991112
);
11001113

1101-
const tx = new WriteTransactionImpl(dbWrite, this._lc);
1114+
const tx = new WriteTransactionImpl(clientID, dbWrite, this._lc);
11021115
const result: R = await mutatorImpl(tx, args);
11031116

11041117
const [ref, changedKeys] = await tx.commit(!isReplay);

src/transactions.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import type {LogContext} from './logger';
1616
* database.
1717
*/
1818
export interface ReadTransaction {
19+
readonly clientID: string;
20+
1921
/**
2022
* Get a single value from the database. If the `key` is not present this
2123
* returns `undefined`.
@@ -65,14 +67,17 @@ export class ReadTransactionImpl<
6567
Value extends ReadonlyJSONValue = ReadonlyJSONValue,
6668
> implements ReadTransaction
6769
{
70+
readonly clientID: string;
6871
protected readonly _dbtx: db.Read;
6972
protected readonly _lc: LogContext;
7073

7174
constructor(
75+
clientID: string,
7276
dbRead: db.Read,
7377
lc: LogContext,
7478
rpcName = 'openReadTransaction',
7579
) {
80+
this.clientID = clientID;
7681
this._dbtx = dbRead;
7782
this._lc = lc
7883
.addContext('rpc', rpcName)
@@ -122,6 +127,10 @@ export class SubscriptionTransactionWrapper implements ReadTransaction {
122127
this._tx = tx;
123128
}
124129

130+
get clientID(): string {
131+
return this._tx.clientID;
132+
}
133+
125134
isEmpty(): Promise<boolean> {
126135
// Any change to the subscription requires rerunning it.
127136
this._scans.push({});
@@ -194,11 +203,12 @@ export class WriteTransactionImpl
194203
protected declare readonly _dbtx: db.Write;
195204

196205
constructor(
206+
clientID: string,
197207
dbWrite: db.Write,
198208
lc: LogContext,
199209
rpcName = 'openWriteTransaction',
200210
) {
201-
super(dbWrite, lc, rpcName);
211+
super(clientID, dbWrite, lc, rpcName);
202212
}
203213

204214
async put(key: string, value: JSONValue): Promise<void> {
@@ -276,8 +286,8 @@ export class IndexTransactionImpl
276286
extends WriteTransactionImpl
277287
implements IndexTransaction
278288
{
279-
constructor(dbWrite: db.Write, lc: LogContext) {
280-
super(dbWrite, lc, 'openIndexTransaction');
289+
constructor(clientID: string, dbWrite: db.Write, lc: LogContext) {
290+
super(clientID, dbWrite, lc, 'openIndexTransaction');
281291
}
282292

283293
async createIndex(options: CreateIndexDefinition): Promise<void> {

0 commit comments

Comments
 (0)