From 520f22adfe9440d78a5ef7305a6d093f8c6b3e3c Mon Sep 17 00:00:00 2001 From: MathBunny Date: Fri, 7 Aug 2020 16:20:25 -0400 Subject: [PATCH 1/3] Refactor to allow auto-generated types for firestore --- gulpfile.js | 1 - src/firestore/firestore-internal.ts | 61 +++++++++++++++++++++++++++ src/firestore/firestore.ts | 47 ++------------------- src/firestore/index.ts | 60 ++++++++++++++++++++++++++ test/unit/firestore/firestore.spec.ts | 3 +- 5 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 src/firestore/firestore-internal.ts create mode 100644 src/firestore/index.ts diff --git a/gulpfile.js b/gulpfile.js index ab23375bd5..013a9e3d3b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -67,7 +67,6 @@ const TEMPORARY_TYPING_EXCLUDES = [ '!lib/firebase-service.d.ts', '!lib/auth/*.d.ts', '!lib/database/*.d.ts', - '!lib/firestore/*.d.ts', '!lib/machine-learning/*.d.ts', '!lib/messaging/*.d.ts', '!lib/remote-config/*.d.ts', diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts new file mode 100644 index 0000000000..fbccc02256 --- /dev/null +++ b/src/firestore/firestore-internal.ts @@ -0,0 +1,61 @@ +/*! + * Copyright 2020 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 { FirebaseApp } from '../firebase-app'; +import { FirebaseFirestoreError } from '../utils/error'; +import { ServiceAccountCredential, isApplicationDefault } from '../auth/credential'; +import { Settings } from '@google-cloud/firestore'; + +import * as validator from '../utils/validator'; +import * as utils from '../utils/index'; + +export function getFirestoreOptions(app: FirebaseApp): Settings { + if (!validator.isNonNullObject(app) || !('options' in app)) { + throw new FirebaseFirestoreError({ + code: 'invalid-argument', + message: 'First argument passed to admin.firestore() must be a valid Firebase app instance.', + }); + } + + const projectId: string | null = utils.getExplicitProjectId(app); + const credential = app.options.credential; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { version: firebaseVersion } = require('../../package.json'); + if (credential instanceof ServiceAccountCredential) { + return { + credentials: { + private_key: credential.privateKey, // eslint-disable-line @typescript-eslint/camelcase + client_email: credential.clientEmail, // eslint-disable-line @typescript-eslint/camelcase + }, + // When the SDK is initialized with ServiceAccountCredentials an explicit projectId is + // guaranteed to be available. + projectId: projectId!, + firebaseVersion, + }; + } else if (isApplicationDefault(app.options.credential)) { + // Try to use the Google application default credentials. + // If an explicit project ID is not available, let Firestore client discover one from the + // environment. This prevents the users from having to set GOOGLE_CLOUD_PROJECT in GCP runtimes. + return validator.isNonEmptyString(projectId) ? { projectId, firebaseVersion } : { firebaseVersion }; + } + + throw new FirebaseFirestoreError({ + code: 'invalid-credential', + message: '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.', + }); +} diff --git a/src/firestore/firestore.ts b/src/firestore/firestore.ts index 28bf7e0272..ec104dd3af 100644 --- a/src/firestore/firestore.ts +++ b/src/firestore/firestore.ts @@ -15,13 +15,10 @@ */ import { FirebaseApp } from '../firebase-app'; -import { FirebaseFirestoreError } from '../utils/error'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; -import { ServiceAccountCredential, isApplicationDefault } from '../auth/credential'; -import { Firestore, Settings } from '@google-cloud/firestore'; - -import * as validator from '../utils/validator'; -import * as utils from '../utils/index'; +import { Firestore } from '@google-cloud/firestore'; +import { getFirestoreOptions } from './firestore-internal'; +import { FirebaseFirestoreError } from '../utils/error'; /** * Internals of a Firestore instance. @@ -63,44 +60,6 @@ export class FirestoreService implements FirebaseServiceInterface { } } -export function getFirestoreOptions(app: FirebaseApp): Settings { - if (!validator.isNonNullObject(app) || !('options' in app)) { - throw new FirebaseFirestoreError({ - code: 'invalid-argument', - message: 'First argument passed to admin.firestore() must be a valid Firebase app instance.', - }); - } - - const projectId: string | null = utils.getExplicitProjectId(app); - const credential = app.options.credential; - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { version: firebaseVersion } = require('../../package.json'); - if (credential instanceof ServiceAccountCredential) { - return { - credentials: { - private_key: credential.privateKey, // eslint-disable-line @typescript-eslint/camelcase - client_email: credential.clientEmail, // eslint-disable-line @typescript-eslint/camelcase - }, - // When the SDK is initialized with ServiceAccountCredentials an explicit projectId is - // guaranteed to be available. - projectId: projectId!, - firebaseVersion, - }; - } else if (isApplicationDefault(app.options.credential)) { - // Try to use the Google application default credentials. - // If an explicit project ID is not available, let Firestore client discover one from the - // environment. This prevents the users from having to set GOOGLE_CLOUD_PROJECT in GCP runtimes. - return validator.isNonEmptyString(projectId) ? { projectId, firebaseVersion } : { firebaseVersion }; - } - - throw new FirebaseFirestoreError({ - code: 'invalid-credential', - message: '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.', - }); -} - function initFirestore(app: FirebaseApp): Firestore { const options = getFirestoreOptions(app); let firestoreDatabase: typeof Firestore; diff --git a/src/firestore/index.ts b/src/firestore/index.ts new file mode 100644 index 0000000000..d32563dabc --- /dev/null +++ b/src/firestore/index.ts @@ -0,0 +1,60 @@ +/*! + * Copyright 2020 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 { FirebaseApp } from '../firebase-app'; +import * as _firestore from '@google-cloud/firestore'; +import * as firebaseAdmin from '../index'; + +export function projectManagement(app?: FirebaseApp): _firestore.Firestore { + if (typeof (app) === 'undefined') { + app = firebaseAdmin.app(); + } + return app.firestore(); +} + +/** + * We must define a namespace to make the typings work correctly. Otherwise + * `admin.firestore()` cannot be called like a function. Temporarily, + * admin.firestore is used as the namespace name because we cannot barrel + * re-export the contents from firestore, and we want it to + * match the namespacing in the re-export inside src/index.d.ts + */ +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace admin.firestore { + // See https://github.com/microsoft/TypeScript/issues/4336 + /* eslint-disable @typescript-eslint/no-unused-vars */ + // See https://github.com/typescript-eslint/typescript-eslint/issues/363 + export import v1beta1 = _firestore.v1beta1; + export import v1 = _firestore.v1; + + export import CollectionReference = _firestore.CollectionReference; + export import DocumentData = _firestore.DocumentData; + export import DocumentReference = _firestore.DocumentReference; + export import DocumentSnapshot = _firestore.DocumentSnapshot; + export import FieldPath = _firestore.FieldPath; + export import FieldValue = _firestore.FieldValue; + export import Firestore = _firestore.Firestore; + export import GeoPoint = _firestore.GeoPoint; + export import Query = _firestore.Query; + export import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; + export import QuerySnapshot = _firestore.QuerySnapshot; + export import Timestamp = _firestore.Timestamp; + export import Transaction = _firestore.Transaction; + export import WriteBatch = _firestore.WriteBatch; + export import WriteResult = _firestore.WriteResult; + + export import setLogFunction = _firestore.setLogFunction; +} diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 236050dfbe..1cd2d0a612 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -22,7 +22,8 @@ import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/firebase-app'; import { ComputeEngineCredential, RefreshTokenCredential } from '../../../src/auth/credential'; -import { FirestoreService, getFirestoreOptions } from '../../../src/firestore/firestore'; +import { FirestoreService } from '../../../src/firestore/firestore'; +import { getFirestoreOptions } from '../../../src/firestore/firestore-internal'; describe('Firestore', () => { let mockApp: FirebaseApp; From 8adb5173d49d94091b3027cbbe1a8d237cd98246 Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 11 Aug 2020 11:43:09 -0400 Subject: [PATCH 2/3] Remove firestore.ts entirely --- src/firebase-app.ts | 4 +- src/firestore/firestore-internal.ts | 61 ++++++++++++++++++++- src/firestore/firestore.ts | 79 --------------------------- src/firestore/index.ts | 2 +- test/unit/firestore/firestore.spec.ts | 3 +- 5 files changed, 64 insertions(+), 85 deletions(-) delete mode 100644 src/firestore/firestore.ts diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 4baf2de08d..194b792ce4 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -28,7 +28,7 @@ import { Storage } from './storage/storage'; import { Database } from '@firebase/database'; import { DatabaseService } from './database/database'; import { Firestore } from '@google-cloud/firestore'; -import { FirestoreService } from './firestore/firestore'; +import { FirestoreService } from './firestore/firestore-internal'; import { InstanceId } from './instance-id/instance-id'; import { ProjectManagement } from './project-management/project-management'; @@ -339,7 +339,7 @@ export class FirebaseApp { public firestore(): Firestore { const service: FirestoreService = this.ensureService_('firestore', () => { - const firestoreService: typeof FirestoreService = require('./firestore/firestore').FirestoreService; + const firestoreService: typeof FirestoreService = require('./firestore/firestore-internal').FirestoreService; return new firestoreService(this); }); return service.client; diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index fbccc02256..9e0a3a101e 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -17,7 +17,8 @@ import { FirebaseApp } from '../firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../auth/credential'; -import { Settings } from '@google-cloud/firestore'; +import { Settings, Firestore } from '@google-cloud/firestore'; +import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; @@ -59,3 +60,61 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { 'to use Cloud Firestore API.', }); } + +/** + * Internals of a Firestore instance. + */ +class FirestoreInternals implements FirebaseServiceInternalsInterface { + /** + * Deletes the service and its associated resources. + * + * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. + */ + public delete(): Promise { + // There are no resources to clean up. + return Promise.resolve(); + } +} + +export class FirestoreService implements FirebaseServiceInterface { + public INTERNAL: FirestoreInternals = new FirestoreInternals(); + + private appInternal: FirebaseApp; + private firestoreClient: Firestore; + + constructor(app: FirebaseApp) { + this.firestoreClient = initFirestore(app); + this.appInternal = app; + } + + /** + * Returns the app associated with this Storage instance. + * + * @return {FirebaseApp} The app associated with this Storage instance. + */ + get app(): FirebaseApp { + return this.appInternal; + } + + get client(): Firestore { + return this.firestoreClient; + } +} + +function initFirestore(app: FirebaseApp): Firestore { + const options = getFirestoreOptions(app); + let firestoreDatabase: typeof Firestore; + try { + // Lazy-load the Firestore implementation here, which in turns loads gRPC. + firestoreDatabase = require('@google-cloud/firestore').Firestore; + } catch (err) { + throw new FirebaseFirestoreError({ + code: 'missing-dependencies', + message: 'Failed to import the Cloud Firestore client library for Node.js. ' + + 'Make sure to install the "@google-cloud/firestore" npm package. ' + + `Original error: ${err}`, + }); + } + + return new firestoreDatabase(options); +} diff --git a/src/firestore/firestore.ts b/src/firestore/firestore.ts deleted file mode 100644 index ec104dd3af..0000000000 --- a/src/firestore/firestore.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * 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 { FirebaseApp } from '../firebase-app'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; -import { Firestore } from '@google-cloud/firestore'; -import { getFirestoreOptions } from './firestore-internal'; -import { FirebaseFirestoreError } from '../utils/error'; - -/** - * Internals of a Firestore instance. - */ -class FirestoreInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(); - } -} - -export class FirestoreService implements FirebaseServiceInterface { - public INTERNAL: FirestoreInternals = new FirestoreInternals(); - - private appInternal: FirebaseApp; - private firestoreClient: Firestore; - - constructor(app: FirebaseApp) { - this.firestoreClient = initFirestore(app); - this.appInternal = app; - } - - /** - * Returns the app associated with this Storage instance. - * - * @return {FirebaseApp} The app associated with this Storage instance. - */ - get app(): FirebaseApp { - return this.appInternal; - } - - get client(): Firestore { - return this.firestoreClient; - } -} - -function initFirestore(app: FirebaseApp): Firestore { - const options = getFirestoreOptions(app); - let firestoreDatabase: typeof Firestore; - try { - // Lazy-load the Firestore implementation here, which in turns loads gRPC. - firestoreDatabase = require('@google-cloud/firestore').Firestore; - } catch (err) { - throw new FirebaseFirestoreError({ - code: 'missing-dependencies', - message: 'Failed to import the Cloud Firestore client library for Node.js. ' - + 'Make sure to install the "@google-cloud/firestore" npm package. ' - + `Original error: ${err}`, - }); - } - - return new firestoreDatabase(options); -} diff --git a/src/firestore/index.ts b/src/firestore/index.ts index d32563dabc..fb48b137b8 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -18,7 +18,7 @@ import { FirebaseApp } from '../firebase-app'; import * as _firestore from '@google-cloud/firestore'; import * as firebaseAdmin from '../index'; -export function projectManagement(app?: FirebaseApp): _firestore.Firestore { +export function firestore(app?: FirebaseApp): _firestore.Firestore { if (typeof (app) === 'undefined') { app = firebaseAdmin.app(); } diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 1cd2d0a612..0935f58821 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -22,8 +22,7 @@ import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/firebase-app'; import { ComputeEngineCredential, RefreshTokenCredential } from '../../../src/auth/credential'; -import { FirestoreService } from '../../../src/firestore/firestore'; -import { getFirestoreOptions } from '../../../src/firestore/firestore-internal'; +import { FirestoreService, getFirestoreOptions } from '../../../src/firestore/firestore-internal'; describe('Firestore', () => { let mockApp: FirebaseApp; From 3abd24281a78482a90055f5ae52b7edbae7e52cc Mon Sep 17 00:00:00 2001 From: MathBunny Date: Tue, 11 Aug 2020 13:58:29 -0400 Subject: [PATCH 3/3] Restore firestore-internal.ts --- src/firestore/firestore-internal.ts | 82 ++++++++++++++--------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index 9e0a3a101e..28bf7e0272 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2020 Google Inc. + * 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. @@ -16,51 +16,13 @@ import { FirebaseApp } from '../firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; -import { ServiceAccountCredential, isApplicationDefault } from '../auth/credential'; -import { Settings, Firestore } from '@google-cloud/firestore'; import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; +import { ServiceAccountCredential, isApplicationDefault } from '../auth/credential'; +import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; -export function getFirestoreOptions(app: FirebaseApp): Settings { - if (!validator.isNonNullObject(app) || !('options' in app)) { - throw new FirebaseFirestoreError({ - code: 'invalid-argument', - message: 'First argument passed to admin.firestore() must be a valid Firebase app instance.', - }); - } - - const projectId: string | null = utils.getExplicitProjectId(app); - const credential = app.options.credential; - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { version: firebaseVersion } = require('../../package.json'); - if (credential instanceof ServiceAccountCredential) { - return { - credentials: { - private_key: credential.privateKey, // eslint-disable-line @typescript-eslint/camelcase - client_email: credential.clientEmail, // eslint-disable-line @typescript-eslint/camelcase - }, - // When the SDK is initialized with ServiceAccountCredentials an explicit projectId is - // guaranteed to be available. - projectId: projectId!, - firebaseVersion, - }; - } else if (isApplicationDefault(app.options.credential)) { - // Try to use the Google application default credentials. - // If an explicit project ID is not available, let Firestore client discover one from the - // environment. This prevents the users from having to set GOOGLE_CLOUD_PROJECT in GCP runtimes. - return validator.isNonEmptyString(projectId) ? { projectId, firebaseVersion } : { firebaseVersion }; - } - - throw new FirebaseFirestoreError({ - code: 'invalid-credential', - message: '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.', - }); -} - /** * Internals of a Firestore instance. */ @@ -101,6 +63,44 @@ export class FirestoreService implements FirebaseServiceInterface { } } +export function getFirestoreOptions(app: FirebaseApp): Settings { + if (!validator.isNonNullObject(app) || !('options' in app)) { + throw new FirebaseFirestoreError({ + code: 'invalid-argument', + message: 'First argument passed to admin.firestore() must be a valid Firebase app instance.', + }); + } + + const projectId: string | null = utils.getExplicitProjectId(app); + const credential = app.options.credential; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { version: firebaseVersion } = require('../../package.json'); + if (credential instanceof ServiceAccountCredential) { + return { + credentials: { + private_key: credential.privateKey, // eslint-disable-line @typescript-eslint/camelcase + client_email: credential.clientEmail, // eslint-disable-line @typescript-eslint/camelcase + }, + // When the SDK is initialized with ServiceAccountCredentials an explicit projectId is + // guaranteed to be available. + projectId: projectId!, + firebaseVersion, + }; + } else if (isApplicationDefault(app.options.credential)) { + // Try to use the Google application default credentials. + // If an explicit project ID is not available, let Firestore client discover one from the + // environment. This prevents the users from having to set GOOGLE_CLOUD_PROJECT in GCP runtimes. + return validator.isNonEmptyString(projectId) ? { projectId, firebaseVersion } : { firebaseVersion }; + } + + throw new FirebaseFirestoreError({ + code: 'invalid-credential', + message: '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.', + }); +} + function initFirestore(app: FirebaseApp): Firestore { const options = getFirestoreOptions(app); let firestoreDatabase: typeof Firestore;