Skip to content

Release Bundles for Next SDK #4352

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 11 commits into from
Jan 29, 2021
28 changes: 28 additions & 0 deletions common/api-review/firestore-exp.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,33 @@ export function limit(limit: number): QueryConstraint;
// @public
export function limitToLast(limit: number): QueryConstraint;

// @public
export function loadBundle(firestore: FirebaseFirestore, bundleData: ReadableStream<Uint8Array> | ArrayBuffer | string): LoadBundleTask;

// @public
export class LoadBundleTask implements PromiseLike<LoadBundleTaskProgress> {
catch<R>(onRejected: (a: Error) => R | PromiseLike<R>): Promise<R | LoadBundleTaskProgress>;
_completeWith(progress: LoadBundleTaskProgress): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these _ methods should be tagged @internal in packages/firestore/src/exp/bundle.ts if we don't want to expose them publicly?

Copy link
Member

Choose a reason for hiding this comment

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

I think @schmidt-sebastian's script strips things with a leading underscore automatically in the d.ts file, but not in the api report.

Ideally this file should be consistent with d.ts. Maybe we can extend the script to update the api report too?

Copy link
Member

Choose a reason for hiding this comment

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

We don't have to do it in this PR of course.

Copy link
Contributor

Choose a reason for hiding this comment

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

Waiting for review :) #4207

_failWith(error: FirestoreError): void;
onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void;
then<T, R>(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike<T>, onRejected?: (a: Error) => R | PromiseLike<R>): Promise<T | R>;
_updateProgress(progress: LoadBundleTaskProgress): void;
}

// @public
export interface LoadBundleTaskProgress {
bytesLoaded: number;
documentsLoaded: number;
taskState: TaskState;
totalBytes: number;
totalDocuments: number;
}

export { LogLevel }

// @public
export function namedQuery(firestore: FirebaseFirestore, name: string): Promise<Query | null>;

