Skip to content

Commit 8d3b38f

Browse files
authored
Support instance selection for database functions (#171)
1 parent 8cd4195 commit 8d3b38f

File tree

3 files changed

+86
-14
lines changed

3 files changed

+86
-14
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@types/sinon": "^1.16.29",
3939
"chai": "^3.5.0",
4040
"chai-as-promised": "^5.2.0",
41-
"firebase-admin": "^5.4.2",
41+
"firebase-admin": "~5.5.0",
4242
"istanbul": "^0.4.2",
4343
"mocha": "^2.4.5",
4444
"mock-require": "^2.0.1",
@@ -48,7 +48,7 @@
4848
"typescript": "^2.0.3"
4949
},
5050
"peerDependencies": {
51-
"firebase-admin": "~5.4.2"
51+
"firebase-admin": "~5.5.0"
5252
},
5353
"dependencies": {
5454
"@types/express": "^4.0.33",

spec/providers/database.spec.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ describe('DatabaseBuilder', () => {
4949
expect(resource).to.eq('projects/_/instances/subdomain/refs/foo');
5050
});
5151

52+
it('should let developers choose a database instance', () => {
53+
let func = database.instance('custom').ref('foo').onWrite(() => null);
54+
let resource = func.__trigger.eventTrigger.resource;
55+
expect(resource).to.eq('projects/_/instances/custom/refs/foo');
56+
});
57+
5258
it('should return a handler that emits events with a proper DeltaSnapshot', () => {
5359
let handler = database.ref('/users/{id}').onWrite(event => {
5460
expect(event.data.val()).to.deep.equal({ foo: 'bar' });
@@ -75,6 +81,12 @@ describe('DatabaseBuilder', () => {
7581
expect(resource).to.eq('projects/_/instances/subdomain/refs/foo');
7682
});
7783

84+
it('should let developers choose a database instance', () => {
85+
let func = database.instance('custom').ref('foo').onCreate(() => null);
86+
let resource = func.__trigger.eventTrigger.resource;
87+
expect(resource).to.eq('projects/_/instances/custom/refs/foo');
88+
});
89+
7890
it('should return a handler that emits events with a proper DeltaSnapshot', () => {
7991
let handler = database.ref('/users/{id}').onCreate(event => {
8092
expect(event.data.val()).to.deep.equal({ foo: 'bar' });
@@ -101,6 +113,12 @@ describe('DatabaseBuilder', () => {
101113
expect(resource).to.eq('projects/_/instances/subdomain/refs/foo');
102114
});
103115

116+
it('should let developers choose a database instance', () => {
117+
let func = database.instance('custom').ref('foo').onUpdate(() => null);
118+
let resource = func.__trigger.eventTrigger.resource;
119+
expect(resource).to.eq('projects/_/instances/custom/refs/foo');
120+
});
121+
104122
it('should return a handler that emits events with a proper DeltaSnapshot', () => {
105123
let handler = database.ref('/users/{id}').onUpdate(event => {
106124
expect(event.data.val()).to.deep.equal({ foo: 'bar' });
@@ -127,6 +145,12 @@ describe('DatabaseBuilder', () => {
127145
expect(resource).to.eq('projects/_/instances/subdomain/refs/foo');
128146
});
129147

148+
it('should let developers choose a database instance', () => {
149+
let func = database.instance('custom').ref('foo').onDelete(() => null);
150+
let resource = func.__trigger.eventTrigger.resource;
151+
expect(resource).to.eq('projects/_/instances/custom/refs/foo');
152+
});
153+
130154
it('should return a handler that emits events with a proper DeltaSnapshot', () => {
131155
let handler = database.ref('/users/{id}').onDelete(event => {
132156
expect(event.data.val()).to.deep.equal({ foo: 'bar' });
@@ -144,20 +168,44 @@ describe('DatabaseBuilder', () => {
144168

145169
});
146170

171+
describe('resourceToInstanceAndPath', () => {
172+
it('should return the correct instance and path strings', () => {
173+
let [instance, path] = database.resourceToInstanceAndPath('projects/_/instances/foo/refs/bar');
174+
expect(instance).to.equal('https://foo.firebaseio.com');
175+
expect(path).to.equal('/bar');
176+
});
177+
});
178+
147179
describe('DeltaSnapshot', () => {
148180
let subject;
149181
const apps = new appsNamespace.Apps(fakeConfig());
150182

151183
let populate = (old: any, change: any) => {
184+
let [instance, path] = database.resourceToInstanceAndPath('projects/_/instances/other-subdomain/refs/foo');
152185
subject = new database.DeltaSnapshot(
153186
apps.admin,
154187
apps.admin,
188+
instance,
155189
old,
156190
change,
157-
database.resourceToPath('projects/_/instances/mySubdomain/refs/foo')
191+
path
158192
);
159193
};
160194

195+
describe('#ref: firebase.database.Reference', () => {
196+
it('should return a ref for correct instance, not the default instance', () => {
197+
populate({}, {});
198+
expect(subject.ref.toJSON()).to.equal('https://other-subdomain.firebaseio.com/foo');
199+
});
200+
});
201+
202+
describe('#adminRef(): firebase.database.Reference', () => {
203+
it('should return an adminRef for correct instance, not the default instance', () => {
204+
populate({}, {});
205+
expect(subject.adminRef.toJSON()).to.equal('https://other-subdomain.firebaseio.com/foo');
206+
});
207+
});
208+
161209
describe('#val(): any', () => {
162210
it('should return child values based on the child path', () => {
163211
populate({ a: { b: 'c' } }, { a: { d: 'e' } });
@@ -372,23 +420,27 @@ describe('DeltaSnapshot', () => {
372420
});
373421

374422
it('should return null for the root', () => {
423+
let [instance, path] = database.resourceToInstanceAndPath('projects/_/instances/refs/refs');
375424
const snapshot = new database.DeltaSnapshot(
376425
apps.admin,
377426
apps.admin,
427+
instance,
378428
null,
379429
null,
380-
database.resourceToPath('projects/_/instances/foo/refs')
430+
path
381431
);
382432
expect(snapshot.key).to.be.null;
383433
});
384434

385435
it('should return null for explicit root', () => {
436+
let [instance, path] = database.resourceToInstanceAndPath('projects/_/instances/refs/refs');
386437
expect(new database.DeltaSnapshot(
387438
apps.admin,
388439
apps.admin,
440+
instance,
389441
null,
390442
{},
391-
database.resourceToPath('projects/_/instances/foo/refs')
443+
path
392444
).key).to.be.null;
393445
});
394446

src/providers/database.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ export const provider = 'google.firebase.database';
3333
// NOTE(inlined): Should we relax this a bit to allow staging or alternate implementations of our API?
3434
const databaseURLRegex = new RegExp('https://([^.]+).firebaseio.com');
3535

36+
/**
37+
* Pick the Realtime Database instance to use. If omitted, will pick the default database for your project.
38+
*/
39+
export function instance(instance: string): InstanceBuilder {
40+
return new InstanceBuilder(instance);
41+
}
42+
43+
export class InstanceBuilder {
44+
/* @internal */
45+
constructor(private instance: string) {}
46+
47+
ref(path: string): RefBuilder {
48+
return new RefBuilder(apps(), `projects/_/instances/${this.instance}/refs/${path}`);
49+
}
50+
}
51+
3652
/**
3753
* Handle events at a Firebase Realtime Database Reference.
3854
*
@@ -75,7 +91,7 @@ export function ref(path: string): RefBuilder {
7591
/** Builder used to create Cloud Functions for Firebase Realtime Database References. */
7692
export class RefBuilder {
7793
/** @internal */
78-
constructor(private apps: apps.Apps, private resource) { }
94+
constructor(private apps: apps.Apps, private resource: string) { }
7995

8096
/** Respond to any write that affects a ref. */
8197
onWrite(handler: (event: Event<DeltaSnapshot>) => PromiseLike<any> | any): CloudFunction<DeltaSnapshot> {
@@ -105,12 +121,14 @@ export class RefBuilder {
105121
if (raw.data instanceof DeltaSnapshot) {
106122
return raw.data;
107123
}
124+
let [dbInstance, path] = resourceToInstanceAndPath(raw.resource);
108125
return new DeltaSnapshot(
109126
this.apps.forMode(raw.auth),
110127
this.apps.admin,
128+
dbInstance,
111129
raw.data.data,
112130
raw.data.delta,
113-
resourceToPath(raw.resource),
131+
path,
114132
);
115133
};
116134
return makeCloudFunction({
@@ -126,21 +144,22 @@ export class RefBuilder {
126144

127145
/* Utility function to extract database reference from resource string */
128146
/** @internal */
129-
export function resourceToPath(resource) {
147+
export function resourceToInstanceAndPath(resource) {
130148
let resourceRegex = `projects/([^/]+)/instances/([^/]+)/refs(/.+)?`;
131149
let match = resource.match(new RegExp(resourceRegex));
132150
if (!match) {
133151
throw new Error(`Unexpected resource string for Firebase Realtime Database event: ${resource}. ` +
134152
'Expected string in the format of "projects/_/instances/{firebaseioSubdomain}/refs/{ref=**}"');
135153
}
136-
let [, project, /* instance */, path] = match;
154+
let [, project, dbInstanceName, path] = match;
137155
if (project !== '_') {
138156
throw new Error(`Expect project to be '_' in a Firebase Realtime Database event`);
139157
}
140-
return path;
158+
let dbInstance = 'https://' + dbInstanceName + '.firebaseio.com';
159+
return [dbInstance, path];
141160
}
142161

143-
export class DeltaSnapshot implements firebase.database.DataSnapshot {
162+
export class DeltaSnapshot {
144163
private _adminRef: firebase.database.Reference;
145164
private _ref: firebase.database.Reference;
146165
private _path: string;
@@ -154,6 +173,7 @@ export class DeltaSnapshot implements firebase.database.DataSnapshot {
154173
constructor(
155174
private app: firebase.app.App,
156175
private adminApp: firebase.app.App,
176+
private instance: string,
157177
data: any,
158178
delta: any,
159179
path?: string // path will be undefined for the database root
@@ -168,14 +188,14 @@ export class DeltaSnapshot implements firebase.database.DataSnapshot {
168188

169189
get ref(): firebase.database.Reference {
170190
if (!this._ref) {
171-
this._ref = this.app.database().ref(this._fullPath());
191+
this._ref = this.app.database(this.instance).ref(this._fullPath());
172192
}
173193
return this._ref;
174194
}
175195

176196
get adminRef(): firebase.database.Reference {
177197
if (!this._adminRef) {
178-
this._adminRef = this.adminApp.database().ref(this._fullPath());
198+
this._adminRef = this.adminApp.database(this.instance).ref(this._fullPath());
179199
}
180200
return this._adminRef;
181201
}
@@ -291,7 +311,7 @@ export class DeltaSnapshot implements firebase.database.DataSnapshot {
291311
}
292312

293313
private _dup(previous: boolean, childPath?: string): DeltaSnapshot {
294-
let dup = new DeltaSnapshot(this.app, this.adminApp, undefined, undefined);
314+
let dup = new DeltaSnapshot(this.app, this.adminApp, this.instance, undefined, undefined);
295315
[dup._path, dup._data, dup._delta, dup._childPath, dup._newData] =
296316
[this._path, this._data, this._delta, this._childPath, this._newData];
297317

0 commit comments

Comments
 (0)