@firebase/firestore
Version:
This is the [Cloud Firestore](https://firebase.google.com/docs/firestore/) component of the [Firebase JS SDK](https://www.npmjs.com/package/firebase).
283 lines (282 loc) • 14.2 kB
TypeScript
/**
* @license
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { User } from '../auth/user';
import { DatabaseInfo } from '../core/database_info';
import { SequenceNumberSyncer } from '../core/listen_sequence';
import { ListenSequenceNumber } from '../core/types';
import { DocumentKey } from '../model/document_key';
import { Platform } from '../platform/platform';
import { JsonProtoSerializer } from '../remote/serializer';
import { AsyncQueue } from '../util/async_queue';
import { FirestoreError } from '../util/error';
import { IndexedDbIndexManager } from './indexeddb_index_manager';
import { IndexedDbRemoteDocumentCache } from './indexeddb_remote_document_cache';
import { IndexedDbTargetCache } from './indexeddb_target_cache';
import { ActiveTargets, LruDelegate, LruGarbageCollector, LruParams } from './lru_garbage_collector';
import { MutationQueue } from './mutation_queue';
import { Persistence, PersistenceTransaction, PersistenceTransactionMode, PrimaryStateListener, ReferenceDelegate } from './persistence';
import { PersistencePromise } from './persistence_promise';
import { ReferenceSet } from './reference_set';
import { ClientId } from './shared_client_state';
import { TargetData } from './target_data';
import { SimpleDbStore, SimpleDbTransaction } from './simple_db';
export declare class IndexedDbTransaction extends PersistenceTransaction {
readonly simpleDbTransaction: SimpleDbTransaction;
readonly currentSequenceNumber: ListenSequenceNumber;
constructor(simpleDbTransaction: SimpleDbTransaction, currentSequenceNumber: ListenSequenceNumber);
}
/**
* An IndexedDB-backed instance of Persistence. Data is stored persistently
* across sessions.
*
* On Web only, the Firestore SDKs support shared access to its persistence
* layer. This allows multiple browser tabs to read and write to IndexedDb and
* to synchronize state even without network connectivity. Shared access is
* currently optional and not enabled unless all clients invoke
* `enablePersistence()` with `{synchronizeTabs:true}`.
*
* In multi-tab mode, if multiple clients are active at the same time, the SDK
* will designate one client as the “primary client”. An effort is made to pick
* a visible, network-connected and active client, and this client is
* responsible for letting other clients know about its presence. The primary
* client writes a unique client-generated identifier (the client ID) to
* IndexedDb’s “owner” store every 4 seconds. If the primary client fails to
* update this entry, another client can acquire the lease and take over as
* primary.
*
* Some persistence operations in the SDK are designated as primary-client only
* operations. This includes the acknowledgment of mutations and all updates of
* remote documents. The effects of these operations are written to persistence
* and then broadcast to other tabs via LocalStorage (see
* `WebStorageSharedClientState`), which then refresh their state from
* persistence.
*
* Similarly, the primary client listens to notifications sent by secondary
* clients to discover persistence changes written by secondary clients, such as
* the addition of new mutations and query targets.
*
* If multi-tab is not enabled and another tab already obtained the primary
* lease, IndexedDbPersistence enters a failed state and all subsequent
* operations will automatically fail.
*
* Additionally, there is an optimization so that when a tab is closed, the
* primary lease is released immediately (this is especially important to make
* sure that a refreshed tab is able to immediately re-acquire the primary
* lease). Unfortunately, IndexedDB cannot be reliably used in window.unload
* since it is an asynchronous API. So in addition to attempting to give up the
* lease, the leaseholder writes its client ID to a "zombiedClient" entry in
* LocalStorage which acts as an indicator that another tab should go ahead and
* take the primary lease immediately regardless of the current lease timestamp.
*
* TODO(b/114226234): Remove `synchronizeTabs` section when multi-tab is no
* longer optional.
*/
export declare class IndexedDbPersistence implements Persistence {
private readonly allowTabSynchronization;
private readonly persistenceKey;
private readonly clientId;
private readonly queue;
private readonly sequenceNumberSyncer;
static getStore<Key extends IDBValidKey, Value>(txn: PersistenceTransaction, store: string): SimpleDbStore<Key, Value>;
/**
* The name of the main (and currently only) IndexedDB database. this name is
* appended to the prefix provided to the IndexedDbPersistence constructor.
*/
static MAIN_DATABASE: string;
static createIndexedDbPersistence(options: {
allowTabSynchronization: boolean;
persistenceKey: string;
clientId: ClientId;
platform: Platform;
lruParams: LruParams;
queue: AsyncQueue;
serializer: JsonProtoSerializer;
sequenceNumberSyncer: SequenceNumberSyncer;
}): Promise<IndexedDbPersistence>;
private readonly document;
private readonly window;
private simpleDb;
private listenSequence;
private _started;
private isPrimary;
private networkEnabled;
private dbName;
/** Our window.unload handler, if registered. */
private windowUnloadHandler;
private inForeground;
private serializer;
/** Our 'visibilitychange' listener if registered. */
private documentVisibilityHandler;
/** The client metadata refresh task. */
private clientMetadataRefresher;
/** The last time we garbage collected the client metadata object store. */
private lastGarbageCollectionTime;
/** A listener to notify on primary state changes. */
private primaryStateListener;
private readonly targetCache;
private readonly indexManager;
private readonly remoteDocumentCache;
private readonly webStorage;
readonly referenceDelegate: IndexedDbLruDelegate;
private constructor();
/**
* Attempt to start IndexedDb persistence.
*
* @return {Promise<void>} Whether persistence was enabled.
*/
private start;
setPrimaryStateListener(primaryStateListener: PrimaryStateListener): Promise<void>;
setDatabaseDeletedListener(databaseDeletedListener: () => Promise<void>): void;
setNetworkEnabled(networkEnabled: boolean): void;
/**
* Updates the client metadata in IndexedDb and attempts to either obtain or
* extend the primary lease for the local client. Asynchronously notifies the
* primary state listener if the client either newly obtained or released its
* primary lease.
*/
private updateClientMetadataAndTryBecomePrimary;
private verifyPrimaryLease;
private removeClientMetadata;
/**
* If the garbage collection threshold has passed, prunes the
* RemoteDocumentChanges and the ClientMetadata store based on the last update
* time of all clients.
*/
private maybeGarbageCollectMultiClientState;
/**
* Schedules a recurring timer to update the client metadata and to either
* extend or acquire the primary lease if the client is eligible.
*/
private scheduleClientMetadataAndPrimaryLeaseRefreshes;
/** Checks whether `client` is the local client. */
private isLocalClient;
/**
* Evaluate the state of all active clients and determine whether the local
* client is or can act as the holder of the primary lease. Returns whether
* the client is eligible for the lease, but does not actually acquire it.
* May return 'false' even if there is no active leaseholder and another
* (foreground) client should become leaseholder instead.
*/
private canActAsPrimary;
shutdown(): Promise<void>;
/**
* Returns clients that are not zombied and have an updateTime within the
* provided threshold.
*/
private filterActiveClients;
getActiveClients(): Promise<ClientId[]>;
static clearPersistence(persistenceKey: string): Promise<void>;
get started(): boolean;
getMutationQueue(user: User): MutationQueue;
getTargetCache(): IndexedDbTargetCache;
getRemoteDocumentCache(): IndexedDbRemoteDocumentCache;
getIndexManager(): IndexedDbIndexManager;
runTransaction<T>(action: string, mode: PersistenceTransactionMode, transactionOperation: (transaction: PersistenceTransaction) => PersistencePromise<T>): Promise<T>;
/**
* Verifies that the current tab is the primary leaseholder or alternatively
* that the leaseholder has opted into multi-tab synchronization.
*/
private verifyAllowTabSynchronization;
/**
* Obtains or extends the new primary lease for the local client. This
* method does not verify that the client is eligible for this lease.
*/
private acquireOrExtendPrimaryLease;
static isAvailable(): boolean;
/**
* Generates a string used as a prefix when storing data in IndexedDB and
* LocalStorage.
*/
static buildStoragePrefix(databaseInfo: DatabaseInfo): string;
/** Checks the primary lease and removes it if we are the current primary. */
private releasePrimaryLeaseIfHeld;
/** Verifies that `updateTimeMs` is within `maxAgeMs`. */
private isWithinAge;
private attachVisibilityHandler;
private detachVisibilityHandler;
/**
* Attaches a window.unload handler that will synchronously write our
* clientId to a "zombie client id" location in LocalStorage. This can be used
* by tabs trying to acquire the primary lease to determine that the lease
* is no longer valid even if the timestamp is recent. This is particularly
* important for the refresh case (so the tab correctly re-acquires the
* primary lease). LocalStorage is used for this rather than IndexedDb because
* it is a synchronous API and so can be used reliably from an unload
* handler.
*/
private attachWindowUnloadHook;
private detachWindowUnloadHook;
/**
* Returns whether a client is "zombied" based on its LocalStorage entry.
* Clients become zombied when their tab closes without running all of the
* cleanup logic in `shutdown()`.
*/
private isClientZombied;
/**
* Record client as zombied (a client that had its tab closed). Zombied
* clients are ignored during primary tab selection.
*/
private markClientZombied;
/** Removes the zombied client entry if it exists. */
private removeClientZombiedEntry;
private zombiedClientLocalStorageKey;
}
/**
* Verifies the error thrown by a LocalStore operation. If a LocalStore
* operation fails because the primary lease has been taken by another client,
* we ignore the error (the persistence layer will immediately call
* `applyPrimaryLease` to propagate the primary state change). All other errors
* are re-thrown.
*
* @param err An error returned by a LocalStore operation.
* @return A Promise that resolves after we recovered, or the original error.
*/
export declare function ignoreIfPrimaryLeaseLoss(err: FirestoreError): Promise<void>;
/** Provides LRU functionality for IndexedDB persistence. */
export declare class IndexedDbLruDelegate implements ReferenceDelegate, LruDelegate {
private readonly db;
private inMemoryPins;
readonly garbageCollector: LruGarbageCollector;
constructor(db: IndexedDbPersistence, params: LruParams);
getSequenceNumberCount(txn: PersistenceTransaction): PersistencePromise<number>;
private orphanedDocmentCount;
forEachTarget(txn: PersistenceTransaction, f: (q: TargetData) => void): PersistencePromise<void>;
forEachOrphanedDocumentSequenceNumber(txn: PersistenceTransaction, f: (sequenceNumber: ListenSequenceNumber) => void): PersistencePromise<void>;
setInMemoryPins(inMemoryPins: ReferenceSet): void;
addReference(txn: PersistenceTransaction, key: DocumentKey): PersistencePromise<void>;
removeReference(txn: PersistenceTransaction, key: DocumentKey): PersistencePromise<void>;
removeTargets(txn: PersistenceTransaction, upperBound: ListenSequenceNumber, activeTargetIds: ActiveTargets): PersistencePromise<number>;
removeMutationReference(txn: PersistenceTransaction, key: DocumentKey): PersistencePromise<void>;
/**
* Returns true if anything would prevent this document from being garbage
* collected, given that the document in question is not present in any
* targets and has a sequence number less than or equal to the upper bound for
* the collection run.
*/
private isPinned;
removeOrphanedDocuments(txn: PersistenceTransaction, upperBound: ListenSequenceNumber): PersistencePromise<number>;
removeTarget(txn: PersistenceTransaction, targetData: TargetData): PersistencePromise<void>;
updateLimboDocument(txn: PersistenceTransaction, key: DocumentKey): PersistencePromise<void>;
/**
* Call provided function for each document in the cache that is 'orphaned'. Orphaned
* means not a part of any target, so the only entry in the target-document index for
* that document will be the sentinel row (targetId 0), which will also have the sequence
* number for the last time the document was accessed.
*/
private forEachOrphanedDocument;
getCacheSize(txn: PersistenceTransaction): PersistencePromise<number>;
}