Skip to content

Commit b11f589

Browse files
committed
add read / write db mini abstractions...
clarifies intent. allows SyncStrategy to configure the targets for reads and writes.
1 parent 94d87ce commit b11f589

File tree

5 files changed

+50
-15
lines changed

5 files changed

+50
-15
lines changed

packages/db/src/impl/common/BaseUserDB.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,14 @@ export class BaseUser implements UserDBInterface, DocumentUpdater {
7979
return !this._username.startsWith(GuestUsername);
8080
}
8181

82-
private remoteDB!: PouchDB.Database;
8382
public remote(): PouchDB.Database {
8483
return this.remoteDB;
8584
}
85+
8686
private localDB!: PouchDB.Database;
87+
private remoteDB!: PouchDB.Database;
88+
private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)
89+
8790
private updateQueue!: UpdateQueue;
8891

8992
public async createAccount(
@@ -597,7 +600,11 @@ Currently logged-in as ${this._username}.`
597600
private setDBandQ() {
598601
this.localDB = getLocalUserDB(this._username);
599602
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
600-
this.updateQueue = new UpdateQueue(this.localDB);
603+
// writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate
604+
this.writeDB = this.syncStrategy.getWriteDB
605+
? this.syncStrategy.getWriteDB(this._username)
606+
: this.localDB;
607+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
601608
}
602609

603610
private async init() {
@@ -697,7 +704,9 @@ Currently logged-in as ${this._username}.`
697704
* @returns The updated state of the card's CardHistory data
698705
*/
699706

700-
public async putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>> {
707+
public async putCardRecord<T extends CardRecord>(
708+
record: T
709+
): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {
701710
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
702711
// stringify the current record to make it writable to couchdb
703712
record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;
@@ -735,8 +744,8 @@ Currently logged-in as ${this._username}.`
735744
streak: 0,
736745
bestInterval: 0,
737746
};
738-
void this.remoteDB.put<CardHistory<T>>(initCardHistory);
739-
return initCardHistory;
747+
const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);
748+
return { ...initCardHistory, _rev: putResult.rev };
740749
} else {
741750
throw new Error(`putCardRecord failed because of:
742751
name:${reason.name}
@@ -793,7 +802,7 @@ Currently logged-in as ${this._username}.`
793802
const deletePromises = duplicateDocIds.map(async (docId) => {
794803
try {
795804
const doc = await this.remoteDB.get(docId);
796-
await this.remoteDB.remove(doc);
805+
await this.writeDB.remove(doc);
797806
log(`Successfully removed duplicate review: ${docId}`);
798807
} catch (error) {
799808
log(`Failed to remove duplicate review ${docId}: ${error}`);
@@ -891,7 +900,7 @@ Currently logged-in as ${this._username}.`
891900

892901
if (err.status === 404) {
893902
// doc does not exist. Create it and then run this fcn again.
894-
await this.remoteDB.put<ClassroomRegistrationDoc>({
903+
await this.writeDB.put<ClassroomRegistrationDoc>({
895904
_id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
896905
registrations: [],
897906
});

packages/db/src/impl/common/SyncStrategy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export interface SyncStrategy {
1414
*/
1515
setupRemoteDB(username: string): PouchDB.Database;
1616

17+
/**
18+
* Get the database to use for write operations (local-first approach)
19+
* @param username The username to get write DB for
20+
* @returns PouchDB database instance for write operations
21+
*/
22+
getWriteDB?(username: string): PouchDB.Database;
23+
1724
/**
1825
* Start synchronization between local and remote databases
1926
* @param localDB The local PouchDB instance

packages/db/src/impl/couch/CouchDBSyncStrategy.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ export class CouchDBSyncStrategy implements SyncStrategy {
3232
}
3333
}
3434

35+
getWriteDB(username: string): PouchDB.Database {
36+
if (username === GuestUsername || username.startsWith(GuestUsername)) {
37+
// Guest users write to local database
38+
return getLocalUserDB(username);
39+
} else {
40+
// Authenticated users write to remote (which will sync to local)
41+
return this.getUserDB(username);
42+
}
43+
}
44+
3545
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {
3646
// Only sync if local and remote are different instances
3747
if (localDB !== remoteDB) {

packages/db/src/impl/couch/updateQueue.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export default class UpdateQueue extends Loggable {
1212
[index: string]: boolean;
1313
} = {};
1414

15-
private db: PouchDB.Database;
15+
private readDB: PouchDB.Database; // Database for read operations
16+
private writeDB: PouchDB.Database; // Database for write operations (local-first)
1617

1718
public update<T extends PouchDB.Core.Document<object>>(
1819
id: PouchDB.Core.DocumentId,
@@ -27,29 +28,32 @@ export default class UpdateQueue extends Loggable {
2728
return this.applyUpdates<T>(id);
2829
}
2930

30-
constructor(db: PouchDB.Database) {
31+
constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {
3132
super();
3233
// PouchDB.debug.enable('*');
33-
this.db = db;
34+
this.readDB = readDB;
35+
this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided
3436
logger.debug(`UpdateQ initialized...`);
35-
void this.db.info().then((i) => {
37+
void this.readDB.info().then((i) => {
3638
logger.debug(`db info: ${JSON.stringify(i)}`);
3739
});
3840
}
3941

40-
private async applyUpdates<T extends PouchDB.Core.Document<object>>(id: string): Promise<T> {
42+
private async applyUpdates<T extends PouchDB.Core.Document<object>>(
43+
id: string
44+
): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {
4145
logger.debug(`Applying updates on doc: ${id}`);
4246
if (this.inprogressUpdates[id]) {
4347
// console.log(`Updates in progress...`);
44-
await this.db.info(); // stall for a round trip
48+
await this.readDB.info(); // stall for a round trip
4549
// console.log(`Retrying...`);
4650
return this.applyUpdates<T>(id);
4751
} else {
4852
if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {
4953
this.inprogressUpdates[id] = true;
5054

5155
try {
52-
let doc = await this.db.get<T>(id);
56+
let doc = await this.readDB.get<T>(id);
5357
logger.debug(`Retrieved doc: ${id}`);
5458
while (this.pendingUpdates[id].length !== 0) {
5559
const update = this.pendingUpdates[id].splice(0, 1)[0];
@@ -66,7 +70,7 @@ export default class UpdateQueue extends Loggable {
6670
// console.log(`${k}: ${typeof k}`);
6771
// }
6872
// console.log(`Applied updates to doc: ${JSON.stringify(doc)}`);
69-
await this.db.put<T>(doc);
73+
await this.writeDB.put<T>(doc);
7074
logger.debug(`Put doc: ${id}`);
7175

7276
if (this.pendingUpdates[id].length === 0) {

packages/db/src/impl/static/NoOpSyncStrategy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export class NoOpSyncStrategy implements SyncStrategy {
1717
return getLocalUserDB(username);
1818
}
1919

20+
getWriteDB(username: string): PouchDB.Database {
21+
// In static mode, always write to local database
22+
return getLocalUserDB(username);
23+
}
24+
2025
startSync(_localDB: PouchDB.Database, _remoteDB: PouchDB.Database): void {
2126
// No-op - in static mode, local and remote are the same database instance
2227
// PouchDB sync with itself is harmless and efficient

0 commit comments

Comments
 (0)