[go: nahoru, domu]

Skip to content

Commit

Permalink
Firestore API (firebase#94)
Browse files Browse the repository at this point in the history
* Prototyped Firestore integration

* Added integration tests

* Fixing a test case

* Exposing Firestore client directly from admin.firestore()

* Merged with master; Updated tests

* Updated documentation and fixed some typos; Introduced FirebaseFirestoreError type; Fixed index.d.ts by exporting Firestore as any; Added unit tests for utils.getProjectId()

* Fixing an incorrect test description

* Using Firestore type definitions

* Using auto IDs in integration test

* Some minor changes that enables building RCs from this branch. Some of these changes can be removed once the Firestore is officially released.

* Supporting Firestore initialization with application default credentials, without a project ID.

* Fixing up some nits

* Exposing Firestore Types from admin.firestore Namespace (#5)

* Upgraded to latest Firestore; Exposing FieldValue and few others via admin.firestore

* Fixing Admin SDK type definitions for Firestore

* Exporting all Firestore types via admin.firestore

* Using lodash.assign() which only copies 'own' members and none of the inheritted members.

* Declaring Firestore type in terms of admin.firestore.Firestore; More integration tests for admin.firestore namespace

* Making GCS types a required dependency

* Updated the Firestore dependency

* Fixing a Node5 test failure

* Updated shrinkwrap

* Fixing NPM shrinkwrap/dependency issues
  • Loading branch information
hiranya911 committed Oct 3, 2017
1 parent 05078a9 commit 72ab108
Show file tree
Hide file tree
Showing 21 changed files with 5,983 additions and 49 deletions.
5,459 changes: 5,420 additions & 39 deletions npm-shrinkwrap.json

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@
],
"types": "./lib/index.d.ts",
"dependencies": {
"google-auth-library": "^0.10.0",
"@google-cloud/storage": "^1.2.1",
"@google-cloud/firestore": "^0.8.0",
"@types/google-cloud__storage": "^1.1.1",
"@types/jsonwebtoken": "^7.1.33",
"faye-websocket": "0.9.3",
"jsonwebtoken": "7.1.9",
"node-forge": "0.7.1"
"node-forge": "0.7.1",
"lodash": "^4.6.1"
},
"devDependencies": {
"@types/chai": "^3.4.34",
Expand All @@ -61,6 +65,7 @@
"@types/lodash": "^4.14.38",
"@types/mocha": "^2.2.32",
"@types/nock": "^8.0.33",
"@types/node": "^8.0.32",
"@types/request": "0.0.32",
"@types/request-promise": "^4.1.33",
"@types/sinon": "^1.16.31",
Expand All @@ -71,7 +76,6 @@
"del": "^2.2.1",
"firebase": "^3.6.9",
"firebase-token-generator": "^2.0.0",
"@types/google-cloud__storage": "^1.1.1",
"gulp": "^3.9.1",
"gulp-exit": "0.0.2",
"gulp-header": "^1.8.8",
Expand All @@ -80,7 +84,6 @@
"gulp-replace": "^0.5.4",
"gulp-tslint": "^6.0.2",
"gulp-typescript": "^3.1.2",
"lodash": "^4.6.1",
"merge2": "^1.0.2",
"mocha": "^3.5.0",
"nock": "^8.0.0",
Expand All @@ -91,8 +94,8 @@
"run-sequence": "^1.1.5",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"tslint": "^3.5.0",
"ts-node": "^3.3.0",
"tslint": "^3.5.0",
"typescript": "^2.0.3",
"typings": "^1.0.4"
}
Expand Down
10 changes: 10 additions & 0 deletions src/firebase-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {GoogleOAuthAccessToken} from './auth/credential';
import {FirebaseServiceInterface} from './firebase-service';
import {FirebaseNamespaceInternals} from './firebase-namespace';
import {AppErrorCodes, FirebaseAppError} from './utils/error';
import {Firestore} from '@google-cloud/firestore';


/**
Expand All @@ -37,6 +38,7 @@ export type FirebaseAppOptions = {
databaseAuthVariableOverride?: Object
databaseURL?: string,
storageBucket?: string,
projectId?: string,
};

/**
Expand Down Expand Up @@ -310,6 +312,14 @@ export class FirebaseApp {
);
}

/* istanbul ignore next */
public firestore(): Firestore {
throw new FirebaseAppError(
AppErrorCodes.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Firebase firestore() service has not been registered.',
);
}

/**
* Returns the name of the FirebaseApp instance.
*
Expand Down
9 changes: 9 additions & 0 deletions src/firebase-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RefreshTokenCredential,
ApplicationDefaultCredential,
} from './auth/credential';
import {Firestore} from '@google-cloud/firestore';

const DEFAULT_APP_NAME = '[DEFAULT]';

Expand Down Expand Up @@ -308,6 +309,14 @@ export class FirebaseNamespace {
);
}

/* istanbul ignore next */
public firestore(): Firestore {
throw new FirebaseAppError(
AppErrorCodes.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Firebase firestore() service has not been registered.',
);
}

/**
* Initializes the FirebaseApp instance.
*
Expand Down
105 changes: 105 additions & 0 deletions src/firestore/firestore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*!
* 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 {FirebaseFirestoreError} from '../utils/error';
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
import {ApplicationDefaultCredential, Certificate} from '../auth/credential';
import {Firestore} from '@google-cloud/firestore';

import * as validator from '../utils/validator';
import * as utils from '../utils/index';

/**
* 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<void> {
// There are no resources to clean up.
return Promise.resolve();
}
}

function initFirestore(app: FirebaseApp): Firestore {
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 = utils.getProjectId(app);
const cert: Certificate = app.options.credential.getCertificate();
let options: any;
if (cert != null) {
// cert is available when the SDK has been initialized with a service account JSON file,
// or by setting the GOOGLE_APPLICATION_CREDENTIALS envrionment variable.

if (!validator.isNonEmptyString(projectId)) {
// Assert for an explicit projct ID (either via AppOptions or the cert itself).
throw new FirebaseFirestoreError({
code: 'no-project-id',
message: 'Failed to determine project ID for Firestore. Initialize the '
+ 'SDK with service account credentials or set project ID as an app option. '
+ 'Alternatively set the GCLOUD_PROJECT environment variable.',
});
}
options = {
credentials: {
private_key: cert.privateKey,
client_email: cert.clientEmail,
},
projectId,
};
} else if (app.options.credential instanceof ApplicationDefaultCredential) {
// Try to use the Google application default credentials.
if (validator.isNonEmptyString(projectId)) {
options = {projectId};
} else {
// If an explicit project ID is not available, let Firestore client discover one from the
// environment. This prevents the users from having to set GCLOUD_PROJECT in GCP runtimes.
options = {};
}
} else {
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.',
});
}
return new Firestore(options);
}

/**
* Creates a new Firestore service instance for the given FirebaseApp.
*
* @param {FirebaseApp} app The App for this Firestore service.
* @return {FirebaseServiceInterface} A Firestore service instance.
*/
export function initFirestoreService(app: FirebaseApp): FirebaseServiceInterface {
let firestore: any = initFirestore(app);

// Extend the Firestore client object so it implements FirebaseServiceInterface.
utils.addReadonlyGetter(firestore, 'app', app);
firestore.INTERNAL = new FirestoreInternals();
return firestore;
}
38 changes: 38 additions & 0 deletions src/firestore/register-firestore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {initFirestoreService} from './firestore';
import {AppHook, FirebaseApp} from '../firebase-app';
import {FirebaseServiceInterface} from '../firebase-service';
import * as firebase from '../default-namespace';
import {FirebaseServiceNamespace} from '../firebase-namespace';
import * as firestoreNamespace from '@google-cloud/firestore';
import * as _ from 'lodash/object';

/**
* Factory function that creates a new Firestore service.
*
* @param {Object} app The app for this service.
* @param {function(Object)} extendApp An extend function to extend the app namespace.
*
* @return {Firestore} The Firestore service for the specified app.
*/
function serviceFactory(app: FirebaseApp, extendApp: (props: Object) => void): FirebaseServiceInterface {
return initFirestoreService(app);
}

/**
* Handles app life-cycle events.
*
* @param {string} event The app event that is occurring.
* @param {FirebaseApp} app The app for which the app hook is firing.
*/
let appHook: AppHook = (event: string, app: FirebaseApp) => {
return;
};

export default function(): FirebaseServiceNamespace<FirebaseServiceInterface> {
return firebase.INTERNAL.registerService(
'firestore',
serviceFactory,
_.assign({}, firestoreNamespace),
appHook
);
}
10 changes: 10 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import {Bucket} from '@google-cloud/storage';
import * as _firestore from '@google-cloud/firestore';

declare namespace admin {
interface FirebaseError {
Expand Down Expand Up @@ -56,6 +57,7 @@ declare namespace admin {
function database(app?: admin.app.App): admin.database.Database;
function messaging(app?: admin.app.App): admin.messaging.Messaging;
function storage(app?: admin.app.App): admin.storage.Storage;
function firestore(app?: admin.app.App): admin.firestore.Firestore;
function initializeApp(options: admin.AppOptions, name?: string): admin.app.App;
}

Expand All @@ -66,6 +68,7 @@ declare namespace admin.app {

auth(): admin.auth.Auth;
database(): admin.database.Database;
firestore(): admin.firestore.Firestore;
messaging(): admin.messaging.Messaging;
storage(): admin.storage.Storage;
delete(): Promise<void>;
Expand Down Expand Up @@ -402,6 +405,13 @@ declare namespace admin.storage {
}
}

declare namespace admin.firestore {
export import FieldPath = _firestore.FieldPath;
export import FieldValue = _firestore.FieldValue;
export import Firestore = _firestore.Firestore;
export import GeoPoint = _firestore.GeoPoint;
}

declare module 'firebase-admin' {
}

Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as firebase from './default-namespace';
import registerAuth from './auth/register-auth';
import registerMessaging from './messaging/register-messaging';
import registerStorage from './storage/register-storage';
import registerFirestore from './firestore/register-firestore';

// Register the Database service
// For historical reasons, the database code is included as minified code and registers itself
Expand All @@ -35,4 +36,7 @@ registerMessaging();
// Register the Storage service
registerStorage();

// Register the Firestore service
registerFirestore();

export = firebase;
14 changes: 11 additions & 3 deletions src/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {FirebaseApp} from '../firebase-app';
import {FirebaseError} from '../utils/error';
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
import {ApplicationDefaultCredential} from '../auth/credential';
import {ApplicationDefaultCredential, Certificate} from '../auth/credential';
import {Bucket} from '@google-cloud/storage';

import * as validator from '../utils/validator';
Expand Down Expand Up @@ -47,7 +47,7 @@ export class Storage implements FirebaseServiceInterface {
private storageClient: any;

/**
* @param {Object} app The app for this Storage service.
* @param {FirebaseApp} app The app for this Storage service.
* @constructor
*/
constructor(app: FirebaseApp) {
Expand All @@ -70,7 +70,7 @@ export class Storage implements FirebaseServiceInterface {
});
}

const cert = app.options.credential.getCertificate();
const cert: Certificate = app.options.credential.getCertificate();
if (cert != null) {
// cert is available when the SDK has been initialized with a service account JSON file,
// or by setting the GOOGLE_APPLICATION_CREDENTIALS envrionment variable.
Expand All @@ -94,6 +94,14 @@ export class Storage implements FirebaseServiceInterface {
this.appInternal = app;
}

/**
* Returns a reference to a Google Cloud Storage bucket. Returned reference can be used to upload
* and download content from Google Cloud Storage.
*
* @param {string=} name Optional name of the bucket to be retrieved. If name is not specified,
* retrieves a reference to the default bucket.
* @return {Bucket} A Bucket object from the @google-cloud/storage library.
*/
public bucket(name?: string): Bucket {
let bucketName;
if (typeof name !== 'undefined') {
Expand Down
15 changes: 15 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ export class FirebaseAuthError extends PrefixedFirebaseError {
}
}

/**
* Firebase Firestore error code structure. This extends FirebaseError.
*
* @param {ErrorInfo} info The error code info.
* @param {string} [message] The error message. This will override the default
* message if provided.
* @constructor
*/
export class FirebaseFirestoreError extends FirebaseError {
constructor(info: ErrorInfo, message?: string) {
// Override default message if custom message provided.
super({code: 'firestore/' + info.code, message: message || info.message});
}
}


/**
* Firebase Messaging error code structure. This extends PrefixedFirebaseError.
Expand Down
Loading

0 comments on commit 72ab108

Please sign in to comment.