Skip to content

Commit 2555b58

Browse files
committed
New way to config Firestore SDK Cache.
1 parent 27b5e7d commit 2555b58

File tree

6 files changed

+227
-15
lines changed

6 files changed

+227
-15
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import {
19+
IndexedDbOfflineComponentProvider,
20+
MemoryOfflineComponentProvider,
21+
MultiTabOfflineComponentProvider,
22+
OfflineComponentProvider,
23+
OnlineComponentProvider
24+
} from '../core/component_provider';
25+
26+
export interface MemoryLocalCache {
27+
kind: 'memory';
28+
_onlineComponentProvider: OnlineComponentProvider;
29+
_offlineComponentProvider: MemoryOfflineComponentProvider;
30+
}
31+
32+
class MemoryLocalCacheImpl implements MemoryLocalCache {
33+
kind: 'memory' = 'memory';
34+
_onlineComponentProvider: OnlineComponentProvider;
35+
_offlineComponentProvider: MemoryOfflineComponentProvider;
36+
37+
constructor() {
38+
this._onlineComponentProvider = new OnlineComponentProvider();
39+
this._offlineComponentProvider = new MemoryOfflineComponentProvider();
40+
}
41+
}
42+
43+
export interface IndexedDbLocalCache {
44+
kind: 'indexeddb';
45+
_onlineComponentProvider: OnlineComponentProvider;
46+
_offlineComponentProvider: OfflineComponentProvider;
47+
}
48+
49+
class IndexedDbLocalCacheImpl implements IndexedDbLocalCache {
50+
kind: 'indexeddb' = 'indexeddb';
51+
_onlineComponentProvider: OnlineComponentProvider;
52+
_offlineComponentProvider: OfflineComponentProvider;
53+
54+
constructor(settings: IndexedDbSettings | undefined) {
55+
let tabManager: IndexedDbTabManager;
56+
if (settings?.tabManager) {
57+
settings.tabManager.initialize(settings);
58+
tabManager = settings.tabManager;
59+
} else {
60+
tabManager = indexedDbSingleTabManager(undefined);
61+
tabManager.initialize(settings);
62+
}
63+
this._onlineComponentProvider = tabManager._onlineComponentProvider!;
64+
this._offlineComponentProvider = tabManager._offlineComponentProvider!;
65+
}
66+
}
67+
68+
export type FirestoreLocalCache = MemoryLocalCache | IndexedDbLocalCache;
69+
70+
// Factory function
71+
export function memoryLocalCache(): MemoryLocalCache {
72+
return new MemoryLocalCacheImpl();
73+
}
74+
75+
export interface IndexedDbSettings {
76+
cacheSizeBytes?: number;
77+
// default to singleTabManager({forceOwnership: false})
78+
tabManager?: IndexedDbTabManager;
79+
}
80+
81+
// Factory function
82+
export function indexedDbLocalCache(
83+
settings?: IndexedDbSettings
84+
): IndexedDbLocalCache {
85+
return new IndexedDbLocalCacheImpl(settings);
86+
}
87+
88+
export interface IndexedDbSingleTabManager {
89+
kind: 'indexedDbSingleTab';
90+
initialize: (
91+
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
92+
) => void;
93+
_onlineComponentProvider?: OnlineComponentProvider;
94+
_offlineComponentProvider?: OfflineComponentProvider;
95+
}
96+
97+
class SingleTabManagerImpl implements IndexedDbSingleTabManager {
98+
kind: 'indexedDbSingleTab' = 'indexedDbSingleTab';
99+
100+
_onlineComponentProvider?: OnlineComponentProvider;
101+
_offlineComponentProvider?: OfflineComponentProvider;
102+
103+
constructor(private forceOwnership?: boolean) {}
104+
105+
initialize(
106+
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
107+
): void {
108+
this._onlineComponentProvider = new OnlineComponentProvider();
109+
this._offlineComponentProvider = new IndexedDbOfflineComponentProvider(
110+
this._onlineComponentProvider,
111+
settings?.cacheSizeBytes,
112+
this.forceOwnership
113+
);
114+
}
115+
}
116+
117+
export interface IndexedDbMultipleTabManager {
118+
kind: 'IndexedDbMultipleTab';
119+
initialize: (settings: Omit<IndexedDbSettings, 'tabManager'>) => void;
120+
_onlineComponentProvider?: OnlineComponentProvider;
121+
_offlineComponentProvider?: OfflineComponentProvider;
122+
}
123+
124+
class MultiTabManagerImpl implements IndexedDbMultipleTabManager {
125+
kind: 'IndexedDbMultipleTab' = 'IndexedDbMultipleTab';
126+
127+
_onlineComponentProvider?: OnlineComponentProvider;
128+
_offlineComponentProvider?: OfflineComponentProvider;
129+
130+
initialize(
131+
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
132+
): void {
133+
this._onlineComponentProvider = new OnlineComponentProvider();
134+
this._offlineComponentProvider = new MultiTabOfflineComponentProvider(
135+
this._onlineComponentProvider,
136+
settings?.cacheSizeBytes
137+
);
138+
}
139+
}
140+
141+
export type IndexedDbTabManager =
142+
| IndexedDbSingleTabManager
143+
| IndexedDbMultipleTabManager;
144+
145+
export function indexedDbSingleTabManager(
146+
settings: { forceOwnership?: boolean } | undefined
147+
): IndexedDbSingleTabManager {
148+
return new SingleTabManagerImpl(settings?.forceOwnership);
149+
}
150+
export function indexedDbMultipleTabManager(): IndexedDbMultipleTabManager {
151+
return new MultiTabManagerImpl();
152+
}

