Skip to content

Compat Layer for Firestore #4003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions packages/firestore/exp/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from '../../../src/core/component_provider';
import {
FirebaseFirestore as LiteFirestore,
FirestoreDatabase,
Settings as LiteSettings
} from '../../../lite/src/api/database';
import { Code, FirestoreError } from '../../../src/util/error';
Expand All @@ -45,8 +46,7 @@ import { LRU_MINIMUM_CACHE_SIZE_BYTES } from '../../../src/local/lru_garbage_col
import {
CACHE_SIZE_UNLIMITED,
configureFirestore,
ensureFirestoreConfigured,
FirestoreCompat
ensureFirestoreConfigured
} from '../../../src/api/database';
import {
indexedDbClearPersistence,
Expand All @@ -70,18 +70,18 @@ export interface Settings extends LiteSettings {
*/
export class FirebaseFirestore
extends LiteFirestore
implements _FirebaseService, FirestoreCompat {
implements _FirebaseService {
readonly _queue = new AsyncQueue();
readonly _persistenceKey: string;

_firestoreClient: FirestoreClient | undefined;

constructor(
app: FirebaseApp,
app: FirestoreDatabase | FirebaseApp,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this parameter name be databaseIdOrApp to match other similar ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

authProvider: Provider<FirebaseAuthInternalName>
) {
super(app, authProvider);
this._persistenceKey = app.name;
this._persistenceKey = 'name' in app ? app.name : '[DEFAULT]';
}

_terminate(): Promise<void> {
Expand Down Expand Up @@ -165,13 +165,13 @@ export function getFirestore(app: FirebaseApp): FirebaseFirestore {
* @return A promise that represents successfully enabling persistent storage.
*/
export function enableIndexedDbPersistence(
firestore: FirestoreCompat,
firestore: FirebaseFirestore,
persistenceSettings?: PersistenceSettings
): Promise<void> {
verifyNotInitialized(firestore);

const client = ensureFirestoreConfigured(firestore);
const settings = firestore._getSettings();
const settings = firestore._freezeSettings();

const onlineComponentProvider = new OnlineComponentProvider();
const offlineComponentProvider = new IndexedDbOfflineComponentProvider(
Expand Down Expand Up @@ -209,12 +209,12 @@ export function enableIndexedDbPersistence(
* storage.
*/
export function enableMultiTabIndexedDbPersistence(
firestore: FirestoreCompat
firestore: FirebaseFirestore
): Promise<void> {
verifyNotInitialized(firestore);

const client = ensureFirestoreConfigured(firestore);
const settings = firestore._getSettings();
const settings = firestore._freezeSettings();

const onlineComponentProvider = new OnlineComponentProvider();
const offlineComponentProvider = new MultiTabOfflineComponentProvider(
Expand Down Expand Up @@ -322,7 +322,7 @@ function canFallbackFromIndexedDbError(
* cleared. Otherwise, the promise is rejected with an error.
*/
export function clearIndexedDbPersistence(
firestore: FirestoreCompat
firestore: FirebaseFirestore
): Promise<void> {
if (firestore._initialized && !firestore._terminated) {
throw new FirestoreError(
Expand Down Expand Up @@ -420,7 +420,7 @@ export function terminate(firestore: FirebaseFirestore): Promise<void> {
return firestore._delete();
}

function verifyNotInitialized(firestore: FirestoreCompat): void {
function verifyNotInitialized(firestore: FirebaseFirestore): void {
if (firestore._initialized || firestore._terminated) {
throw new FirestoreError(
Code.FAILED_PRECONDITION,
Expand Down
166 changes: 22 additions & 144 deletions packages/firestore/exp/test/shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,29 @@
* limitations under the License.
*/

import { FirebaseApp as FirebaseAppLegacy } from '@firebase/app-types';
import { FirebaseApp as FirebaseAppExp } from '@firebase/app-types-exp';
import { deleteApp } from '@firebase/app-exp';
import * as legacy from '@firebase/firestore-types';
import * as exp from '../index';

import {
addDoc,
clearIndexedDbPersistence,
collection,
collectionGroup,
deleteDoc,
disableNetwork,
doc,
DocumentReference as DocumentReferenceExp,
enableIndexedDbPersistence,
enableMultiTabIndexedDbPersistence,
enableNetwork,
FieldPath as FieldPathExp,
getDoc,
getDocFromCache,
getDocFromServer,
getDocs,
getDocsFromCache,
getDocsFromServer,
initializeFirestore,
onSnapshot,
onSnapshotsInSync,
query,
queryEqual,
refEqual,
runTransaction,
setDoc,
snapshotEqual,
terminate,
updateDoc,
waitForPendingWrites,
writeBatch,
endAt,
endBefore,
startAfter,
Expand All @@ -70,115 +55,21 @@ import {
validateSetOptions
} from '../../src/util/input_validation';
import { Compat } from '../../src/compat/compat';
import { Firestore } from '../../src/api/database';

export { GeoPoint, Timestamp } from '../index';
export { FieldValue } from '../../src/compat/field_value';

/* eslint-disable @typescript-eslint/no-explicit-any */

// This module defines a shim layer that implements the legacy API on top
// of the experimental SDK. This shim is used to run integration tests against
// both SDK versions.

export class FirebaseApp
extends Compat<FirebaseAppExp>
implements FirebaseAppLegacy {
name = this._delegate.name;
options = this._delegate.options;
automaticDataCollectionEnabled = this._delegate
.automaticDataCollectionEnabled;

delete(): Promise<void> {
return deleteApp(this._delegate);
}
}

export class FirebaseFirestore
extends Compat<exp.FirebaseFirestore>
implements legacy.FirebaseFirestore {
app = new FirebaseApp(this._delegate.app);

settings(settings: legacy.Settings): void {
initializeFirestore(this.app._delegate, settings);
}

useEmulator(host: string, port: number): void {
this.settings({ host: `${host}:${port}`, ssl: false, merge: true });
}

enablePersistence(settings?: legacy.PersistenceSettings): Promise<void> {
return settings?.synchronizeTabs
? enableMultiTabIndexedDbPersistence(this._delegate)
: enableIndexedDbPersistence(this._delegate);
}

collection(collectionPath: string): CollectionReference<legacy.DocumentData> {
return new CollectionReference(
this,
collection(this._delegate, collectionPath)
);
}

doc(documentPath: string): DocumentReference<legacy.DocumentData> {
return new DocumentReference(this, doc(this._delegate, documentPath));
}

collectionGroup(collectionId: string): Query<legacy.DocumentData> {
return new Query(this, collectionGroup(this._delegate, collectionId));
}

runTransaction<T>(
updateFunction: (transaction: legacy.Transaction) => Promise<T>
): Promise<T> {
return runTransaction(this._delegate, t =>
updateFunction(new Transaction(this, t))
);
}

batch(): legacy.WriteBatch {
return new WriteBatch(writeBatch(this._delegate));
}

clearPersistence(): Promise<void> {
return clearIndexedDbPersistence(this._delegate);
}

enableNetwork(): Promise<void> {
return enableNetwork(this._delegate);
}

disableNetwork(): Promise<void> {
return disableNetwork(this._delegate);
}

waitForPendingWrites(): Promise<void> {
return waitForPendingWrites(this._delegate);
}

onSnapshotsInSync(observer: {
next?: (value: void) => void;
error?: (error: legacy.FirestoreError) => void;
complete?: () => void;
}): () => void;
onSnapshotsInSync(onSync: () => void): () => void;
onSnapshotsInSync(arg: any): () => void {
return onSnapshotsInSync(this._delegate, arg);
}

terminate(): Promise<void> {
return terminate(this._delegate);
}

INTERNAL = {
delete: () => terminate(this._delegate)
};
}

export class Transaction
extends Compat<exp.Transaction>
implements legacy.Transaction {
constructor(
private readonly _firestore: FirebaseFirestore,
private readonly _firestore: Firestore,
delegate: exp.Transaction
) {
super(delegate);
Expand Down Expand Up @@ -301,7 +192,7 @@ export class DocumentReference<T = legacy.DocumentData>
extends Compat<exp.DocumentReference<T>>
implements legacy.DocumentReference<T> {
constructor(
readonly firestore: FirebaseFirestore,
readonly firestore: Firestore,
delegate: exp.DocumentReference<T>
) {
super(delegate);
Expand Down Expand Up @@ -424,7 +315,7 @@ export class DocumentSnapshot<T = legacy.DocumentData>
extends Compat<exp.DocumentSnapshot<T>>
implements legacy.DocumentSnapshot<T> {
constructor(
private readonly _firestore: FirebaseFirestore,
private readonly _firestore: Firestore,
delegate: exp.DocumentSnapshot<T>
) {
super(delegate);
Expand All @@ -439,11 +330,14 @@ export class DocumentSnapshot<T = legacy.DocumentData>
}

data(options?: legacy.SnapshotOptions): T | undefined {
return wrap(this._delegate.data(options));
return wrap(this._firestore, this._delegate.data(options));
}

get(fieldPath: string | FieldPath, options?: legacy.SnapshotOptions): any {
return wrap(this._delegate.get(unwrap(fieldPath), options));
return wrap(
this._firestore,
this._delegate.get(unwrap(fieldPath), options)
);
}

isEqual(other: DocumentSnapshot<T>): boolean {
Expand All @@ -454,22 +348,15 @@ export class DocumentSnapshot<T = legacy.DocumentData>
export class QueryDocumentSnapshot<T = legacy.DocumentData>
extends DocumentSnapshot<T>
implements legacy.QueryDocumentSnapshot<T> {
constructor(
firestore: FirebaseFirestore,
readonly _delegate: exp.QueryDocumentSnapshot<T>
) {
super(firestore, _delegate);
}

data(options?: legacy.SnapshotOptions): T {
return this._delegate.data(options);
return this._delegate.data(options)!;
}
}

export class Query<T = legacy.DocumentData>
extends Compat<exp.Query<T>>
implements legacy.Query<T> {
constructor(readonly firestore: FirebaseFirestore, delegate: exp.Query<T>) {
constructor(readonly firestore: Firestore, delegate: exp.Query<T>) {
super(delegate);
}

Expand Down Expand Up @@ -592,7 +479,7 @@ export class Query<T = legacy.DocumentData>
export class QuerySnapshot<T = legacy.DocumentData>
implements legacy.QuerySnapshot<T> {
constructor(
readonly _firestore: FirebaseFirestore,
readonly _firestore: Firestore,
readonly _delegate: exp.QuerySnapshot<T>
) {}

Expand Down Expand Up @@ -633,7 +520,7 @@ export class QuerySnapshot<T = legacy.DocumentData>
export class DocumentChange<T = legacy.DocumentData>
implements legacy.DocumentChange<T> {
constructor(
private readonly _firestore: FirebaseFirestore,
private readonly _firestore: Firestore,
private readonly _delegate: exp.DocumentChange<T>
) {}
readonly type = this._delegate.type;
Expand All @@ -649,7 +536,7 @@ export class CollectionReference<T = legacy.DocumentData>
extends Query<T>
implements legacy.CollectionReference<T> {
constructor(
firestore: FirebaseFirestore,
readonly firestore: Firestore,
readonly _delegate: exp.CollectionReference<T>
) {
super(firestore, _delegate);
Expand Down Expand Up @@ -698,15 +585,11 @@ export class CollectionReference<T = legacy.DocumentData>
}
}

export class FieldPath implements legacy.FieldPath {
private readonly fieldNames: string[];

export class FieldPath
extends Compat<FieldPathExp>
implements legacy.FieldPath {
constructor(...fieldNames: string[]) {
this.fieldNames = fieldNames;
}

get _delegate(): FieldPathExp {
return new FieldPathExp(...this.fieldNames);
super(new FieldPathExp(...fieldNames));
}

static documentId(): FieldPath {
Expand Down Expand Up @@ -744,25 +627,20 @@ export class Blob extends Compat<BytesExp> implements legacy.Blob {
* Takes document data that uses the firestore-exp API types and replaces them
* with the API types defined in this shim.
*/
function wrap(value: any): any {
function wrap(firestore: Firestore, value: any): any {
if (Array.isArray(value)) {
return value.map(v => wrap(v));
return value.map(v => wrap(firestore, v));
} else if (value instanceof FieldPathExp) {
return new FieldPath(...value._internalPath.toArray());
} else if (value instanceof BytesExp) {
return new Blob(value);
} else if (value instanceof DocumentReferenceExp) {
// TODO(mrschmidt): Ideally, we should use an existing instance of
// FirebaseFirestore here rather than instantiating a new instance
return new DocumentReference(
new FirebaseFirestore(value.firestore as exp.FirebaseFirestore),
value
);
return new DocumentReference(firestore, value);
} else if (isPlainObject(value)) {
const obj: any = {};
for (const key in value) {
if (value.hasOwnProperty(key)) {
obj[key] = wrap(value[key]);
obj[key] = wrap(firestore, value[key]);
}
}
return obj;
Expand Down
Loading