Skip to content

Commit ff78ee9

Browse files
committed
Make local store an interface.
1 parent 2c8c17c commit ff78ee9

File tree

3 files changed

+208
-12
lines changed

3 files changed

+208
-12
lines changed

packages/firestore/src/core/component_provider.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import {
2121
SharedClientState,
2222
WebStorageSharedClientState
2323
} from '../local/shared_client_state';
24-
import { LocalStore, MultiTabLocalStore } from '../local/local_store';
24+
import {
25+
LocalStore,
26+
MultiTabLocalStore,
27+
newLocalStore,
28+
newMultiTabLocalStore
29+
} from '../local/local_store';
2530
import { MultiTabSyncEngine, SyncEngine } from './sync_engine';
2631
import { RemoteStore } from '../remote/remote_store';
2732
import { EventManager } from './event_manager';
@@ -126,7 +131,7 @@ export class MemoryComponentProvider implements ComponentProvider {
126131
}
127132

128133
createLocalStore(cfg: ComponentConfiguration): LocalStore {
129-
return new LocalStore(
134+
return newLocalStore(
130135
this.persistence,
131136
new IndexFreeQueryEngine(),
132137
cfg.initialUser
@@ -212,7 +217,7 @@ export class IndexedDbComponentProvider extends MemoryComponentProvider {
212217
}
213218

214219
createLocalStore(cfg: ComponentConfiguration): LocalStore {
215-
return new MultiTabLocalStore(
220+
return newMultiTabLocalStore(
216221
this.persistence,
217222
new IndexFreeQueryEngine(),
218223
cfg.initialUser

packages/firestore/src/local/local_store.ts

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
MutationBatchResult
3838
} from '../model/mutation_batch';
3939
import { RemoteEvent, TargetChange } from '../remote/remote_event';
40-
import { hardAssert, debugAssert } from '../util/assert';
40+
import { debugAssert, hardAssert } from '../util/assert';
4141
import { Code, FirestoreError } from '../util/error';
4242
import { logDebug } from '../util/log';
4343
import { primitiveComparator } from '../util/misc';
@@ -139,7 +139,142 @@ export interface QueryResult {
139139
* (unexpected) failure (e.g. failed assert) and always represent an
140140
* unrecoverable error (should be caught / reported by the async_queue).
141141
*/
142-
export class LocalStore {
142+
export interface LocalStore {
143+
/** Starts the LocalStore. */
144+
start(): Promise<void>;
145+
146+
/**
147+
* Tells the LocalStore that the currently authenticated user has changed.
148+
*
149+
* In response the local store switches the mutation queue to the new user and
150+
* returns any resulting document changes.
151+
*/
152+
// PORTING NOTE: Android and iOS only return the documents affected by the
153+
// change.
154+
handleUserChange(user: User): Promise<UserChangeResult>;
155+
156+
localWrite(mutations: Mutation[]): Promise<LocalWriteResult>;
157+
158+
/**
159+
* Acknowledge the given batch.
160+
*
161+
* On the happy path when a batch is acknowledged, the local store will
162+
*
163+
* + remove the batch from the mutation queue;
164+
* + apply the changes to the remote document cache;
165+
* + recalculate the latency compensated view implied by those changes (there
166+
* may be mutations in the queue that affect the documents but haven't been
167+
* acknowledged yet); and
168+
* + give the changed documents back the sync engine
169+
*
170+
* @returns The resulting (modified) documents.
171+
*/
172+
acknowledgeBatch(batchResult: MutationBatchResult): Promise<MaybeDocumentMap>;
173+
174+
/**
175+
* Remove mutations from the MutationQueue for the specified batch;
176+
* LocalDocuments will be recalculated.
177+
*
178+
* @returns The resulting modified documents.
179+
*/
180+
rejectBatch(batchId: BatchId): Promise<MaybeDocumentMap>;
181+
182+
/**
183+
* Returns the largest (latest) batch id in mutation queue that is pending server response.
184+
* Returns `BATCHID_UNKNOWN` if the queue is empty.
185+
*/
186+
getHighestUnacknowledgedBatchId(): Promise<BatchId>;
187+
188+
/**
189+
* Returns the last consistent snapshot processed (used by the RemoteStore to
190+
* determine whether to buffer incoming snapshots from the backend).
191+
*/
192+
getLastRemoteSnapshotVersion(): Promise<SnapshotVersion>;
193+
194+
/**
195+
* Update the "ground-state" (remote) documents. We assume that the remote
196+
* event reflects any write batches that have been acknowledged or rejected
197+
* (i.e. we do not re-apply local mutations to updates from this event).
198+
*
199+
* LocalDocuments are re-calculated if there are remaining mutations in the
200+
* queue.
201+
*/
202+
applyRemoteEvent(remoteEvent: RemoteEvent): Promise<MaybeDocumentMap>;
203+
204+
/**
205+
* Notify local store of the changed views to locally pin documents.
206+
*/
207+
notifyLocalViewChanges(viewChanges: LocalViewChanges[]): Promise<void>;
208+
209+
/**
210+
* Gets the mutation batch after the passed in batchId in the mutation queue
211+
* or null if empty.
212+
* @param afterBatchId If provided, the batch to search after.
213+
* @returns The next mutation or null if there wasn't one.
214+
*/
215+
nextMutationBatch(afterBatchId?: BatchId): Promise<MutationBatch | null>;
216+
217+
/**
218+
* Read the current value of a Document with a given key or null if not
219+
* found - used for testing.
220+
*/
221+
readDocument(key: DocumentKey): Promise<MaybeDocument | null>;
222+
223+
/**
224+
* Assigns the given target an internal ID so that its results can be pinned so
225+
* they don't get GC'd. A target must be allocated in the local store before
226+
* the store can be used to manage its view.
227+
*
228+
* Allocating an already allocated `Target` will return the existing `TargetData`
229+
* for that `Target`.
230+
*/
231+
allocateTarget(target: Target): Promise<TargetData>;
232+
233+
/**
234+
* Returns the TargetData as seen by the LocalStore, including updates that may
235+
* have not yet been persisted to the TargetCache.
236+
*/
237+
// Visible for testing.
238+
getTargetData(
239+
transaction: PersistenceTransaction,
240+
target: Target
241+
): PersistencePromise<TargetData | null>;
242+
243+
/**
244+
* Unpin all the documents associated with the given target. If
245+
* `keepPersistedTargetData` is set to false and Eager GC enabled, the method
246+
* directly removes the associated target data from the target cache.
247+
*
248+
* Releasing a non-existing `Target` is a no-op.
249+
*/
250+
// PORTING NOTE: `keepPersistedTargetData` is multi-tab only.
251+
releaseTarget(
252+
targetId: number,
253+
keepPersistedTargetData: boolean
254+
): Promise<void>;
255+
256+
/**
257+
* Runs the specified query against the local store and returns the results,
258+
* potentially taking advantage of query data from previous executions (such
259+
* as the set of remote keys).
260+
*
261+
* @param usePreviousResults Whether results from previous executions can
262+
* be used to optimize this query execution.
263+
*/
264+
executeQuery(query: Query, usePreviousResults: boolean): Promise<QueryResult>;
265+
266+
collectGarbage(garbageCollector: LruGarbageCollector): Promise<LruResults>;
267+
}
268+
269+
/**
270+
* Implements `LocalStore` interface.
271+
*
272+
* Note: some field defined in this class might have public access level, but
273+
* the class is not exported so they are only accessible from this file. This is
274+
* useful to implement optional features (like bundles) in free functions, such
275+
* that they are tree-shakeable.
276+
*/
277+
class LocalStoreImpl implements LocalStore {
143278
/**
144279
* The maximum time to leave a resume token buffered without writing it out.
145280
* This value is arbitrary: it's long enough to avoid several writes
@@ -502,7 +637,7 @@ export class LocalStore {
502637
// Update the target data if there are target changes (or if
503638
// sufficient time has passed since the last update).
504639
if (
505-
LocalStore.shouldPersistTargetData(
640+
LocalStoreImpl.shouldPersistTargetData(
506641
oldTargetData,
507642
newTargetData,
508643
change
@@ -979,12 +1114,59 @@ export class LocalStore {
9791114
}
9801115
}
9811116

1117+
export function newLocalStore(
1118+
/** Manages our in-memory or durable persistence. */
1119+
persistence: Persistence,
1120+
queryEngine: QueryEngine,
1121+
initialUser: User
1122+
): LocalStore {
1123+
return new LocalStoreImpl(persistence, queryEngine, initialUser);
1124+
}
1125+
1126+
/**
1127+
* An interface on top of LocalStore that provides additional functionality
1128+
* for MultiTabSyncEngine.
1129+
*/
1130+
export interface MultiTabLocalStore extends LocalStore {
1131+
/** Returns the local view of the documents affected by a mutation batch. */
1132+
lookupMutationDocuments(batchId: BatchId): Promise<MaybeDocumentMap | null>;
1133+
1134+
removeCachedMutationBatchMetadata(batchId: BatchId): void;
1135+
1136+
setNetworkEnabled(networkEnabled: boolean): void;
1137+
1138+
getActiveClients(): Promise<ClientId[]>;
1139+
1140+
getTarget(targetId: TargetId): Promise<Target | null>;
1141+
1142+
/**
1143+
* Returns the set of documents that have been updated since the last call.
1144+
* If this is the first call, returns the set of changes since client
1145+
* initialization. Further invocations will return document changes since
1146+
* the point of rejection.
1147+
*/
1148+
getNewDocumentChanges(): Promise<MaybeDocumentMap>;
1149+
1150+
/**
1151+
* Reads the newest document change from persistence and forwards the internal
1152+
* synchronization marker so that calls to `getNewDocumentChanges()`
1153+
* only return changes that happened after client initialization.
1154+
*/
1155+
synchronizeLastDocumentChangeReadTime(): Promise<void>;
1156+
}
1157+
9821158
/**
9831159
* An implementation of LocalStore that provides additional functionality
9841160
* for MultiTabSyncEngine.
1161+
*
1162+
* Note: some field defined in this class might have public access level, but
1163+
* the class is not exported so they are only accessible from this file. This is
1164+
* useful to implement optional features (like bundles) in free functions, such
1165+
* that they are tree-shakeable.
9851166
*/
9861167
// PORTING NOTE: Web only.
987-
export class MultiTabLocalStore extends LocalStore {
1168+
class MultiTabLocalStoreImpl extends LocalStoreImpl
1169+
implements MultiTabLocalStore {
9881170
protected mutationQueue: IndexedDbMutationQueue;
9891171
protected remoteDocuments: IndexedDbRemoteDocumentCache;
9901172
protected targetCache: IndexedDbTargetCache;
@@ -1092,6 +1274,15 @@ export class MultiTabLocalStore extends LocalStore {
10921274
}
10931275
}
10941276

1277+
export function newMultiTabLocalStore(
1278+
/** Manages our in-memory or durable persistence. */
1279+
persistence: IndexedDbPersistence,
1280+
queryEngine: QueryEngine,
1281+
initialUser: User
1282+
): MultiTabLocalStore {
1283+
return new MultiTabLocalStoreImpl(persistence, queryEngine, initialUser);
1284+
}
1285+
10951286
/**
10961287
* Verifies the error thrown by a LocalStore operation. If a LocalStore
10971288
* operation fails because the primary lease has been taken by another client,

packages/firestore/test/unit/local/local_store.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
3030
import {
3131
LocalStore,
3232
LocalWriteResult,
33-
MultiTabLocalStore
33+
newLocalStore, newMultiTabLocalStore
3434
} from '../../../src/local/local_store';
3535
import { LocalViewChanges } from '../../../src/local/local_view_changes';
3636
import { Persistence } from '../../../src/local/persistence';
@@ -396,7 +396,7 @@ describe('LocalStore w/ Memory Persistence (SimpleQueryEngine)', () => {
396396
QueryEngineType.Simple
397397
);
398398
const persistence = await persistenceHelpers.testMemoryEagerPersistence();
399-
const localStore = new LocalStore(
399+
const localStore = newLocalStore(
400400
persistence,
401401
queryEngine,
402402
User.UNAUTHENTICATED
@@ -414,7 +414,7 @@ describe('LocalStore w/ Memory Persistence (IndexFreeQueryEngine)', () => {
414414
QueryEngineType.IndexFree
415415
);
416416
const persistence = await persistenceHelpers.testMemoryEagerPersistence();
417-
const localStore = new LocalStore(
417+
const localStore = newLocalStore(
418418
persistence,
419419
queryEngine,
420420
User.UNAUTHENTICATED
@@ -440,7 +440,7 @@ describe('LocalStore w/ IndexedDB Persistence (SimpleQueryEngine)', () => {
440440
QueryEngineType.Simple
441441
);
442442
const persistence = await persistenceHelpers.testIndexedDbPersistence();
443-
const localStore = new MultiTabLocalStore(
443+
const localStore = newMultiTabLocalStore(
444444
persistence,
445445
queryEngine,
446446
User.UNAUTHENTICATED
@@ -467,7 +467,7 @@ describe('LocalStore w/ IndexedDB Persistence (IndexFreeQueryEngine)', () => {
467467
QueryEngineType.IndexFree
468468
);
469469
const persistence = await persistenceHelpers.testIndexedDbPersistence();
470-
const localStore = new MultiTabLocalStore(
470+
const localStore = newMultiTabLocalStore(
471471
persistence,
472472
queryEngine,
473473
User.UNAUTHENTICATED

0 commit comments

Comments
 (0)