packages/firestore/src/api/database.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ export function configureFirestore(firestore: Firestore): void {
283283
firestore._queue,
284284
databaseInfo
285285
);
286+
if (
287+
settings.cache?._offlineComponentProvider &&
288+
settings.cache?._onlineComponentProvider
289+
) {
290+
firestore._firestoreClient.uninitializedComponentsProvider = {
291+
offline: settings.cache._offlineComponentProvider,
292+
online: settings.cache._onlineComponentProvider
293+
};
294+
}
286295
}
287296

288297
/**
@@ -308,6 +317,7 @@ export function configureFirestore(firestore: Firestore): void {
308317
* persistence.
309318
* @returns A `Promise` that represents successfully enabling persistent storage.
310319
*/
320+
// TODO(wuandy): mark obselete
311321
export function enableIndexedDbPersistence(
312322
firestore: Firestore,
313323
persistenceSettings?: PersistenceSettings
@@ -316,6 +326,10 @@ export function enableIndexedDbPersistence(
316326
verifyNotInitialized(firestore);
317327

318328
const client = ensureFirestoreConfigured(firestore);
329+
if (client.uninitializedComponentsProvider) {
330+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Already specified.');
331+
}
332+
319333
const settings = firestore._freezeSettings();
320334

321335
const onlineComponentProvider = new OnlineComponentProvider();
@@ -353,13 +367,18 @@ export function enableIndexedDbPersistence(
353367
* @returns A `Promise` that represents successfully enabling persistent
354368
* storage.
355369
*/
370+
// TODO(wuandy): mark obselete
356371
export function enableMultiTabIndexedDbPersistence(
357372
firestore: Firestore
358373
): Promise<void> {
359374
firestore = cast(firestore, Firestore);
360375
verifyNotInitialized(firestore);
361376

362377
const client = ensureFirestoreConfigured(firestore);
378+
if (client.uninitializedComponentsProvider) {
379+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Already specified.');
380+
}
381+
363382
const settings = firestore._freezeSettings();
364383

365384
const onlineComponentProvider = new OnlineComponentProvider();

packages/firestore/src/api/settings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import { FirestoreSettings as LiteSettings } from '../lite-api/settings';
1919

20+
import { FirestoreLocalCache } from './cache_config';
21+
2022
export { DEFAULT_HOST } from '../lite-api/settings';
2123

2224
/**
@@ -30,6 +32,7 @@ export interface PersistenceSettings {
3032
* Workers. Setting this to `true` will enable persistence, but cause other
3133
* tabs using persistence to fail.
3234
*/
35+
// TODO(wuandy): Deprecate this
3336
forceOwnership?: boolean;
3437
}
3538

@@ -48,8 +51,11 @@ export interface FirestoreSettings extends LiteSettings {
4851
* The default value is 40 MB. The threshold must be set to at least 1 MB, and
4952
* can be set to `CACHE_SIZE_UNLIMITED` to disable garbage collection.
5053
*/
54+
// TODO(wuandy): Deprecate this
5155
cacheSizeBytes?: number;
5256

57+
cache?: FirestoreLocalCache;
58+
5359
/**
5460
* Forces the SDK’s underlying network transport (WebChannel) to use
5561
* long-polling. Each response from the backend will be closed immediately

packages/firestore/src/core/firestore_client.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ export class FirestoreClient {
112112
appCheckToken: string,
113113
user: User
114114
) => Promise<void> = () => Promise.resolve();
115+
uninitializedComponentsProvider?: {
116+
offline: OfflineComponentProvider;
117+
online: OnlineComponentProvider;
118+
};
115119

116120
offlineComponents?: OfflineComponentProvider;
117121
onlineComponents?: OnlineComponentProvider;
@@ -120,12 +124,12 @@ export class FirestoreClient {
120124
private authCredentials: CredentialsProvider<User>,
121125
private appCheckCredentials: CredentialsProvider<string>,
122126
/**
123-
* Asynchronous queue responsible for all of our internal processing. When
124-
* we get incoming work from the user (via public API) or the network
125-
* (incoming GRPC messages), we should always schedule onto this queue.
126-
* This ensures all of our work is properly serialized (e.g. we don't
127-
* start processing a new operation while the previous one is waiting for
128-
* an async I/O to complete).
127+
* Asynchronous queue responsible for all of our internal processing. When //
128+
* we get incoming work from the user (via public API) or the network //
129+
* (incoming GRPC messages), we should always schedule onto this queue. //
130+
* This ensures all of our work is properly serialized (e.g. we don't //
131+
* start processing a new operation while the previous one is waiting for //
132+
* an async I/O to complete). //
129133
*/
130134
public asyncQueue: AsyncQueue,
131135
private databaseInfo: DatabaseInfo
@@ -265,11 +269,19 @@ async function ensureOfflineComponents(
265269
client: FirestoreClient
266270
): Promise<OfflineComponentProvider> {
267271
if (!client.offlineComponents) {
268-
logDebug(LOG_TAG, 'Using default OfflineComponentProvider');
269-
await setOfflineComponentProvider(
270-
client,
271-
new MemoryOfflineComponentProvider()
272-
);
272+
if (client.uninitializedComponentsProvider) {
273+
logDebug(LOG_TAG, 'Using user provided OfflineComponentProvider');
274+
await setOfflineComponentProvider(
275+
client,
276+
client.uninitializedComponentsProvider.offline
277+
);
278+
} else {
279+
logDebug(LOG_TAG, 'Using default OfflineComponentProvider');
280+
await setOfflineComponentProvider(
281+
client,
282+
new MemoryOfflineComponentProvider()
283+
);
284+
}
273285
}
274286

275287
return client.offlineComponents!;
@@ -279,8 +291,16 @@ async function ensureOnlineComponents(
279291
client: FirestoreClient
280292
): Promise<OnlineComponentProvider> {
281293
if (!client.onlineComponents) {
282-
logDebug(LOG_TAG, 'Using default OnlineComponentProvider');
283-
await setOnlineComponentProvider(client, new OnlineComponentProvider());
294+
if (client.uninitializedComponentsProvider) {
295+
logDebug(LOG_TAG, 'Using user provided OnlineComponentProvider');
296+
await setOnlineComponentProvider(
297+
client,
298+
client.uninitializedComponentsProvider.online
299+
);
300+
} else {
301+
logDebug(LOG_TAG, 'Using default OnlineComponentProvider');
302+
await setOnlineComponentProvider(client, new OnlineComponentProvider());
303+
}
284304
}
285305

286306
return client.onlineComponents!;

packages/firestore/src/lite-api/settings.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { FirestoreLocalCache } from '../api/cache_config';
1819
import { CredentialsSettings } from '../api/credentials';
1920
import {
2021
LRU_COLLECTION_DISABLED,
@@ -23,6 +24,7 @@ import {
2324
import { LRU_MINIMUM_CACHE_SIZE_BYTES } from '../local/lru_garbage_collector_impl';
2425
import { Code, FirestoreError } from '../util/error';
2526
import { validateIsNotUsedTogether } from '../util/input_validation';
27+
import { logWarn } from '../util/log';
2628

2729
// settings() defaults:
2830
export const DEFAULT_HOST = 'firestore.googleapis.com';
@@ -60,6 +62,8 @@ export interface PrivateSettings extends FirestoreSettings {
6062
experimentalAutoDetectLongPolling?: boolean;
6163
// Used in firestore@exp
6264
useFetchStreams?: boolean;
65+
66+
cache?: FirestoreLocalCache;
6367
}
6468

6569
/**
@@ -83,6 +87,7 @@ export class FirestoreSettingsImpl {
8387
readonly ignoreUndefinedProperties: boolean;
8488

8589
readonly useFetchStreams: boolean;
90+
readonly cache?: FirestoreLocalCache;
8691

8792
// Can be a google-auth-library or gapi client.
8893
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -105,6 +110,12 @@ export class FirestoreSettingsImpl {
105110

106111
this.credentials = settings.credentials;
107112
this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
113+
logWarn(
114+
`Setting offline cache to ${JSON.stringify(
115+
settings
116+
)} from PrivateSettings`
117+
);
118+
this.cache = settings.cache;
108119

109120
if (settings.cacheSizeBytes === undefined) {
110121
this.cacheSizeBytes = LRU_DEFAULT_CACHE_SIZE_BYTES;

packages/firestore/test/integration/util/helpers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import { isIndexedDBAvailable } from '@firebase/util';
1919

20+
import { indexedDbLocalCache } from '../../../src/api/cache_config';
21+
import { logWarn } from '../../../src/util/log';
22+
2023
import {
2124
collection,
2225
doc,
@@ -184,10 +187,11 @@ export async function withTestDbsSettings(
184187
const dbs: Firestore[] = [];
185188

186189
for (let i = 0; i < numDbs; i++) {
187-
const db = newTestFirestore(newTestApp(projectId), settings);
190+
logWarn(`set persistence from helper: ${persistence}`);
188191
if (persistence) {
189-
await enableIndexedDbPersistence(db);
192+
settings.cache = indexedDbLocalCache();
190193
}
194+
const db = newTestFirestore(newTestApp(projectId), settings);
191195
dbs.push(db);
192196
}
193197

0 commit comments

Comments
 (0)