Skip to content

Commit 8e0c036

Browse files
authored
Make SyncEngine an interface. (#3283)
* Make SyncEngine an interface such that SyncEngineImpl can be contained in the module. * Leave private methods out. * Address comments * Create yellow-lemons-provide.md * Revert "Create yellow-lemons-provide.md" This reverts commit de8ed28. * Create perfect-carpets-chew.md
1 parent 64d9ab6 commit 8e0c036

File tree

3 files changed

+155
-49
lines changed

3 files changed

+155
-49
lines changed

.changeset/perfect-carpets-chew.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
3+
---

packages/firestore/src/core/component_provider.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
newLocalStore,
2828
newMultiTabLocalStore
2929
} from '../local/local_store';
30-
import { MultiTabSyncEngine, SyncEngine } from './sync_engine';
30+
import {
31+
MultiTabSyncEngine,
32+
newMultiTabSyncEngine,
33+
newSyncEngine,
34+
SyncEngine
35+
} from './sync_engine';
3136
import { RemoteStore } from '../remote/remote_store';
3237
import { EventManager } from './event_manager';
3338
import { AsyncQueue } from '../util/async_queue';
@@ -167,7 +172,7 @@ export class MemoryComponentProvider implements ComponentProvider {
167172
}
168173

169174
createSyncEngine(cfg: ComponentConfiguration): SyncEngine {
170-
return new SyncEngine(
175+
return newSyncEngine(
171176
this.localStore,
172177
this.remoteStore,
173178
cfg.datastore,
@@ -225,7 +230,7 @@ export class IndexedDbComponentProvider extends MemoryComponentProvider {
225230
}
226231

227232
createSyncEngine(cfg: ComponentConfiguration): SyncEngine {
228-
const syncEngine = new MultiTabSyncEngine(
233+
const syncEngine = newMultiTabSyncEngine(
229234
this.localStore,
230235
this.remoteStore,
231236
cfg.datastore,

packages/firestore/src/core/sync_engine.ts

+144-46
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,96 @@ export interface SyncEngineListener {
145145
* The SyncEngine’s methods should only ever be called by methods running in the
146146
* global async queue.
147147
*/
148-
export class SyncEngine implements RemoteSyncer {
148+
export interface SyncEngine extends RemoteSyncer {
149+
isPrimaryClient: boolean;
150+
151+
/** Subscribes to SyncEngine notifications. Has to be called exactly once. */
152+
subscribe(syncEngineListener: SyncEngineListener): void;
153+
154+
/**
155+
* Initiates the new listen, resolves promise when listen enqueued to the
156+
* server. All the subsequent view snapshots or errors are sent to the
157+
* subscribed handlers. Returns the initial snapshot.
158+
*/
159+
listen(query: Query): Promise<ViewSnapshot>;
160+
161+
/** Stops listening to the query. */
162+
unlisten(query: Query): Promise<void>;
163+
164+
/**
165+
* Initiates the write of local mutation batch which involves adding the
166+
* writes to the mutation queue, notifying the remote store about new
167+
* mutations and raising events for any changes this write caused.
168+
*
169+
* The promise returned by this call is resolved when the above steps
170+
* have completed, *not* when the write was acked by the backend. The
171+
* userCallback is resolved once the write was acked/rejected by the
172+
* backend (or failed locally for any other reason).
173+
*/
174+
write(batch: Mutation[], userCallback: Deferred<void>): Promise<void>;
175+
176+
/**
177+
* Takes an updateFunction in which a set of reads and writes can be performed
178+
* atomically. In the updateFunction, the client can read and write values
179+
* using the supplied transaction object. After the updateFunction, all
180+
* changes will be committed. If a retryable error occurs (ex: some other
181+
* client has changed any of the data referenced), then the updateFunction
182+
* will be called again after a backoff. If the updateFunction still fails
183+
* after all retries, then the transaction will be rejected.
184+
*
185+
* The transaction object passed to the updateFunction contains methods for
186+
* accessing documents and collections. Unlike other datastore access, data
187+
* accessed with the transaction will not reflect local changes that have not
188+
* been committed. For this reason, it is required that all reads are
189+
* performed before any writes. Transactions must be performed while online.
190+
*
191+
* The Deferred input is resolved when the transaction is fully committed.
192+
*/
193+
runTransaction<T>(
194+
asyncQueue: AsyncQueue,
195+
updateFunction: (transaction: Transaction) => Promise<T>,
196+
deferred: Deferred<T>
197+
): void;
198+
199+
/**
200+
* Applies an OnlineState change to the sync engine and notifies any views of
201+
* the change.
202+
*/
203+
applyOnlineStateChange(
204+
onlineState: OnlineState,
205+
source: OnlineStateSource
206+
): void;
207+
208+
/**
209+
* Registers a user callback that resolves when all pending mutations at the moment of calling
210+
* are acknowledged .
211+
*/
212+
registerPendingWritesCallback(callback: Deferred<void>): Promise<void>;
213+
214+
// Visible for testing
215+
activeLimboDocumentResolutions(): SortedMap<DocumentKey, TargetId>;
216+
217+
// Visible for testing
218+
enqueuedLimboDocumentResolutions(): DocumentKey[];
219+
220+
handleCredentialChange(user: User): Promise<void>;
221+
222+
enableNetwork(): Promise<void>;
223+
224+
disableNetwork(): Promise<void>;
225+
226+
getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet;
227+
}
228+
229+
/**
230+
* An implementation of `SyncEngine` coordinating with other parts of SDK.
231+
*
232+
* Note: some field defined in this class might have public access level, but
233+
* the class is not exported so they are only accessible from this module.
234+
* This is useful to implement optional features (like bundles) in free
235+
* functions, such that they are tree-shakeable.
236+
*/
237+
class SyncEngineImpl implements SyncEngine {
149238
protected syncEngineListener: SyncEngineListener | null = null;
150239

151240
protected queryViewsByQuery = new ObjectMap<Query, QueryView>(
@@ -198,7 +287,6 @@ export class SyncEngine implements RemoteSyncer {
198287
return true;
199288
}
200289

201-
/** Subscribes to SyncEngine notifications. Has to be called exactly once. */
202290
subscribe(syncEngineListener: SyncEngineListener): void {
203291
debugAssert(
204292
syncEngineListener !== null,
@@ -212,11 +300,6 @@ export class SyncEngine implements RemoteSyncer {
212300
this.syncEngineListener = syncEngineListener;
213301
}
214302

215-
/**
216-
* Initiates the new listen, resolves promise when listen enqueued to the
217-
* server. All the subsequent view snapshots or errors are sent to the
218-
* subscribed handlers. Returns the initial snapshot.
219-
*/
220303
async listen(query: Query): Promise<ViewSnapshot> {
221304
this.assertSubscribed('listen()');
222305

@@ -295,7 +378,6 @@ export class SyncEngine implements RemoteSyncer {
295378
return viewChange.snapshot!;
296379
}
297380

298-
/** Stops listening to the query. */
299381
async unlisten(query: Query): Promise<void> {
300382
this.assertSubscribed('unlisten()');
301383

@@ -342,16 +424,6 @@ export class SyncEngine implements RemoteSyncer {
342424
}
343425
}
344426

345-
/**
346-
* Initiates the write of local mutation batch which involves adding the
347-
* writes to the mutation queue, notifying the remote store about new
348-
* mutations and raising events for any changes this write caused.
349-
*
350-
* The promise returned by this call is resolved when the above steps
351-
* have completed, *not* when the write was acked by the backend. The
352-
* userCallback is resolved once the write was acked/rejected by the
353-
* backend (or failed locally for any other reason).
354-
*/
355427
async write(batch: Mutation[], userCallback: Deferred<void>): Promise<void> {
356428
this.assertSubscribed('write()');
357429

@@ -369,23 +441,6 @@ export class SyncEngine implements RemoteSyncer {
369441
}
370442
}
371443

372-
/**
373-
* Takes an updateFunction in which a set of reads and writes can be performed
374-
* atomically. In the updateFunction, the client can read and write values
375-
* using the supplied transaction object. After the updateFunction, all
376-
* changes will be committed. If a retryable error occurs (ex: some other
377-
* client has changed any of the data referenced), then the updateFunction
378-
* will be called again after a backoff. If the updateFunction still fails
379-
* after all retries, then the transaction will be rejected.
380-
*
381-
* The transaction object passed to the updateFunction contains methods for
382-
* accessing documents and collections. Unlike other datastore access, data
383-
* accessed with the transaction will not reflect local changes that have not
384-
* been committed. For this reason, it is required that all reads are
385-
* performed before any writes. Transactions must be performed while online.
386-
*
387-
* The Deferred input is resolved when the transaction is fully committed.
388-
*/
389444
runTransaction<T>(
390445
asyncQueue: AsyncQueue,
391446
updateFunction: (transaction: Transaction) => Promise<T>,
@@ -442,10 +497,6 @@ export class SyncEngine implements RemoteSyncer {
442497
}
443498
}
444499

445-
/**
446-
* Applies an OnlineState change to the sync engine and notifies any views of
447-
* the change.
448-
*/
449500
applyOnlineStateChange(
450501
onlineState: OnlineState,
451502
source: OnlineStateSource
@@ -568,10 +619,6 @@ export class SyncEngine implements RemoteSyncer {
568619
}
569620
}
570621

571-
/**
572-
* Registers a user callback that resolves when all pending mutations at the moment of calling
573-
* are acknowledged .
574-
*/
575622
async registerPendingWritesCallback(callback: Deferred<void>): Promise<void> {
576623
if (!this.remoteStore.canUseNetwork()) {
577624
logDebug(
@@ -912,13 +959,46 @@ export class SyncEngine implements RemoteSyncer {
912959
}
913960
}
914961

962+
export function newSyncEngine(
963+
localStore: LocalStore,
964+
remoteStore: RemoteStore,
965+
datastore: Datastore,
966+
// PORTING NOTE: Manages state synchronization in multi-tab environments.
967+
sharedClientState: SharedClientState,
968+
currentUser: User,
969+
maxConcurrentLimboResolutions: number
970+
): SyncEngine {
971+
return new SyncEngineImpl(
972+
localStore,
973+
remoteStore,
974+
datastore,
975+
sharedClientState,
976+
currentUser,
977+
maxConcurrentLimboResolutions
978+
);
979+
}
980+
915981
/**
916-
* An impplementation of SyncEngine that implement SharedClientStateSyncer for
982+
* An extension of SyncEngine that also includes SharedClientStateSyncer for
917983
* Multi-Tab synchronization.
918984
*/
919985
// PORTING NOTE: Web only
920-
export class MultiTabSyncEngine extends SyncEngine
921-
implements SharedClientStateSyncer {
986+
export interface MultiTabSyncEngine
987+
extends SharedClientStateSyncer,
988+
SyncEngine {
989+
applyPrimaryState(isPrimary: boolean): Promise<void>;
990+
}
991+
992+
/**
993+
* An implementation of `SyncEngineImpl` providing multi-tab synchronization on
994+
* top of `SyncEngineImpl`.
995+
*
996+
* Note: some field defined in this class might have public access level, but
997+
* the class is not exported so they are only accessible from this module.
998+
* This is useful to implement optional features (like bundles) in free
999+
* functions, such that they are tree-shakeable.
1000+
*/
1001+
class MultiTabSyncEngineImpl extends SyncEngineImpl {
9221002
// The primary state is set to `true` or `false` immediately after Firestore
9231003
// startup. In the interim, a client should only be considered primary if
9241004
// `isPrimary` is true.
@@ -1273,3 +1353,21 @@ export class MultiTabSyncEngine extends SyncEngine
12731353
}
12741354
}
12751355
}
1356+
1357+
export function newMultiTabSyncEngine(
1358+
localStore: MultiTabLocalStore,
1359+
remoteStore: RemoteStore,
1360+
datastore: Datastore,
1361+
sharedClientState: SharedClientState,
1362+
currentUser: User,
1363+
maxConcurrentLimboResolutions: number
1364+
): MultiTabSyncEngine {
1365+
return new MultiTabSyncEngineImpl(
1366+
localStore,
1367+
remoteStore,
1368+
datastore,
1369+
sharedClientState,
1370+
currentUser,
1371+
maxConcurrentLimboResolutions
1372+
);
1373+
}

0 commit comments

Comments
 (0)