UNPKG

ran-boilerplate

Version:

React . Apollo (GraphQL) . Next.js Toolkit

1,191 lines (1,079 loc) 36.8 kB
/*! * Copyright 2017 Google Inc. All Rights Reserved. * * 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. */ 'use strict'; const bun = require('bun'); const common = require('@google-cloud/common'); const commonGrpc = require('@google-cloud/common-grpc'); const extend = require('extend'); const is = require('is'); const through = require('through2'); const util = require('util'); const v1beta1 = require('./v1beta1'); const libVersion = require('../package.json').version; const path = require('./path'); const convert = require('./convert'); /*! * DO NOT REMOVE THE FOLLOWING NAMESPACE DEFINITIONS */ /** * @namespace google.protobuf */ /** * @namespace google.rpc */ /** * @namespace google.firestore.v1beta1 */ /*! * @see ResourcePath */ const ResourcePath = path.ResourcePath; /*! * @see ResourcePath */ const FieldPath = path.FieldPath; /*! * @see FieldValue */ const FieldValue = require('./field-value'); /*! * @see CollectionReference */ let CollectionReference; /*! * @see DocumentReference */ let DocumentReference; /*! * @see DocumentSnapshot */ let DocumentSnapshot; /*! * @see GeoPoint */ let GeoPoint; /*! Injected. */ let validate; /*! * @see WriteBatch */ let WriteBatch; /*! * @see Transaction */ let Transaction; /*! * HTTP header for the resource prefix to improve routing and project isolation * by the backend. * @type {string} */ const CLOUD_RESOURCE_HEADER = 'google-cloud-resource-prefix'; /*! * The maximum number of times to retry idempotent requests. * @type {number} */ const MAX_REQUEST_RETRIES = 5; /*! * GRPC Error code for 'UNAVAILABLE'. * @type {number} */ const GRPC_UNAVAILABLE = 14; /** * Document data (e.g. for use with * [set()]{@link DocumentReference#set}) consisting of fields mapped * to values. * * @typedef {Object.<string, *>} DocumentData */ /** * Update data (for use with [update]{@link DocumentReference#update}) * that contains paths (e.g. 'foo' or 'foo.baz') mapped to values. Fields that * contain dots reference nested fields within the document. * * @typedef {Object.<string, *>} UpdateData */ /** * An options object that configures conditional behavior of * [update()]{@link DocumentReference#update} and * [delete()]{@link DocumentReference#delete} calls in * [DocumentReference]{@link DocumentReference}, * [WriteBatch]{@link WriteBatch}, and * [Transaction]{@link Transaction}. Using Preconditions, these calls * can be restricted to only apply to documents that match the specified * conditions. * * @property {string} lastUpdateTime - The update time to enforce (specified as * an ISO 8601 string). * @typedef {Object} Precondition */ /** * An options object that configures the behavior of * [set()]{@link DocumentReference#set} calls in * [DocumentReference]{@link DocumentReference}, * [WriteBatch]{@link WriteBatch}, and * [Transaction]{@link Transaction}. These calls can be * configured to perform granular merges instead of overwriting the target * documents in their entirety by providing a SetOptions object with * { merge : true }. * * @property {boolean} merge - Changes the behavior of a set() call to only * replace the values specified in its data argument. Fields omitted from the * set() call remain untouched. * @typedef {Object} SetOptions */ /** * The Firestore client represents a Firestore Database and is the entry point * for all Firestore operations. * * @see [Firestore Documentation]{@link https://firebase.google.com/docs/firestore/} * * @class * * @example <caption>Install the client library with <a href="https://www.npmjs.com/">npm</a>:</caption> * npm install --save @google-cloud/firestore * * @example <caption>Import the client library</caption> * var Firestore = require('@google-cloud/firestore'); * * @example <caption>Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:</caption> * var firestore = new Firestore(); * * @example <caption>Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:</caption> * var firestore = new Firestore({ * projectId: 'your-project-id', * keyFilename: '/path/to/keyfile.json' * }); * * @example <caption>include:samples/quickstart.js</caption> * region_tag:firestore_quickstart * Full quickstart example: */ class Firestore extends commonGrpc.Service { /** * @param {Object=} options - [Configuration object](#/docs). */ constructor(options) { let config = { service: 'firestore', apiVersion: 'v1beta1', protoServices: {}, packageJson: require('../package.json'), }; options = extend({}, options, { libName: 'gccl', libVersion: libVersion, }); super(config, options); // GCF currently tears down idle connections after two minutes. Requests // that are issued after this period may fail. On GCF, we therefore issue // these requests as part of a transaction so that we can safely retry until // the network link is reestablished. // // The environment variable FUNCTION_TRIGGER_TYPE is used to detect the GCF // environment. this._preferTransactions = is.defined(process.env.FUNCTION_TRIGGER_TYPE); this._lastSuccessfulRequest = null; if (this._preferTransactions) { Firestore.log('Firestore', 'Detected GCF environment'); } /** * @private * @type {object} * @property {FirestoreClient} Firestore The Firestore GAPIC client. */ this.api = { Firestore: v1beta1(options).firestoreClient(options), }; this._referencePath = new ResourcePath('{{projectId}}', '(default)'); if (options) { if (options.projectId) { validate.isString('options.projectId', options.projectId); this._referencePath = new ResourcePath(options.projectId, '(default)'); } } Firestore.log('Firestore', 'Initialized Firestore'); } /** * The root path to the database. * * @private * @type {string} */ get formattedName() { return this._referencePath.formattedName; } /** * Gets a [DocumentReference]{@link DocumentReference} instance that * refers to the document at the specified path. * * @param {string} documentPath - A slash-separated path to a document. * @returns {DocumentReference} The * [DocumentReference]{@link DocumentReference} instance. * * @example * let documentRef = firestore.doc('collection/document'); * console.log(`Path of document is ${documentRef.path}`); */ doc(documentPath) { validate.isResourcePath('documentPath', documentPath); let path = this._referencePath.append(documentPath); if (!path.isDocument) { throw new Error( `Argument "documentPath" must point to a document, but was "${documentPath}". Your path does not contain an even number of components.` ); } return new DocumentReference(this, path); } /** * Gets a [CollectionReference]{@link CollectionReference} instance * that refers to the collection at the specified path. * * @param {string} collectionPath - A slash-separated path to a collection. * @returns {CollectionReference} The * [CollectionReference]{@link CollectionReference} instance. * * @example * let collectionRef = firestore.collection('collection'); * * // Add a document with an auto-generated ID. * collectionRef.add({foo: 'bar'}).then((documentRef) => { * console.log(`Added document at ${documentRef.path})`); * }); */ collection(collectionPath) { validate.isResourcePath('collectionPath', collectionPath); let path = this._referencePath.append(collectionPath); if (!path.isCollection) { throw new Error( `Argument "collectionPath" must point to a collection, but was "${collectionPath}". Your path does not contain an odd number of components.` ); } return new CollectionReference(this, path); } /** * Creates a [WriteBatch]{@link WriteBatch}, used for performing * multiple writes as a single atomic operation. * * @returns {WriteBatch} A WriteBatch that operates on this Firestore * client. * * @example * let writeBatch = firestore.batch(); * * // Add two documents in an atomic batch. * let data = { foo: 'bar' }; * writeBatch.set(firestore.doc('col/doc1'), data); * writeBatch.set(firestore.doc('col/doc2'), data); * * writeBatch.commit().then(res => { * console.log(`Added document at ${res.writeResults[0].updateTime}`); * }); */ batch() { return new WriteBatch(this); } /** * Creates a [DocumentSnapshot]{@link DocumentSnapshot} or a * [QueryDocumentSnapshot]{@link QueryDocumentSnapshot} from a * `firestore.v1beta1.Document` proto (or from a resource name for missing * documents). * * This API is used by Google Cloud Functions and can be called with both * 'Proto3 JSON' and 'Protobuf JS' encoded data. * * @private * @param {object} documentOrName - The Firestore 'Document' proto or the * resource name of a missing document. * @param {object=} readTime - A 'Timestamp' proto indicating the time this * document was read. * @param {string=} encoding - One of 'json' or 'protobufJS'. Applies to both * the 'document' Proto and 'readTime'. Defaults to 'protobufJS'. * @returns {DocumentSnapshot|QueryDocumentSnapshot} - A QueryDocumentSnapshot * for existing documents, otherwise a DocumentSnapshot. */ snapshot_(documentOrName, readTime, encoding) { let convertTimestamp; let convertDocument; if (!is.defined(encoding) || encoding === 'protobufJS') { convertTimestamp = data => data; convertDocument = data => data; } else if (encoding === 'json') { // Google Cloud Functions calls us with Proto3 JSON format data, which we // must convert to Protobuf JS. convertTimestamp = convert.timestampFromJson; convertDocument = convert.documentFromJson; } else { throw new Error( `Unsupported encoding format. Expected 'json' or 'protobufJS', ` + `but was '${encoding}'.` ); } const document = new DocumentSnapshot.Builder(); if (is.string(documentOrName)) { document.ref = new DocumentReference( this, ResourcePath.fromSlashSeparatedString(documentOrName) ); } else { document.ref = new DocumentReference( this, ResourcePath.fromSlashSeparatedString(documentOrName.name) ); document.fieldsProto = documentOrName.fields ? convertDocument(documentOrName.fields) : {}; document.createTime = DocumentSnapshot.toISOTime( convertTimestamp(documentOrName.createTime, 'documentOrName.createTime') ); document.updateTime = DocumentSnapshot.toISOTime( convertTimestamp(documentOrName.updateTime, 'documentOrName.updateTime') ); } document.readTime = DocumentSnapshot.toISOTime( convertTimestamp(readTime, 'readTime') ); return document.build(); } /** * Executes the given updateFunction and commits the changes applied within * the transaction. * * You can use the transaction object passed to 'updateFunction' to read and * modify Firestore documents under lock. Transactions are committed once * 'updateFunction' resolves and attempted up to five times on failure. * * @param {function(Transaction)} updateFunction - The * function to execute within the transaction * context. * @param {object=} transactionOptions - Transaction options. * @param {number=} transactionOptions.maxAttempts - The maximum number of * attempts for this transaction. * @returns {Promise} If the transaction completed successfully or was * explicitly aborted (by the updateFunction returning a failed Promise), the * Promise returned by the updateFunction will be returned here. Else if the * transaction failed, a rejected Promise with the corresponding failure * error will be returned. * * @example * let counterTransaction = firestore.runTransaction(transaction => { * let documentRef = firestore.doc('col/doc'); * return transaction.get(documentRef).then(doc => { * if (doc.exists) { * let count = doc.get('count') || 0; * if (count > 10) { * return Promise.reject('Reached maximum count'); * } * transaction.update(documentRef, { count: ++count }); * return Promise.resolve(count); * } * * transaction.create(documentRef, { count: 1 }); * return Promise.resolve(1); * }); * }); * * counterTransaction.then(res => { * console.log(`Count updated to ${res}`); * }); */ runTransaction(updateFunction, transactionOptions) { validate.isFunction('updateFunction', updateFunction); const defaultAttempts = 5; let attemptsRemaining = defaultAttempts; let previousTransaction; if (is.defined(transactionOptions)) { validate.isObject('transactionOptions', transactionOptions); validate.isOptionalInteger( 'transactionOptions.maxAttempts', transactionOptions.maxAttempts, 1 ); attemptsRemaining = transactionOptions.maxAttempts || attemptsRemaining; previousTransaction = transactionOptions.previousTransaction; } let transaction = new Transaction(this, previousTransaction); let result; --attemptsRemaining; return transaction .begin() .then(() => { let promise = updateFunction(transaction); result = is.instanceof(promise, Promise) ? promise : Promise.reject( new Error( 'You must return a Promise in your transaction()-callback.' ) ); return result.catch(err => { Firestore.log( 'Firestore.runTransaction', 'Rolling back transaction after callback error:', err ); // Rollback the transaction and return the failed result. return transaction.rollback().then(() => { return result; }); }); }) .then(() => { return transaction.commit().catch(err => { if (attemptsRemaining > 0) { Firestore.log( 'Firestore.runTransaction', `Retrying transaction after error: ${JSON.stringify(err)}.` ); return this.runTransaction(updateFunction, { previousTransaction: transaction, maxAttempts: attemptsRemaining, }); } Firestore.log( 'Firestore.runTransaction', 'Exhausted transaction retries, returning error: %s', err ); return Promise.reject(err); }); }) .then(() => { return result; }); } /** * Fetches the root collections that are associated with this Firestore * database. * * @returns {Promise.<Array.<CollectionReference>>} A Promise that resolves * with an array of CollectionReferences. * * @example * firestore.getCollections().then(collections => { * for (let collection of collections) { * console.log(`Found collection with id: ${collection.id}`); * } * }); */ getCollections() { let rootDocument = new DocumentReference(this, this._referencePath); return rootDocument.getCollections(); } /** * Retrieves multiple documents from Firestore. * * @param {...DocumentReference} documents - The document references * to receive. * @returns {Promise<Array.<DocumentSnapshot>>} A Promise that * contains an array with the resulting document snapshots. * * @example * let documentRef1 = firestore.doc('col/doc1'); * let documentRef2 = firestore.doc('col/doc2'); * * firestore.getAll(documentRef1, documentRef2).then(docs => { * console.log(`First document: ${JSON.stringify(docs[0])}`); * console.log(`Second document: ${JSON.stringify(docs[1])}`); * }); */ getAll(documents) { documents = is.array(arguments[0]) ? arguments[0].slice() : Array.prototype.slice.call(arguments); for (let i = 0; i < documents.length; ++i) { validate.isDocumentReference(i, documents[i]); } return this.getAll_(documents, null); } /** * Internal method to retrieve multiple documents from Firestore, optionally * as part of a transaction. * * @private * @param {Array.<DocumentReference>} docRefs - The documents * to receive. * @param {object=} readOptions - The options to use for this request. * @param {bytes|null} readOptions.transactionId - The transaction ID to use * for this read. * @returns {Array.<DocumentSnapshot>} A Promise that contains an array with * the resulting documents. */ getAll_(docRefs, readOptions) { const requestedDocuments = new Set(); const retrievedDocuments = new Map(); let request = { database: this.formattedName, }; for (let docRef of docRefs) { requestedDocuments.add(docRef.formattedName); } request.documents = Array.from(requestedDocuments); if (readOptions && readOptions.transactionId) { request.transaction = readOptions.transactionId; } let self = this; return self .readStream( this.api.Firestore.batchGetDocuments.bind(this.api.Firestore), request, /* allowRetries= */ true ) .then(stream => { return new Promise((resolve, reject) => { stream .on('error', err => { Firestore.log( 'Firestore.getAll_', 'GetAll failed with error:', err ); reject(err); }) .on('data', response => { try { let document; if (response.found) { Firestore.log( 'Firestore.getAll_', 'Received document: %s', response.found.name ); document = self.snapshot_(response.found, response.readTime); } else { Firestore.log( 'Firestore.getAll_', 'Document missing: %s', response.missing ); document = self.snapshot_( response.missing, response.readTime ); } let path = document.ref.path; retrievedDocuments.set(path, document); } catch (err) { Firestore.log( 'Firestore.getAll_', 'GetAll failed with exception:', err ); reject(err); } }) .on('end', () => { Firestore.log( 'Firestore.getAll_', 'Received %d results', retrievedDocuments.size ); // BatchGetDocuments doesn't preserve document order. We use the // request order to sort the resulting documents. const orderedDocuments = []; for (let docRef of docRefs) { let document = retrievedDocuments.get(docRef.path); if (!is.defined(document)) { reject( new Error(`Did not receive document for "${docRef.path}".`) ); } orderedDocuments.push(document); } resolve(orderedDocuments); }); stream.resume(); }); }); } /** * Generate a unique client-side identifier. * * Used for the creation of new documents. * * @private * @returns {string} A unique 20-character wide identifier. */ static autoId() { let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let autoId = ''; for (let i = 0; i < 20; i++) { autoId += chars.charAt(Math.floor(Math.random() * chars.length)); } return autoId; } /** * Decorate all request options before being sent with to an API request. This * is used to replace any `{{projectId}}` placeholders with the value detected * from the user's environment, if one wasn't provided manually. * * @private */ _decorateRequest(request) { let self = this; function decorate() { return new Promise(resolve => { let decoratedRequest = extend(true, {}, request); decoratedRequest = common.util.replaceProjectIdToken( decoratedRequest, self._referencePath.projectId ); let decoratedGax = {otherArgs: {headers: {}}}; decoratedGax.otherArgs.headers[CLOUD_RESOURCE_HEADER] = self.formattedName; resolve({request: decoratedRequest, gax: decoratedGax}); }); } if (this._referencePath.projectId !== '{{projectId}}') { return decorate(); } return new Promise((resolve, reject) => { this.api.Firestore.getProjectId((err, projectId) => { if (err) { Firestore.log( 'Firestore._decorateRequest', 'Failed to detect project ID: %s', err ); reject(err); } else { Firestore.log( 'Firestore._decorateRequest', 'Detected project ID: %s', projectId ); self._referencePath = new ResourcePath( projectId, self._referencePath.databaseId ); decorate() .then(resolve) .catch(reject); } }); }); } /** * A function returning a Promise that can be retried. * * @private * @callback retryFunction * @returns {Promise} A Promise indicating the function's success. */ /** * Helper method that retries failed Promises. * * If 'delayMs' is specified, waits 'delayMs' between invocations. Otherwise, * schedules the first attempt immediately, and then waits 100 milliseconds * for further attempts. * * @private * @param {number} attemptsRemaining - The number of available attempts. * @param {retryFunction} func - Method returning a Promise than can be * retried. * @param {number=} delayMs - How long to wait before issuing a this retry. * Defaults to zero. * @returns {Promise} - A Promise with the function's result if successful * within `attemptsRemaining`. Otherwise, returns the last rejected Promise. */ _retry(attemptsRemaining, func, delayMs) { let self = this; let currentDelay = delayMs || 0; let nextDelay = delayMs || 100; --attemptsRemaining; return new Promise(resolve => { setTimeout(resolve, currentDelay); }) .then(func) .then(result => { self._lastSuccessfulRequest = new Date().getTime(); return result; }) .catch(err => { if (is.defined(err.code) && err.code !== GRPC_UNAVAILABLE) { Firestore.log( 'Firestore._retry', 'Request failed with unrecoverable error:', err ); return Promise.reject(err); } if (attemptsRemaining === 0) { Firestore.log('Firestore._retry', 'Request failed with error:', err); return Promise.reject(err); } Firestore.log( 'Firestore._retry', 'Retrying request that failed with error:', err ); return self._retry(attemptsRemaining, func, nextDelay); }); } /** * Opens the provided stream and waits for it to become healthy. If an error * occurs before the first byte is read, the method rejects the returned * Promise. * * @private * @param {Stream} resultStream - The Node stream to monitor. * @param {Object=} request - If specified, the request that should be written * to the stream after it opened. * @returns {Promise.<Stream>} The given Stream once it is considered healthy. */ _initializeStream(resultStream, request) { /** The last error we received and have not forwarded yet. */ let errorReceived = null; /** * Whether we have resolved the Promise and returned the stream to the * caller. */ let streamReleased = false; /** * Whether the stream end has been reached. This has to be forwarded to the * caller.. */ let endCalled = false; return new Promise((resolve, reject) => { const releaseStream = () => { if (errorReceived) { Firestore.log( 'Firestore._initializeStream', 'Emit error:', errorReceived ); resultStream.emit('error', errorReceived); errorReceived = null; } else if (!streamReleased) { Firestore.log('Firestore._initializeStream', 'Releasing stream'); streamReleased = true; resultStream.pause(); // Calling 'stream.pause()' only holds up 'data' events and not the // 'end' event we intend to forward here. We therefore need to wait // until the API consumer registers their listeners (in the .then() // call) before emitting any further events. resolve(resultStream); // We execute the forwarding of the 'end' event via setTimeout() as // V8 guarantees that the above the Promise chain is resolved before // any calls invoked via setTimeout(). setTimeout(() => { if (endCalled) { Firestore.log( 'Firestore._initializeStream', 'Forwarding stream close' ); resultStream.emit('end'); } }, 0); } }; // We capture any errors received and buffer them until the caller has // registered a listener. We register our event handler as early as // possible to avoid the default stream behavior (which is just to log and // continue). resultStream.on('readable', () => { releaseStream(); }); resultStream.on('end', () => { Firestore.log('Firestore._initializeStream', 'Received stream end'); endCalled = true; releaseStream(); }); resultStream.on('error', err => { Firestore.log( 'Firestore._initializeStream', 'Received stream error:', err ); // If we receive an error before we were able to receive any data, // reject this stream. if (!streamReleased) { Firestore.log( 'Firestore._initializeStream', 'Received initial error:', err ); streamReleased = true; reject(err); } else { errorReceived = err; } }); if (is.defined(request)) { Firestore.log( 'Firestore._initializeStream', 'Sending request: %j', request ); resultStream.write(request, 'utf-8', () => { Firestore.log( 'Firestore._initializeStream', 'Marking stream as healthy' ); releaseStream(); }); } }); } /** * A funnel for all non-streaming API requests, assigning a project ID where * necessary within the request options. * * @private * @param {function} method - Veneer API endpoint that takes a request and * GAX options. * @param {Object} request - The Protobuf request to send. * @param {boolean} allowRetries - Whether this is an idempotent request that * can be retried. * @returns {Promise.<Object>} A Promise with the request result. */ request(method, request, allowRetries) { let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1; return this._decorateRequest(request).then(decorated => { return this._retry(attempts, () => { return new Promise((resolve, reject) => { Firestore.log( 'Firestore.request', 'Sending request: %j', decorated.request ); method(decorated.request, decorated.gax, (err, result) => { if (err) { Firestore.log('Firestore.request', 'Received error:', err); reject(err); } else { Firestore.log( 'Firestore.request', 'Received response: %j', result ); resolve(result); } }); }); }); }); } /** * A funnel for read-only streaming API requests, assigning a project ID where * necessary within the request options. * * The stream is returned in paused state and needs to be resumed once all * listeners are attached. * * @private * @param {function} method - Streaming Veneer API endpoint that takes a * request and GAX options. * @param {Object} request - The Protobuf request to send. * @param {boolean} allowRetries - Whether this is an idempotent request that * can be retried. * @returns {Promise.<Stream>} A Promise with the resulting read-only stream. */ readStream(method, request, allowRetries) { let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1; return this._decorateRequest(request).then(decorated => { return this._retry(attempts, () => { return new Promise((resolve, reject) => { try { Firestore.log( 'Firestore.readStream', 'Sending request: %j', decorated.request ); let stream = method(decorated.request, decorated.gax); let logger = through.obj(function(chunk, enc, callback) { Firestore.log( 'Firestore.readStream', 'Received response: %j', chunk ); this.push(chunk); callback(); }); resolve(bun([stream, logger])); } catch (err) { Firestore.log('Firestore.readStream', 'Received error:', err); reject(err); } }).then(stream => this._initializeStream(stream)); }); }); } /** * A funnel for read-write streaming API requests, assigning a project ID * where necessary for all writes. * * The stream is returned in paused state and needs to be resumed once all * listeners are attached. * * @private * @param {function} method - Streaming Veneer API endpoint that takes GAX * options. * @param {Object} request - The Protobuf request to send as the first stream * message. * @param {boolean} allowRetries - Whether this is an idempotent request that * can be retried. * @returns {Promise.<Stream>} A Promise with the resulting read/write stream. */ readWriteStream(method, request, allowRetries) { let self = this; let attempts = allowRetries ? MAX_REQUEST_RETRIES : 1; return this._decorateRequest({}).then(decorated => { return this._retry(attempts, () => { return Promise.resolve().then(() => { Firestore.log('Firestore.readWriteStream', 'Opening stream'); // The generated bi-directional streaming API takes the list of GAX // headers as its second argument. let requestStream = method({}, decorated.gax); // The transform stream to assign the project ID. let transform = through.obj(function(chunk, encoding, callback) { let decoratedChunk = extend(true, {}, chunk); common.util.replaceProjectIdToken( decoratedChunk, self._referencePath.projectId ); Firestore.log( 'Firestore.readWriteStream', 'Streaming request: %j', decoratedChunk ); requestStream.write(decoratedChunk, encoding, callback); }); let logger = through.obj(function(chunk, enc, callback) { Firestore.log( 'Firestore.readWriteStream', 'Received response: %j', chunk ); this.push(chunk); callback(); }); let resultStream = bun([transform, requestStream, logger]); return this._initializeStream(resultStream, request); }); }); }); } } /** * A logging function that takes a single string. * * @callback Firestore~logFunction * @param {string} Log message */ /** * Log function to use for debug output. By default, we don't perform any * logging. * * @private * @type {Firestore~logFunction} */ Firestore.log = function() {}; /** * Sets the log function for all active Firestore instances. * * @method Firestore.setLogFunction * @param {Firestore~logFunction} logger - A log function that takes a single * string. */ Firestore.setLogFunction = function(logger) { validate.isFunction('logger', logger); Firestore.log = function(methodName, varargs) { varargs = Array.prototype.slice.call(arguments, 1); let formattedMessage = util.format.apply(null, varargs); let time = new Date().toISOString(); logger( `Firestore (${libVersion}) ${time} [${methodName}]: ` + formattedMessage ); }; }; // Initializing dependencies that require that Firestore class type. let reference = require('./reference')(Firestore); CollectionReference = reference.CollectionReference; DocumentReference = reference.DocumentReference; let document = require('./document')(DocumentReference); DocumentSnapshot = document.DocumentSnapshot; GeoPoint = document.GeoPoint; validate = require('./validate')({ DocumentReference: reference.validateDocumentReference, ResourcePath: ResourcePath.validateResourcePath, }); WriteBatch = require('./write-batch')(Firestore, DocumentReference).WriteBatch; Transaction = require('./transaction')(Firestore); /** * The default export of the `@google-cloud/firestore` package is the * {@link Firestore} class. * * See {@link Firestore} and {@link ClientConfig} for client methods and * configuration options. * * @module {Firestore} @google-cloud/firestore * @alias nodejs-firestore * * @example <caption>Install the client library with <a href="https://www.npmjs.com/">npm</a>:</caption> * npm install --save @google-cloud/firestore * * @example <caption>Import the client library</caption> * var Firestore = require('@google-cloud/firestore'); * * @example <caption>Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:</caption> * var firestore = new Firestore(); * * @example <caption>Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:</caption> * var firestore = new Firestore({ * projectId: 'your-project-id', * keyFilename: '/path/to/keyfile.json' * }); * * @example <caption>include:samples/quickstart.js</caption> * region_tag:firestore_quickstart * Full quickstart example: */ module.exports = Firestore; module.exports.default = Firestore; module.exports.Firestore = Firestore; /** * {@link v1beta1} factory function. * * @name Firestore.v1beta1 * @see v1beta1 * @type {function} */ module.exports.v1beta1 = v1beta1; /** * {@link FieldPath} class. * * @name Firestore.FieldPath * @see FieldPath * @type {Constructor} */ module.exports.FieldPath = FieldPath; /** * {@link FieldValue} class. * * @name Firestore.FieldValue * @see FieldValue * @type {Constructor} */ module.exports.FieldValue = FieldValue; /** * {@link GeoPoint} class. * * @name Firestore.GeoPoint * @see GeoPoint * @type {Constructor} */ module.exports.GeoPoint = GeoPoint;