From 84714daad843df2aa7b1da13acc828b2b6754893 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 29 Jan 2021 12:54:17 -0800 Subject: [PATCH] feat(firestore): Exposed Firestore APIs from firebase-admin/firestore entry point --- etc/firebase-admin.api.md | 2 +- etc/firebase-admin.firestore.api.md | 138 ++++++++++++++++++++++++++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 8 +- src/firestore/firestore-internal.ts | 14 +-- src/firestore/index.ts | 48 +++++++++- test/unit/firestore/index.spec.ts | 73 +++++++++++++++ test/unit/index.spec.ts | 1 + 9 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 etc/firebase-admin.firestore.api.md create mode 100644 test/unit/firestore/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index db1e843ede..f67bf14aee 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -334,7 +334,7 @@ export interface FirebaseError { } // @public (undocumented) -export function firestore(app?: app.App): _firestore.Firestore; +export function firestore(app?: App): _firestore.Firestore; // @public (undocumented) export namespace firestore { diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md new file mode 100644 index 0000000000..e060e6d4c2 --- /dev/null +++ b/etc/firebase-admin.firestore.api.md @@ -0,0 +1,138 @@ +## API Report File for "firebase-admin.firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; +import { BulkWriter } from '@google-cloud/firestore'; +import { BulkWriterOptions } from '@google-cloud/firestore'; +import { CollectionGroup } from '@google-cloud/firestore'; +import { CollectionReference } from '@google-cloud/firestore'; +import { DocumentChangeType } from '@google-cloud/firestore'; +import { DocumentData } from '@google-cloud/firestore'; +import { DocumentReference } from '@google-cloud/firestore'; +import { DocumentSnapshot } from '@google-cloud/firestore'; +import { FieldPath } from '@google-cloud/firestore'; +import { FieldValue } from '@google-cloud/firestore'; +import { Firestore } from '@google-cloud/firestore'; +import * as _firestore from '@google-cloud/firestore'; +import { FirestoreDataConverter } from '@google-cloud/firestore'; +import { GeoPoint } from '@google-cloud/firestore'; +import { GrpcStatus } from '@google-cloud/firestore'; +import { Precondition } from '@google-cloud/firestore'; +import { Query } from '@google-cloud/firestore'; +import { QueryDocumentSnapshot } from '@google-cloud/firestore'; +import { QueryPartition } from '@google-cloud/firestore'; +import { QuerySnapshot } from '@google-cloud/firestore'; +import { ReadOptions } from '@google-cloud/firestore'; +import { setLogFunction } from '@google-cloud/firestore'; +import { Settings } from '@google-cloud/firestore'; +import { Timestamp } from '@google-cloud/firestore'; +import { Transaction } from '@google-cloud/firestore'; +import { UpdateData } from '@google-cloud/firestore'; +import { v1 } from '@google-cloud/firestore'; +import { WriteBatch } from '@google-cloud/firestore'; +import { WriteResult } from '@google-cloud/firestore'; + +export { BulkWriter } + +export { BulkWriterOptions } + +export { CollectionGroup } + +export { CollectionReference } + +export { DocumentChangeType } + +export { DocumentData } + +export { DocumentReference } + +export { DocumentSnapshot } + +export { FieldPath } + +export { FieldValue } + +export { Firestore } + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function firestore(app?: App): _firestore.Firestore; + +// @public (undocumented) +export namespace firestore { + import v1beta1 = _firestore.v1beta1; + import v1 = _firestore.v1; + import BulkWriter = _firestore.BulkWriter; + import BulkWriterOptions = _firestore.BulkWriterOptions; + import CollectionGroup = _firestore.CollectionGroup; + import CollectionReference = _firestore.CollectionReference; + import DocumentChangeType = _firestore.DocumentChangeType; + import DocumentData = _firestore.DocumentData; + import DocumentReference = _firestore.DocumentReference; + import DocumentSnapshot = _firestore.DocumentSnapshot; + import FieldPath = _firestore.FieldPath; + import FieldValue = _firestore.FieldValue; + import Firestore = _firestore.Firestore; + import FirestoreDataConverter = _firestore.FirestoreDataConverter; + import GeoPoint = _firestore.GeoPoint; + import GrpcStatus = _firestore.GrpcStatus; + import Precondition = _firestore.Precondition; + import Query = _firestore.Query; + import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; + import QueryPartition = _firestore.QueryPartition; + import QuerySnapshot = _firestore.QuerySnapshot; + import ReadOptions = _firestore.ReadOptions; + import Settings = _firestore.Settings; + import Timestamp = _firestore.Timestamp; + import Transaction = _firestore.Transaction; + import UpdateData = _firestore.UpdateData; + import WriteBatch = _firestore.WriteBatch; + import WriteResult = _firestore.WriteResult; + import setLogFunction = _firestore.setLogFunction; +} + +export { FirestoreDataConverter } + +export { GeoPoint } + +// @public (undocumented) +export function getFirestore(app?: App): _firestore.Firestore; + +export { GrpcStatus } + +export { Precondition } + +export { Query } + +export { QueryDocumentSnapshot } + +export { QueryPartition } + +export { QuerySnapshot } + +export { ReadOptions } + +export { setLogFunction } + +export { Settings } + +export { Timestamp } + +export { Transaction } + +export { UpdateData } + +export { v1 } + +export { WriteBatch } + +export { WriteResult } + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index b5325f1e9e..7a0c95bdc1 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -37,6 +37,7 @@ const entryPoints = { 'firebase-admin/app': './lib/app/index.d.ts', 'firebase-admin/auth': './lib/auth/index.d.ts', 'firebase-admin/database': './lib/database/index.d.ts', + 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index 7d3580d7cf..b759ed89f9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -90,6 +90,7 @@ gulp.task('compile', function() { 'lib/app/*.d.ts', 'lib/auth/*.d.ts', 'lib/database/*.d.ts', + 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index c1b748e132..40a3415b4a 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -29,7 +29,6 @@ import { Messaging } from '../messaging/messaging'; import { Storage } from '../storage/storage'; import { Database } from '../database/index'; import { Firestore } from '@google-cloud/firestore'; -import { FirestoreService } from '../firestore/firestore-internal'; import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; @@ -313,11 +312,8 @@ export class FirebaseApp implements app.App { } public firestore(): Firestore { - const service: FirestoreService = this.ensureService_('firestore', () => { - const firestoreService: typeof FirestoreService = require('../firestore/firestore-internal').FirestoreService; - return new firestoreService(this); - }); - return service.client; + const fn = require('../firestore/index').getFirestore; + return fn(this); } /** diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index ef83053ce4..7c4df98242 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -15,20 +15,20 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; +import { App } from '../app'; export class FirestoreService { - private appInternal: FirebaseApp; + private appInternal: App; private firestoreClient: Firestore; - constructor(app: FirebaseApp) { + constructor(app: App) { this.firestoreClient = initFirestore(app); this.appInternal = app; } @@ -36,9 +36,9 @@ export class FirestoreService { /** * Returns the app associated with this Storage instance. * - * @return {FirebaseApp} The app associated with this Storage instance. + * @return The app associated with this Storage instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -47,7 +47,7 @@ export class FirestoreService { } } -export function getFirestoreOptions(app: FirebaseApp): Settings { +export function getFirestoreOptions(app: App): Settings { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseFirestoreError({ code: 'invalid-argument', @@ -85,7 +85,7 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { }); } -function initFirestore(app: FirebaseApp): Firestore { +function initFirestore(app: App): Firestore { const options = getFirestoreOptions(app); let firestoreDatabase: typeof Firestore; try { diff --git a/src/firestore/index.ts b/src/firestore/index.ts index 4efe99e053..ebfb3243dc 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -14,10 +14,54 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; import * as _firestore from '@google-cloud/firestore'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { FirestoreService } from './firestore-internal'; -export declare function firestore(app?: app.App): _firestore.Firestore; +export { + BulkWriter, + BulkWriterOptions, + CollectionGroup, + CollectionReference, + DocumentChangeType, + DocumentData, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Firestore, + FirestoreDataConverter, + GeoPoint, + GrpcStatus, + Precondition, + Query, + QueryDocumentSnapshot, + QueryPartition, + QuerySnapshot, + ReadOptions, + Settings, + Timestamp, + Transaction, + UpdateData, + WriteBatch, + WriteResult, + v1, + setLogFunction, +} from '@google-cloud/firestore'; + +export function getFirestore(app?: App): _firestore.Firestore { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + const firestoreService = firebaseApp.getOrInitService( + 'firestore', (app) => new FirestoreService(app)); + return firestoreService.client; +} + +export declare function firestore(app?: App): _firestore.Firestore; /* eslint-disable @typescript-eslint/no-namespace */ export namespace firestore { diff --git a/test/unit/firestore/index.spec.ts b/test/unit/firestore/index.spec.ts new file mode 100644 index 0000000000..5cfd800507 --- /dev/null +++ b/test/unit/firestore/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getFirestore, Firestore } from '../../../src/firestore/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Firestore', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Firestore client with the ' + + 'available credentials. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Firestore API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getFirestore()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getFirestore(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getFirestore(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getFirestore(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Firestore = getFirestore(mockApp); + const db2: Firestore = getFirestore(mockApp); + expect(db1).to.equal(db2); + }); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 256c2112aa..f1b5a0d647 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -58,6 +58,7 @@ import './storage/storage.spec'; // Firestore import './firestore/firestore.spec'; +import './firestore/index.spec'; // InstanceId import './instance-id/index.spec';