Skip to content

Commit d9c3726

Browse files
Deferring the initialization of the GAPIC client (#190)
1 parent 8fdd9d3 commit d9c3726

File tree

14 files changed

+657
-663
lines changed

14 files changed

+657
-663
lines changed

conformance/runner.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ function runTest(spec) {
295295
console.log(`Running Spec:\n${JSON.stringify(spec, null, 2)}\n`); // eslint-disable-line no-console
296296

297297
const updateTest = function(spec) {
298-
firestore.api.Firestore._commit = commitHandler(spec);
298+
firestore._firestoreClient._commit = commitHandler(spec);
299299

300300
return Promise.resolve().then(() => {
301301
let varargs = [];
@@ -321,7 +321,7 @@ function runTest(spec) {
321321
};
322322

323323
const queryTest = function(spec) {
324-
firestore.api.Firestore._runQuery = queryHandler(spec);
324+
firestore._firestoreClient._runQuery = queryHandler(spec);
325325

326326
const applyClause = function(query, clause) {
327327
if (clause.select) {
@@ -370,7 +370,7 @@ function runTest(spec) {
370370
};
371371

372372
const deleteTest = function(spec) {
373-
firestore.api.Firestore._commit = commitHandler(spec);
373+
firestore._firestoreClient._commit = commitHandler(spec);
374374

375375
return Promise.resolve().then(() => {
376376
if (spec.precondition) {
@@ -383,7 +383,7 @@ function runTest(spec) {
383383
};
384384

385385
const setTest = function(spec) {
386-
firestore.api.Firestore._commit = commitHandler(spec);
386+
firestore._firestoreClient._commit = commitHandler(spec);
387387

388388
return Promise.resolve().then(() => {
389389
const setOption = {};
@@ -405,7 +405,7 @@ function runTest(spec) {
405405
};
406406

407407
const createTest = function(spec) {
408-
firestore.api.Firestore._commit = commitHandler(spec);
408+
firestore._firestoreClient._commit = commitHandler(spec);
409409

410410
return Promise.resolve().then(() => {
411411
return docRef(spec.docRefPath).create(
@@ -415,7 +415,7 @@ function runTest(spec) {
415415
};
416416

417417
const getTest = function(spec) {
418-
firestore.api.Firestore._batchGetDocuments = getHandler(spec);
418+
firestore._firestoreClient._batchGetDocuments = getHandler(spec);
419419

420420
return Promise.resolve().then(() => {
421421
return docRef(spec.docRefPath).get();
@@ -427,7 +427,7 @@ function runTest(spec) {
427427

428428
const writeStream = through.obj();
429429

430-
firestore.api.Firestore._listen = () => {
430+
firestore._firestoreClient._listen = () => {
431431
return duplexify.obj(through.obj(), writeStream);
432432
};
433433

@@ -534,6 +534,10 @@ describe('Conformance Tests', function() {
534534
return testSuite.tests;
535535
};
536536

537+
before(() => {
538+
firestore._ensureClient();
539+
});
540+
537541
for (let testCase of loadTestCases()) {
538542
const isIgnored = ignoredRe.find(re => re.test(testCase.description));
539543
const isExclusive = exclusiveRe.find(re => re.test(testCase.description));

src/index.js

Lines changed: 126 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,28 @@ class Firestore extends commonGrpc.Service {
206206

207207
super(config, options);
208208

209+
/**
210+
* @private
211+
* @type {object|null}
212+
* @property {FirestoreClient} Firestore The Firestore GAPIC client.
213+
*/
214+
this._firestoreClient = null;
215+
216+
/**
217+
* The configuration options for the GAPIC client.
218+
* @private
219+
* @type {Object}
220+
*/
221+
this._initalizationOptions = options;
222+
223+
/**
224+
* A Promise that resolves when client initialization completes. Can be
225+
* 'null' if initialization hasn't started yet.
226+
* @private
227+
* @type {Promise|null}
228+
*/
229+
this._clientInitialized = null;
230+
209231
// GCF currently tears down idle connections after two minutes. Requests
210232
// that are issued after this period may fail. On GCF, we therefore issue
211233
// these requests as part of a transaction so that we can safely retry until
@@ -220,22 +242,13 @@ class Firestore extends commonGrpc.Service {
220242
Firestore.log('Firestore', 'Detected GCF environment');
221243
}
222244

223-
/**
224-
* @private
225-
* @type {object}
226-
* @property {FirestoreClient} Firestore The Firestore GAPIC client.
227-
*/
228-
this.api = {
229-
Firestore: v1beta1(options).firestoreClient(options),
230-
};
231-
232-
this._referencePath = new ResourcePath('{{projectId}}', '(default)');
233-
234-
if (options) {
235-
if (options.projectId) {
236-
validate.isString('options.projectId', options.projectId);
237-
this._referencePath = new ResourcePath(options.projectId, '(default)');
238-
}
245+
if (options && options.projectId) {
246+
validate.isString('options.projectId', options.projectId);
247+
this._referencePath = new ResourcePath(options.projectId, '(default)');
248+
} else {
249+
// Initialize a temporary reference path that will be overwritten during
250+
// project ID detection.
251+
this._referencePath = new ResourcePath('{{projectId}}', '(default)');
239252
}
240253

241254
Firestore.log('Firestore', 'Initialized Firestore');
@@ -589,11 +602,7 @@ class Firestore extends commonGrpc.Service {
589602
let self = this;
590603

591604
return self
592-
.readStream(
593-
this.api.Firestore.batchGetDocuments.bind(this.api.Firestore),
594-
request,
595-
/* allowRetries= */ true
596-
)
605+
.readStream('batchGetDocuments', request, /* allowRetries= */ true)
597606
.then(stream => {
598607
return new Promise((resolve, reject) => {
599608
stream
@@ -684,60 +693,70 @@ class Firestore extends commonGrpc.Service {
684693
}
685694

686695
/**
687-
* Decorate all request options before being sent with to an API request. This
688-
* is used to replace any `{{projectId}}` placeholders with the value detected
689-
* from the user's environment, if one wasn't provided manually.
696+
* Initializes the client and detects the Firestore Project ID. Returns a
697+
* Promise on completion. If the client is already initialized, the returned
698+
* Promise resolves immediately.
690699
*
691700
* @private
692701
*/
693-
_decorateRequest(request) {
694-
let self = this;
695-
696-
function decorate() {
697-
return new Promise(resolve => {
698-
let decoratedRequest = extend(true, {}, request);
699-
decoratedRequest = common.util.replaceProjectIdToken(
700-
decoratedRequest,
701-
self._referencePath.projectId
702-
);
703-
704-
let decoratedGax = {otherArgs: {headers: {}}};
705-
decoratedGax.otherArgs.headers[CLOUD_RESOURCE_HEADER] =
706-
self.formattedName;
707-
708-
resolve({request: decoratedRequest, gax: decoratedGax});
702+
_ensureClient() {
703+
if (!this._clientInitialized) {
704+
this._clientInitialized = new Promise((resolve, reject) => {
705+
this._firestoreClient = v1beta1(
706+
this._initalizationOptions
707+
).firestoreClient(this._initalizationOptions);
708+
709+
Firestore.log('Firestore', 'Initialized Firestore GAPIC Client');
710+
711+
// We schedule Project ID detection using `setImmediate` to allow the
712+
// testing framework to provide its own implementation of
713+
// `getProjectId`.
714+
setImmediate(() => {
715+
this._firestoreClient.getProjectId((err, projectId) => {
716+
if (err) {
717+
Firestore.log(
718+
'Firestore._ensureClient',
719+
'Failed to detect project ID: %s',
720+
err
721+
);
722+
reject(err);
723+
} else {
724+
Firestore.log(
725+
'Firestore._ensureClient',
726+
'Detected project ID: %s',
727+
projectId
728+
);
729+
this._referencePath = new ResourcePath(
730+
projectId,
731+
this._referencePath.databaseId
732+
);
733+
resolve();
734+
}
735+
});
736+
});
709737
});
710738
}
739+
return this._clientInitialized;
740+
}
711741

712-
if (this._referencePath.projectId !== '{{projectId}}') {
713-
return decorate();
714-
}
742+
/**
743+
* Decorate the request options of an API request. This is used to replace
744+
* any `{{projectId}}` placeholders with the value detected from the user's
745+
* environment, if one wasn't provided manually.
746+
*
747+
* @private
748+
*/
749+
_decorateRequest(request) {
750+
let decoratedRequest = extend(true, {}, request);
751+
decoratedRequest = common.util.replaceProjectIdToken(
752+
decoratedRequest,
753+
this._referencePath.projectId
754+
);
715755

716-
return new Promise((resolve, reject) => {
717-
this.api.Firestore.getProjectId((err, projectId) => {
718-
if (err) {
719-
Firestore.log(
720-
'Firestore._decorateRequest',
721-
'Failed to detect project ID: %s',
722-
err
723-
);
724-
reject(err);
725-
} else {
726-
Firestore.log(
727-
'Firestore._decorateRequest',
728-
'Detected project ID: %s',
729-
projectId
730-
);
731-
self._referencePath = new ResourcePath(
732-
projectId,
733-
self._referencePath.databaseId
734-
);
735-
decorate()
736-
.then(resolve)
737-
.catch(reject);
738-
}
739-
});
740-
});
756+
let decoratedGax = {otherArgs: {headers: {}}};
757+
decoratedGax.otherArgs.headers[CLOUD_RESOURCE_HEADER] = this.formattedName;
758+
759+
return {request: decoratedRequest, gax: decoratedGax};
741760
}
742761

743762
/**
@@ -922,37 +941,42 @@ class Firestore extends commonGrpc.Service {
922941
* necessary within the request options.
923942
*
924943
* @private
925-
* @param {function} method - Veneer API endpoint that takes a request and
926-
* GAX options.
944+
* @param {function} methodName - Name of the veneer API endpoint that takes a
945+
* request and GAX options.
927946
* @param {Object} request - The Protobuf request to send.
928947
* @param {boolean} allowRetries - Whether this is an idempotent request that
929948
* can be retried.
930949
* @returns {Promise.<Object>} A Promise with the request result.
931950
*/
932-
request(method, request, allowRetries) {
951+
request(methodName, request, allowRetries) {
933952
let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1;
934953

935-
return this._decorateRequest(request).then(decorated => {
954+
return this._ensureClient().then(() => {
955+
const decorated = this._decorateRequest(request);
936956
return this._retry(attempts, () => {
937957
return new Promise((resolve, reject) => {
938958
Firestore.log(
939959
'Firestore.request',
940960
'Sending request: %j',
941961
decorated.request
942962
);
943-
method(decorated.request, decorated.gax, (err, result) => {
944-
if (err) {
945-
Firestore.log('Firestore.request', 'Received error:', err);
946-
reject(err);
947-
} else {
948-
Firestore.log(
949-
'Firestore.request',
950-
'Received response: %j',
951-
result
952-
);
953-
resolve(result);
963+
this._firestoreClient[methodName](
964+
decorated.request,
965+
decorated.gax,
966+
(err, result) => {
967+
if (err) {
968+
Firestore.log('Firestore.request', 'Received error:', err);
969+
reject(err);
970+
} else {
971+
Firestore.log(
972+
'Firestore.request',
973+
'Received response: %j',
974+
result
975+
);
976+
resolve(result);
977+
}
954978
}
955-
});
979+
);
956980
});
957981
});
958982
});
@@ -966,17 +990,18 @@ class Firestore extends commonGrpc.Service {
966990
* listeners are attached.
967991
*
968992
* @private
969-
* @param {function} method - Streaming Veneer API endpoint that takes a
970-
* request and GAX options.
993+
* @param {string} methodName - Name of the streaming Veneer API endpoint that
994+
* takes a request and GAX options.
971995
* @param {Object} request - The Protobuf request to send.
972996
* @param {boolean} allowRetries - Whether this is an idempotent request that
973997
* can be retried.
974998
* @returns {Promise.<Stream>} A Promise with the resulting read-only stream.
975999
*/
976-
readStream(method, request, allowRetries) {
1000+
readStream(methodName, request, allowRetries) {
9771001
let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1;
9781002

979-
return this._decorateRequest(request).then(decorated => {
1003+
return this._ensureClient().then(() => {
1004+
const decorated = this._decorateRequest(request);
9801005
return this._retry(attempts, () => {
9811006
return new Promise((resolve, reject) => {
9821007
try {
@@ -985,7 +1010,10 @@ class Firestore extends commonGrpc.Service {
9851010
'Sending request: %j',
9861011
decorated.request
9871012
);
988-
let stream = method(decorated.request, decorated.gax);
1013+
let stream = this._firestoreClient[methodName](
1014+
decorated.request,
1015+
decorated.gax
1016+
);
9891017
let logger = through.obj(function(chunk, enc, callback) {
9901018
Firestore.log(
9911019
'Firestore.readStream',
@@ -1013,25 +1041,29 @@ class Firestore extends commonGrpc.Service {
10131041
* listeners are attached.
10141042
*
10151043
* @private
1016-
* @param {function} method - Streaming Veneer API endpoint that takes GAX
1017-
* options.
1044+
* @param {string} methodName - Name of the streaming Veneer API endpoint that
1045+
* takes GAX options.
10181046
* @param {Object} request - The Protobuf request to send as the first stream
10191047
* message.
10201048
* @param {boolean} allowRetries - Whether this is an idempotent request that
10211049
* can be retried.
10221050
* @returns {Promise.<Stream>} A Promise with the resulting read/write stream.
10231051
*/
1024-
readWriteStream(method, request, allowRetries) {
1052+
readWriteStream(methodName, request, allowRetries) {
10251053
let self = this;
10261054
let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1;
10271055

1028-
return this._decorateRequest({}).then(decorated => {
1056+
return this._ensureClient().then(() => {
1057+
const decorated = this._decorateRequest(request);
10291058
return this._retry(attempts, () => {
10301059
return Promise.resolve().then(() => {
10311060
Firestore.log('Firestore.readWriteStream', 'Opening stream');
10321061
// The generated bi-directional streaming API takes the list of GAX
10331062
// headers as its second argument.
1034-
let requestStream = method({}, decorated.gax);
1063+
let requestStream = this._firestoreClient[methodName](
1064+
{},
1065+
decorated.gax
1066+
);
10351067

10361068
// The transform stream to assign the project ID.
10371069
let transform = through.obj(function(chunk, encoding, callback) {

0 commit comments

Comments
 (0)