// @public
export function onSnapshot<T>(reference: DocumentReference<T>, observer: {
next?: (snapshot: DocumentSnapshot<T>) => void;
Expand Down Expand Up @@ -463,6 +488,9 @@ export function startAt(snapshot: DocumentSnapshot_2<unknown>): QueryConstraint;
// @public
export function startAt(...fieldValues: unknown[]): QueryConstraint;

// @public
export type TaskState = 'Error' | 'Running' | 'Success';

// @public
export function terminate(firestore: FirebaseFirestore): Promise<void>;

Expand Down
10 changes: 9 additions & 1 deletion packages/firestore/exp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ export {
disableNetwork,
enableNetwork,
terminate,
useFirestoreEmulator
useFirestoreEmulator,
loadBundle,
namedQuery
} from '../src/exp/database';

export {
LoadBundleTask,
LoadBundleTaskProgress,
TaskState
} from '../src/exp/bundle';

export { Settings, PersistenceSettings } from '../src/exp/settings';

export {
Expand Down
32 changes: 31 additions & 1 deletion packages/firestore/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
* limitations under the License.
*/

import { Firestore, Query } from './src/api/database';
import { LoadBundleTask } from './src/exp/bundle';
import {
loadBundle as expLoadBundle,
namedQuery as expNamedQuery
} from './src/exp/database';

export { Blob } from './src/api/blob';
export {
CollectionReference,
Expand All @@ -36,4 +43,27 @@ export { FieldPath } from './src/api/field_path';
export { FieldValue } from './src/api/field_value';
export { Timestamp } from './src/api/timestamp';
export { FirebaseFirestore as ExpFirebaseFirestore } from './src/exp/database';
export { loadBundle, namedQuery } from './src/api/bundle';

export function loadBundle(
this: Firestore,
data: ArrayBuffer | ReadableStream<Uint8Array> | string
): LoadBundleTask {
return expLoadBundle(this._delegate, data);
}

export function namedQuery(
this: Firestore,
queryName: string
): Promise<Query | null> {
return expNamedQuery(this._delegate, queryName).then(expQuery => {
if (!expQuery) {
return null;
}
return new Query(
this,
// We can pass `expQuery` here directly since named queries don't have a UserDataConverter.
// Otherwise, we would have to create a new ExpQuery and pass the old UserDataConverter.
expQuery
);
});
}
14 changes: 2 additions & 12 deletions packages/firestore/index.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,8 @@ import { Firestore, loadBundle, namedQuery } from './export';
* Prototype patches bundle loading to Firestore.
*/
export function registerBundle(instance: typeof Firestore): void {
instance.prototype.loadBundle = function (
this: Firestore,
data: ArrayBuffer | ReadableStream<Uint8Array> | string
) {
return loadBundle(this, data);
};
instance.prototype.namedQuery = function (
this: Firestore,
queryName: string
) {
return namedQuery(this, queryName);
};
instance.prototype.loadBundle = loadBundle;
instance.prototype.namedQuery = namedQuery;
}

registerBundle(Firestore);
2 changes: 1 addition & 1 deletion packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
} from '@firebase/firestore-types';

import { DatabaseId } from '../core/database_info';
import { LoadBundleTask } from '../exp/bundle';
import { Bytes } from '../exp/bytes';
import {
clearIndexedDbPersistence,
Expand Down Expand Up @@ -122,7 +123,6 @@ import {
import { setLogLevel as setClientLogLevel } from '../util/log';

import { Blob } from './blob';
import { LoadBundleTask } from './bundle';
import { Compat } from './compat';
import {
CompleteFn,
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/src/core/firestore_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

import { GetOptions } from '@firebase/firestore-types';

import { LoadBundleTask } from '../api/bundle';
import {
CredentialChangeListener,
CredentialsProvider
} from '../api/credentials';
import { User } from '../auth/user';
import { LoadBundleTask } from '../exp/bundle';
import { LocalStore } from '../local/local_store';
import {
localStoreExecuteQuery,
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/src/core/sync_engine_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
* limitations under the License.
*/

import { LoadBundleTask } from '../api/bundle';
import { User } from '../auth/user';
import { LoadBundleTask } from '../exp/bundle';
import { ignoreIfPrimaryLeaseLoss, LocalStore } from '../local/local_store';
import {
localStoreAcknowledgeBatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,42 @@
* limitations under the License.
*/

import {
LoadBundleTask as ApiLoadBundleTask,
LoadBundleTaskProgress
} from '@firebase/firestore-types';

import { ensureFirestoreConfigured } from '../../src/exp/database';
import { Query as ExpQuery } from '../../src/exp/reference';
import {
firestoreClientGetNamedQuery,
firestoreClientLoadBundle
} from '../core/firestore_client';
import { PartialObserver } from '../api/observer';
import { debugAssert } from '../util/assert';
import { FirestoreError } from '../util/error';
import { Deferred } from '../util/promise';

import { Query, Firestore } from './database';
import { PartialObserver } from './observer';
/**
* Represents the state of bundle loading tasks.
*
* Both 'Error' and 'Success' are sinking state: task will abort or complete and there will
* be no more updates after they are reported.
*/
export type TaskState = 'Error' | 'Running' | 'Success';

export class LoadBundleTask
implements ApiLoadBundleTask, PromiseLike<LoadBundleTaskProgress> {
/**
* Represents a progress update or a final state from loading bundles.
*/
export interface LoadBundleTaskProgress {
/** How many documents have been loaded. */
documentsLoaded: number;
/** How many documents are in the bundle being loaded. */
totalDocuments: number;
/** How many bytes have been loaded. */
bytesLoaded: number;
/** How many bytes are in the bundle being loaded. */
totalBytes: number;
/** Current task state. */
taskState: TaskState;
}

/**
* Represents the task of loading a Firestore bundle. It provides progress of bundle
* loading, as well as task completion and error events.
*
* The API is compatible with `Promise<LoadBundleTaskProgress>`.
*/
export class LoadBundleTask implements PromiseLike<LoadBundleTaskProgress> {
private _progressObserver: PartialObserver<LoadBundleTaskProgress> = {};
private _taskCompletionResolver = new Deferred<LoadBundleTaskProgress>();

Expand All @@ -46,6 +62,17 @@ export class LoadBundleTask
documentsLoaded: 0
};

/**
* Registers functions to listen to bundle loading progress events.
* @param next
Copy link
Member

Choose a reason for hiding this comment

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

There should be a - after @param next. It is the format required by tsdoc.

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.

* Called when there is a progress update from bundle loading. Typically `next` calls occur
* each time a Firestore document is loaded from the bundle.
* @param error
* Called when an error occurs during bundle loading. The task aborts after reporting the
* error, and there should be no more updates after this.
* @param complete
* Called when the loading task is complete.
*/
onProgress(
next?: (progress: LoadBundleTaskProgress) => unknown,
error?: (err: Error) => unknown,
Expand All @@ -58,12 +85,27 @@ export class LoadBundleTask
};
}

/**
* Implements the `Promise<LoadBundleTaskProgress>.catch` interface.
*
* @param onRejected
* Called when an error occurs during bundle loading.
*/
catch<R>(
onRejected: (a: Error) => R | PromiseLike<R>
): Promise<R | LoadBundleTaskProgress> {
return this._taskCompletionResolver.promise.catch(onRejected);
}

/**
* Implements the `Promise<LoadBundleTaskProgress>.then` interface.
*
* @param onFulfilled
* Called on the completion of the loading task with a final `LoadBundleTaskProgress` update.
* The update will always have its `taskState` set to `"Success"`.
* @param onRejected
* Called when an error occurs during bundle loading.
*/
then<T, R>(
onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike<T>,
onRejected?: (a: Error) => R | PromiseLike<R>
Expand Down Expand Up @@ -122,30 +164,3 @@ export class LoadBundleTask
}
}
}

export function loadBundle(
db: Firestore,
bundleData: ArrayBuffer | ReadableStream<Uint8Array> | string
): LoadBundleTask {
const resultTask = new LoadBundleTask();
firestoreClientLoadBundle(
ensureFirestoreConfigured(db._delegate),
db._databaseId,
bundleData,
resultTask
);
return resultTask;
}

export function namedQuery(db: Firestore, name: string): Promise<Query | null> {
return firestoreClientGetNamedQuery(
ensureFirestoreConfigured(db._delegate),
name
).then(namedQuery => {
if (!namedQuery) {
return null;
}

return new Query(db, new ExpQuery(db._delegate, null, namedQuery.query));
});
}
54 changes: 53 additions & 1 deletion packages/firestore/src/exp/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ import {
FirestoreClient,
firestoreClientDisableNetwork,
firestoreClientEnableNetwork,
firestoreClientGetNamedQuery,
firestoreClientLoadBundle,
firestoreClientWaitForPendingWrites,
setOfflineComponentProvider,
setOnlineComponentProvider
} from '../core/firestore_client';
import { makeDatabaseInfo } from '../lite/components';
import { FirebaseFirestore as LiteFirestore } from '../lite/database';
import { Query } from '../lite/reference';
import {
indexedDbClearPersistence,
indexedDbStoragePrefix
Expand All @@ -51,8 +54,8 @@ import { Code, FirestoreError } from '../util/error';
import { cast } from '../util/input_validation';
import { Deferred } from '../util/promise';

import { LoadBundleTask } from './bundle';
import { PersistenceSettings, Settings } from './settings';

export { useFirestoreEmulator } from '../lite/database';

/** DOMException error code constants. */
Expand Down Expand Up @@ -460,6 +463,55 @@ export function terminate(firestore: FirebaseFirestore): Promise<void> {
return firestore._delete();
}

/**
* Loads a Firestore bundle into the local cache.
*
* @param firestore - The `Firestore` instance to load bundles for for.
* @param bundleData - An object representing the bundle to be loaded. Valid objects are
* `ArrayBuffer`, `ReadableStream<Uint8Array>` or `string`.
*
Copy link
Contributor

Choose a reason for hiding this comment

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

This documentation style (newline versus dash) seems a bit inconsistent.

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.

* @return
* A `LoadBundleTask` object, which notifies callers with progress updates, and completion
* or error events. It can be used as a `Promise<LoadBundleTaskProgress>`.
*/
export function loadBundle(
firestore: FirebaseFirestore,
bundleData: ReadableStream<Uint8Array> | ArrayBuffer | string
): LoadBundleTask {
firestore = cast(firestore, FirebaseFirestore);
const client = ensureFirestoreConfigured(firestore);
const resultTask = new LoadBundleTask();
firestoreClientLoadBundle(
client,
firestore._databaseId,
bundleData,
resultTask
);
return resultTask;
}

/**
* Reads a Firestore `Query` from local cache, identified by the given name.
*
* The named queries are packaged into bundles on the server side (along
* with resulting documents), and loaded to local cache using `loadBundle`. Once in local
* cache, use this method to extract a `Query` by name.
*/
export function namedQuery(
firestore: FirebaseFirestore,
name: string
): Promise<Query | null> {
firestore = cast(firestore, FirebaseFirestore);
const client = ensureFirestoreConfigured(firestore);
return firestoreClientGetNamedQuery(client, name).then(namedQuery => {
if (!namedQuery) {
return null;
}

return new Query(firestore, null, namedQuery.query);
});
}

function verifyNotInitialized(firestore: FirebaseFirestore): void {
if (firestore._initialized || firestore._terminated) {
throw new FirestoreError(
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/test/unit/specs/spec_test_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import { expect } from 'chai';

import { LoadBundleTask } from '../../../src/api/bundle';
import { EmptyCredentialsProvider } from '../../../src/api/credentials';
import { User } from '../../../src/auth/user';
import { ComponentConfiguration } from '../../../src/core/component_provider';
Expand Down Expand Up @@ -58,6 +57,7 @@ import {
ChangeType,
DocumentViewChange
} from '../../../src/core/view_snapshot';
import { LoadBundleTask } from '../../../src/exp/bundle';
import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
import {
DbPrimaryClient,
Expand Down