Skip to content

Commit a8db16e

Browse files
authored
Importing RTDB code via @firebase/database (#112)
* Importing RTDB code via @firebase/database * Further tests and code cleanup * More tests and code clean up * Upgraded to @firebase/database 0.1.3 * Fixing initStandalone import * Code cleanup * Minor improvements to test * Updated type definition for multi DB support
1 parent f45ab51 commit a8db16e

16 files changed

+481
-386
lines changed

package-lock.json

+32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
],
4848
"types": "./lib/index.d.ts",
4949
"dependencies": {
50+
"@firebase/database": "^0.1.3",
51+
"@firebase/app": "^0.1.1",
5052
"google-auth-library": "^0.10.0",
5153
"@google-cloud/storage": "^1.2.1",
5254
"@google-cloud/firestore": "~0.8.2",

src/database/database.js

-248
This file was deleted.

src/database/database.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {FirebaseApp} from '../firebase-app';
2+
import {FirebaseDatabaseError} from '../utils/error';
3+
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
4+
import {Database} from '@firebase/database';
5+
6+
import * as validator from '../utils/validator';
7+
8+
9+
/**
10+
* Internals of a Database instance.
11+
*/
12+
class DatabaseInternals implements FirebaseServiceInternalsInterface {
13+
14+
public databases: {
15+
[dbUrl: string]: Database
16+
} = {};
17+
18+
/**
19+
* Deletes the service and its associated resources.
20+
*
21+
* @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted.
22+
*/
23+
public delete(): Promise<void> {
24+
for (const dbUrl of Object.keys(this.databases)) {
25+
let db: Database = this.databases[dbUrl];
26+
db.INTERNAL.delete();
27+
}
28+
return Promise.resolve(undefined);
29+
}
30+
}
31+
32+
export class DatabaseService implements FirebaseServiceInterface {
33+
34+
public INTERNAL: DatabaseInternals = new DatabaseInternals();
35+
36+
private appInternal: FirebaseApp;
37+
38+
constructor(app: FirebaseApp) {
39+
if (!validator.isNonNullObject(app) || !('options' in app)) {
40+
throw new FirebaseDatabaseError({
41+
code: 'invalid-argument',
42+
message: 'First argument passed to admin.database() must be a valid Firebase app instance.',
43+
});
44+
}
45+
this.appInternal = app;
46+
}
47+
48+
/**
49+
* Returns the app associated with this DatabaseService instance.
50+
*
51+
* @return {FirebaseApp} The app associated with this DatabaseService instance.
52+
*/
53+
get app(): FirebaseApp {
54+
return this.appInternal;
55+
}
56+
57+
public getDatabase(url?: string): Database {
58+
let dbUrl: string = this.ensureUrl(url);
59+
if (!validator.isNonEmptyString(dbUrl)) {
60+
throw new FirebaseDatabaseError({
61+
code: 'invalid-argument',
62+
message: 'Database URL must be a valid, non-empty URL string.',
63+
});
64+
}
65+
66+
let db: Database = this.INTERNAL.databases[dbUrl];
67+
if (typeof db === 'undefined') {
68+
let rtdb = require('@firebase/database');
69+
db = rtdb.initStandalone(this.appInternal, dbUrl).instance;
70+
this.INTERNAL.databases[dbUrl] = db;
71+
}
72+
return db;
73+
}
74+
75+
private ensureUrl(url?: string): string {
76+
if (typeof url !== 'undefined') {
77+
return url;
78+
} else if (typeof this.appInternal.options.databaseURL !== 'undefined') {
79+
return this.appInternal.options.databaseURL;
80+
}
81+
throw new FirebaseDatabaseError({
82+
code: 'invalid-argument',
83+
message: 'Can\'t determine Firebase Database URL.',
84+
});
85+
}
86+
}

src/firebase-app.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import {GoogleOAuthAccessToken} from './auth/credential';
2121
import {FirebaseServiceInterface} from './firebase-service';
2222
import {FirebaseNamespaceInternals} from './firebase-namespace';
2323
import {AppErrorCodes, FirebaseAppError} from './utils/error';
24-
import {Firestore} from '@google-cloud/firestore';
25-
import {FirestoreService} from './firestore/firestore';
2624

2725
import {Auth} from './auth/auth';
2826
import {Messaging} from './messaging/messaging';
2927
import {Storage} from './storage/storage';
28+
import {Database} from '@firebase/database';
29+
import {DatabaseService} from './database/database';
30+
import {Firestore} from '@google-cloud/firestore';
31+
import {FirestoreService} from './firestore/firestore';
3032

3133
/**
3234
* Type representing a callback which is called every time an app lifecycle event occurs.
@@ -290,12 +292,16 @@ export class FirebaseApp {
290292
});
291293
}
292294

293-
/* istanbul ignore next */
294-
public database(): FirebaseServiceInterface {
295-
throw new FirebaseAppError(
296-
AppErrorCodes.INTERNAL_ERROR,
297-
'INTERNAL ASSERT FAILED: Firebase database() service has not been registered.',
298-
);
295+
/**
296+
* Returns the Database service for the specified URL, and the current app.
297+
*
298+
* @return {Database} The Database service instance of this app.
299+
*/
300+
public database(url?: string): Database {
301+
let service: DatabaseService = this.ensureService_('database', () => {
302+
return new DatabaseService(this);
303+
});
304+
return service.getDatabase(url);
299305
}
300306

301307
/**

src/firebase-namespace.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ import {
2424
RefreshTokenCredential,
2525
ApplicationDefaultCredential,
2626
} from './auth/credential';
27-
import {Firestore} from '@google-cloud/firestore';
2827

2928
import {Auth} from './auth/auth';
3029
import {Messaging} from './messaging/messaging';
3130
import {Storage} from './storage/storage';
31+
import {Database} from '@firebase/database';
32+
import {Firestore} from '@google-cloud/firestore';
3233

3334
const DEFAULT_APP_NAME = '[DEFAULT]';
3435

@@ -289,12 +290,16 @@ export class FirebaseNamespace {
289290
return Object.assign(fn, {Auth});
290291
}
291292

292-
/* istanbul ignore next */
293-
public database(): FirebaseServiceInterface {
294-
throw new FirebaseAppError(
295-
AppErrorCodes.INTERNAL_ERROR,
296-
'INTERNAL ASSERT FAILED: Firebase database() service has not been registered.',
297-
);
293+
/**
294+
* Gets the `Database` service namespace. The returned namespace can be used to get the
295+
* `Database` service for the default app or an explicitly specified app.
296+
*/
297+
get database(): FirebaseServiceNamespace<Database> {
298+
const ns: FirebaseNamespace = this;
299+
let fn: FirebaseServiceNamespace<Database> = (app?: FirebaseApp) => {
300+
return ns.ensureApp(app).database();
301+
};
302+
return Object.assign(fn, require('@firebase/database'));
298303
}
299304

300305
/**

src/index.d.ts

+12-112
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import {Bucket} from '@google-cloud/storage';
18+
import * as _rtdb from '@firebase/database';
1819
import * as _firestore from '@google-cloud/firestore';
1920

2021
declare namespace admin {
@@ -67,7 +68,7 @@ declare namespace admin.app {
6768
options: admin.AppOptions;
6869

6970
auth(): admin.auth.Auth;
70-
database(): admin.database.Database;
71+
database(url?: string): admin.database.Database;
7172
firestore(): admin.firestore.Firestore;
7273
messaging(): admin.messaging.Messaging;
7374
storage(): admin.storage.Storage;
@@ -174,117 +175,6 @@ declare namespace admin.credential {
174175
function refreshToken(refreshTokenPathOrObject: string|Object): admin.credential.Credential;
175176
}
176177

177-
declare namespace admin.database {
178-
interface Database {
179-
app: admin.app.App;
180-
181-
goOffline(): void;
182-
goOnline(): void;
183-
ref(path?: string): admin.database.Reference;
184-
refFromURL(url: string): admin.database.Reference;
185-
}
186-
187-
interface DataSnapshot {
188-
key: string|null;
189-
ref: admin.database.Reference;
190-
191-
child(path: string): admin.database.DataSnapshot;
192-
exists(): boolean;
193-
exportVal(): any;
194-
forEach(action: (a: admin.database.DataSnapshot) => boolean): boolean;
195-
getPriority(): string|number|null;
196-
hasChild(path: string): boolean;
197-
hasChildren(): boolean;
198-
numChildren(): number;
199-
toJSON(): Object;
200-
val(): any;
201-
}
202-
203-
interface OnDisconnect {
204-
cancel(onComplete?: (a: Error|null) => any): Promise<void>;
205-
remove(onComplete?: (a: Error|null) => any): Promise<void>;
206-
set(value: any, onComplete?: (a: Error|null) => any): Promise<void>;
207-
setWithPriority(
208-
value: any,
209-
priority: number|string|null,
210-
onComplete?: (a: Error|null) => any
211-
): Promise<void>;
212-
update(values: Object, onComplete?: (a: Error|null) => any): Promise<void>;
213-
}
214-
215-
type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed';
216-
interface Query {
217-
ref: admin.database.Reference;
218-
219-
endAt(value: number|string|boolean|null, key?: string): admin.database.Query;
220-
equalTo(value: number|string|boolean|null, key?: string): admin.database.Query;
221-
isEqual(other: admin.database.Query|null): boolean;
222-
limitToFirst(limit: number): admin.database.Query;
223-
limitToLast(limit: number): admin.database.Query;
224-
off(
225-
eventType?: admin.database.EventType,
226-
callback?: (a: admin.database.DataSnapshot, b?: string|null) => any,
227-
context?: Object|null
228-
): void;
229-
on(
230-
eventType: admin.database.EventType,
231-
callback: (a: admin.database.DataSnapshot|null, b?: string) => any,
232-
cancelCallbackOrContext?: Object|null,
233-
context?: Object|null
234-
): (a: admin.database.DataSnapshot|null, b?: string) => any;
235-
once(
236-
eventType: admin.database.EventType,
237-
successCallback?: (a: admin.database.DataSnapshot, b?: string) => any,
238-
failureCallbackOrContext?: Object|null,
239-
context?: Object|null
240-
): Promise<any>;
241-
orderByChild(path: string): admin.database.Query;
242-
orderByKey(): admin.database.Query;
243-
orderByPriority(): admin.database.Query;
244-
orderByValue(): admin.database.Query;
245-
startAt(value: number|string|boolean|null, key?: string): admin.database.Query;
246-
toJSON(): Object;
247-
toString(): string;
248-
}
249-
250-
interface Reference extends admin.database.Query {
251-
key: string|null;
252-
parent: admin.database.Reference|null;
253-
root: admin.database.Reference;
254-
255-
child(path: string): admin.database.Reference;
256-
onDisconnect(): admin.database.OnDisconnect;
257-
push(value?: any, onComplete?: (a: Error|null) => any): admin.database.ThenableReference;
258-
remove(onComplete?: (a: Error|null) => any): Promise<void>;
259-
set(value: any, onComplete?: (a: Error|null) => any): Promise<void>;
260-
setPriority(
261-
priority: string|number|null,
262-
onComplete: (a: Error|null) => any
263-
): Promise<void>;
264-
setWithPriority(
265-
newVal: any, newPriority: string|number|null,
266-
onComplete?: (a: Error|null) => any
267-
): Promise<void>;
268-
transaction(
269-
transactionUpdate: (a: any) => any,
270-
onComplete?: (a: Error|null, b: boolean, c: admin.database.DataSnapshot|null) => any,
271-
applyLocally?: boolean
272-
): Promise<{
273-
committed: boolean,
274-
snapshot: admin.database.DataSnapshot|null
275-
}>;
276-
update(values: Object, onComplete?: (a: Error|null) => any): Promise<void>;
277-
}
278-
279-
interface ThenableReference extends admin.database.Reference, Promise<any> {}
280-
281-
function enableLogging(logger?: boolean|((message: string) => any), persistent?: boolean): any;
282-
}
283-
284-
declare namespace admin.database.ServerValue {
285-
var TIMESTAMP: number;
286-
}
287-
288178
declare namespace admin.messaging {
289179
type DataMessagePayload = {
290180
[key: string]: string;
@@ -405,6 +295,16 @@ declare namespace admin.storage {
405295
}
406296
}
407297

298+
declare namespace admin.database {
299+
export import Database = _rtdb.Database;
300+
export import Reference = _rtdb.Reference;
301+
export import Query = _rtdb.Query;
302+
export import ServerValue = _rtdb.ServerValue;
303+
export import enableLogging = _rtdb.enableLogging;
304+
export import OnDisconnect = _rtdb.OnDisconnect;
305+
export import DataSnapshot = _rtdb.DataSnapshot;
306+
}
307+
408308
declare namespace admin.firestore {
409309
export import FieldPath = _firestore.FieldPath;
410310
export import FieldValue = _firestore.FieldValue;

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as firebase from './default-namespace';
2020
// For historical reasons, the database code is included as minified code and registers itself
2121
// as a side effect of requiring the file.
2222
/* tslint:disable:no-var-requires */
23-
require('./database/database');
23+
// require('./database/database');
2424
/* tslint:enable:no-var-requires */
2525

2626
export = firebase;

src/utils/error.ts

+15
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ export class FirebaseAuthError extends PrefixedFirebaseError {
176176
}
177177
}
178178

179+
/**
180+
* Firebase Database error code structure. This extends FirebaseError.
181+
*
182+
* @param {ErrorInfo} info The error code info.
183+
* @param {string} [message] The error message. This will override the default
184+
* message if provided.
185+
* @constructor
186+
*/
187+
export class FirebaseDatabaseError extends FirebaseError {
188+
constructor(info: ErrorInfo, message?: string) {
189+
// Override default message if custom message provided.
190+
super({code: 'database/' + info.code, message: message || info.message});
191+
}
192+
}
193+
179194
/**
180195
* Firebase Firestore error code structure. This extends FirebaseError.
181196
*

0 commit comments

Comments
 (0)