From 087e029f949f10a520af801798df63b156a7a2d6 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 24 Oct 2023 17:46:22 +0200 Subject: [PATCH 1/8] chore(deps): update dependency @types/node to v20 (#1928) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61f7851cd..80449453a 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@types/duplexify": "^3.5.0", "@types/extend": "^3.0.0", "@types/mocha": "^9.0.0", - "@types/node": "^18.0.0", + "@types/node": "^20.0.0", "@types/sinon": "^10.0.0", "@types/through2": "^2.0.34", "c8": "^8.0.0", From 5da7138abc15e99edc51fdfd25a70bf5eb0c97b9 Mon Sep 17 00:00:00 2001 From: Ehsan Date: Tue, 24 Oct 2023 18:29:22 -0700 Subject: [PATCH 2/8] docs: Improve documentation for `DocumentReference.create`. (#1926) Co-authored-by: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> --- dev/src/reference.ts | 2 +- types/firestore.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/reference.ts b/dev/src/reference.ts index 79626b479..cad75511a 100644 --- a/dev/src/reference.ts +++ b/dev/src/reference.ts @@ -371,7 +371,7 @@ export class DocumentReference< * * @param {DocumentData} data An object that contains the fields and data to * serialize as the document. - * @throws {Error} If the provided input is not a valid Firestore document. + * @throws {Error} If the provided input is not a valid Firestore document or if the document already exists. * @returns {Promise.} A Promise that resolves with the * write time of this create. * diff --git a/types/firestore.d.ts b/types/firestore.d.ts index ce6c9adb2..18d240a44 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -1273,7 +1273,7 @@ declare namespace FirebaseFirestore { * provided object values. The write fails if the document already exists * * @param data The object data to serialize as the document. - * @throws Error If the provided input is not a valid Firestore document. + * @throws {Error} If the provided input is not a valid Firestore document or if the document already exists. * @return A Promise resolved with the write time of this create. */ create(data: WithFieldValue): Promise; From 8a4ae5b07712588bd0deae917d9e17f34f1ebb60 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Thu, 9 Nov 2023 11:54:49 -0500 Subject: [PATCH 3/8] fix: Remove temporary header encoding workaround (#1935) * fix: Remove header encoding workaround * remove unused import --- dev/src/index.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/dev/src/index.ts b/dev/src/index.ts index 1a95d689b..e797669b3 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -84,7 +84,6 @@ import { RECURSIVE_DELETE_MIN_PENDING_OPS, RecursiveDelete, } from './recursive-delete'; -import {stringify} from 'querystring'; export { CollectionReference, @@ -593,21 +592,6 @@ export class Firestore implements firestore.Firestore { } } - // TODO (multi-db) Revert this override of gax.routingHeader.fromParams - // after a permanent fix is applied. See b/292075646 - // This override of the routingHeader.fromParams does not - // encode forward slash characters. This is a temporary fix for b/291780066 - gax.routingHeader.fromParams = params => { - return stringify(params, undefined, undefined, { - encodeURIComponent: (val: string) => { - return val - .split('/') - .map(component => encodeURIComponent(component)) - .join('/'); - }, - }); - }; - if (this._settings.ssl === false) { const grpcModule = this._settings.grpc ?? require('google-gax').grpc; const sslCreds = grpcModule.credentials.createInsecure(); From 53d7c2f504a7cd23c8a23a513568a0825559775a Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:25:11 -0700 Subject: [PATCH 4/8] chore: update cloud-rad version to ^0.4.0 (#1940) Source-Link: https://github.com/googleapis/synthtool/commit/1063ef32bfe41b112bade7a2dfad4e84d0058ebd Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:e92044720ab3cb6984a70b0c6001081204375959ba3599ef6c42dd99a7783a67 Co-authored-by: Owl Bot --- .eslintignore | 1 + .github/.OwlBot.lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index ea5b04aeb..c4a0963e9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,3 +5,4 @@ build/ docs/ protos/ samples/generated/ +system-test/**/fixtures diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 807a89161..638efabfb 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest - digest: sha256:8b6a07a38d1583d96b6e251ba208bd4ef0bc2a0cc37471ffc518841651d15bd6 -# created: 2023-09-25T22:18:27.595486267Z + digest: sha256:e92044720ab3cb6984a70b0c6001081204375959ba3599ef6c42dd99a7783a67 +# created: 2023-11-10T00:24:05.581078808Z From b29d0deaf3e927c7f56adee7de3252d8a38af4e5 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 13 Nov 2023 23:29:24 +0100 Subject: [PATCH 5/8] chore(deps): update dependency sinon to v17 (#1925) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 80449453a..eba52a91c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/extend": "^3.0.0", "@types/mocha": "^9.0.0", "@types/node": "^20.0.0", - "@types/sinon": "^10.0.0", + "@types/sinon": "^17.0.0", "@types/through2": "^2.0.34", "c8": "^8.0.0", "chai": "^4.1.2", @@ -90,7 +90,7 @@ "mocha": "^9.2.2", "protobufjs-cli": "^1.1.2", "proxyquire": "^2.1.3", - "sinon": "^16.0.0", + "sinon": "^17.0.0", "through2": "^4.0.0", "ts-node": "^10.0.0", "typescript": "^5.2.2" From f83a995f2bcb5fe4b35124d6a48d27a47a6b27fd Mon Sep 17 00:00:00 2001 From: dansaadati Date: Fri, 17 Nov 2023 11:53:54 -0800 Subject: [PATCH 6/8] chore: update cloud-rad version to ^0.4.0 (#1947) --- .kokoro/release/docs-devsite.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.kokoro/release/docs-devsite.sh b/.kokoro/release/docs-devsite.sh index 2198e67fe..b6a4662b8 100755 --- a/.kokoro/release/docs-devsite.sh +++ b/.kokoro/release/docs-devsite.sh @@ -25,5 +25,5 @@ if [[ -z "$CREDENTIALS" ]]; then fi npm install -npm install --no-save @google-cloud/cloud-rad@^0.2.5 -npx @google-cloud/cloud-rad \ No newline at end of file +npm install --no-save @google-cloud/cloud-rad@^0.4.0 +npx @google-cloud/cloud-rad From 170d05b1fa6c720d1109506ed3d3feb525c16efe Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:06:14 -0700 Subject: [PATCH 7/8] fix: update retry policy to not retry streams that have not made progress (#1946) fix: update retry policy to not retry streams with retryable error that have not made progress receiving documents --- dev/src/index.ts | 2 +- dev/src/reference.ts | 122 +++++++++++-------- dev/src/util.ts | 15 +++ dev/test/aggregateQuery.ts | 41 ++++++- dev/test/query.ts | 239 ++++++++++++++++++++++++++++++++++++- 5 files changed, 367 insertions(+), 52 deletions(-) diff --git a/dev/src/index.ts b/dev/src/index.ts index e797669b3..47fe60eb0 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -1637,7 +1637,7 @@ export class Firestore implements firestore.Firestore { function streamReady(): void { if (!streamInitialized) { streamInitialized = true; - logger('Firestore._initializeStream', requestTag, 'Releasing stream'); + logger('Firestore._initializeStream', requestTag, 'Stream ready'); resolve(resultStream); } } diff --git a/dev/src/reference.ts b/dev/src/reference.ts index cad75511a..012634c8f 100644 --- a/dev/src/reference.ts +++ b/dev/src/reference.ts @@ -44,6 +44,7 @@ import {defaultConverter} from './types'; import { autoId, Deferred, + getTotalTimeout, isPermanentRpcError, mapToArray, requestTag, @@ -2569,6 +2570,15 @@ export class Query< return isPermanentRpcError(err, methodName); } + _hasRetryTimedOut(methodName: string, startTime: number): boolean { + const totalTimeout = getTotalTimeout(methodName); + if (totalTimeout === 0) { + return false; + } + + return Date.now() - startTime >= totalTimeout; + } + /** * Internal streaming method that accepts an optional transaction ID. * @@ -2579,6 +2589,7 @@ export class Query< */ _stream(transactionId?: Uint8Array): NodeJS.ReadableStream { const tag = requestTag(); + const startTime = Date.now(); let lastReceivedDocument: QueryDocumentSnapshot< AppModelType, @@ -2638,8 +2649,9 @@ export class Query< let streamActive: Deferred; do { streamActive = new Deferred(); + const methodName = 'runQuery'; backendStream = await this._firestore.requestStream( - 'runQuery', + methodName, /* bidirectional= */ false, request, tag @@ -2656,12 +2668,28 @@ export class Query< 'Query failed with retryable stream error:', err ); - // Enqueue a "no-op" write into the stream and resume the query - // once it is processed. This allows any enqueued results to be - // consumed before resuming the query so that the query resumption - // can start at the correct document. + + // Enqueue a "no-op" write into the stream and wait for it to be + // read by the downstream consumer. This ensures that all enqueued + // results in the stream are consumed, which will give us an accurate + // value for `lastReceivedDocument`. stream.write(NOOP_MESSAGE, () => { - if (lastReceivedDocument) { + if (this._hasRetryTimedOut(methodName, startTime)) { + logger( + 'Query._stream', + tag, + 'Query failed with retryable stream error but the total retry timeout has exceeded.' + ); + stream.destroy(err); + streamActive.resolve(/* active= */ false); + } else if (lastReceivedDocument) { + logger( + 'Query._stream', + tag, + 'Query failed with retryable stream error and progress was made receiving ' + + 'documents, so the stream is being retried.' + ); + // Restart the query but use the last document we received as // the query cursor. Note that we do not use backoff here. The // call to `requestStream()` will backoff should the restart @@ -2673,8 +2701,21 @@ export class Query< } else { request = this.startAfter(lastReceivedDocument).toProto(); } + + // Set lastReceivedDocument to null before each retry attempt to ensure the retry makes progress + lastReceivedDocument = null; + + streamActive.resolve(/* active= */ true); + } else { + logger( + 'Query._stream', + tag, + 'Query failed with retryable stream error however no progress was made receiving ' + + 'documents, so the stream is being closed.' + ); + stream.destroy(err); + streamActive.resolve(/* active= */ false); } - streamActive.resolve(/* active= */ true); }); } else { logger( @@ -3320,48 +3361,33 @@ export class AggregateQuery< // catch below. const request = this.toProto(transactionId); - let streamActive: Deferred; - do { - streamActive = new Deferred(); - const backendStream = await firestore.requestStream( - 'runAggregationQuery', - /* bidirectional= */ false, - request, - tag - ); - stream.on('close', () => { - backendStream.resume(); - backendStream.end(); - }); - backendStream.on('error', err => { - backendStream.unpipe(stream); - // If a non-transactional query failed, attempt to restart. - // Transactional queries are retried via the transaction runner. - if ( - !transactionId && - !isPermanentRpcError(err, 'runAggregationQuery') - ) { - logger( - 'AggregateQuery._stream', - tag, - 'AggregateQuery failed with retryable stream error:', - err - ); - streamActive.resolve(/* active= */ true); - } else { - logger( - 'AggregateQuery._stream', - tag, - 'AggregateQuery failed with stream error:', - err - ); - stream.destroy(err); - streamActive.resolve(/* active= */ false); - } - }); + const backendStream = await firestore.requestStream( + 'runAggregationQuery', + /* bidirectional= */ false, + request, + tag + ); + stream.on('close', () => { backendStream.resume(); - backendStream.pipe(stream); - } while (await streamActive.promise); + backendStream.end(); + }); + backendStream.on('error', err => { + // TODO(group-by) When group-by queries are supported for aggregates + // consider implementing retries if the stream is making progress + // receiving results for groups. See the use of lastReceivedDocument + // in the retry strategy for runQuery. + + backendStream.unpipe(stream); + logger( + 'AggregateQuery._stream', + tag, + 'AggregateQuery failed with stream error:', + err + ); + stream.destroy(err); + }); + backendStream.resume(); + backendStream.pipe(stream); }) .catch(e => stream.destroy(e)); diff --git a/dev/src/util.ts b/dev/src/util.ts index c68695f82..667959402 100644 --- a/dev/src/util.ts +++ b/dev/src/util.ts @@ -178,6 +178,21 @@ export function getRetryCodes(methodName: string): number[] { return getServiceConfig(methodName)?.retry?.retryCodes ?? []; } +/** + * Gets the total timeout in milliseconds from the retry settings in + * the service config for the given RPC. If the total timeout is not + * set, then `0` is returned. + * + * @private + * @internal + */ +export function getTotalTimeout(methodName: string): number { + return ( + getServiceConfig(methodName)?.retry?.backoffSettings?.totalTimeoutMillis ?? + 0 + ); +} + /** * Returns the backoff setting from the service configuration. * @private diff --git a/dev/test/aggregateQuery.ts b/dev/test/aggregateQuery.ts index ff81a254d..b3e9490fb 100644 --- a/dev/test/aggregateQuery.ts +++ b/dev/test/aggregateQuery.ts @@ -126,19 +126,31 @@ describe('aggregate query interface', () => { }); }); - it('handles stream exception at initialization', () => { + it('handles stream exception at initialization', async () => { + let attempts = 0; const query = firestore.collection('collectionId').count(); query._stream = () => { + ++attempts; throw new Error('Expected error'); }; - return expect(query.get()).to.eventually.rejectedWith('Expected error'); + await query + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal('Expected error'); + expect(attempts).to.equal(1); + }); }); it('handles stream exception during initialization', async () => { + let attempts = 0; const overrides: ApiOverride = { runAggregationQuery: () => { + ++attempts; return stream(new Error('Expected error')); }, }; @@ -152,6 +164,31 @@ describe('aggregate query interface', () => { }) .catch(err => { expect(err.message).to.equal('Expected error'); + expect(attempts).to.equal(5); + }); + }); + + it('handles message without result during initialization', async () => { + let attempts = 0; + const overrides: ApiOverride = { + runAggregationQuery: () => { + ++attempts; + return stream({readTime: {seconds: 5, nanos: 6}}); + }, + }; + firestore = await createInstance(overrides); + + const query = firestore.collection('collectionId').count(); + await query + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal( + 'RunAggregationQueryResponse is missing result' + ); + expect(attempts).to.equal(1); }); }); }); diff --git a/dev/test/query.ts b/dev/test/query.ts index da2c8c079..97dce3453 100644 --- a/dev/test/query.ts +++ b/dev/test/query.ts @@ -50,7 +50,7 @@ import { writeResult, } from './util/helpers'; -import {GoogleError} from 'google-gax'; +import {GoogleError, Status} from 'google-gax'; import api = google.firestore.v1; import protobuf = google.protobuf; import {Filter} from '../src/filter'; @@ -449,6 +449,14 @@ export function result( } } +export function heartbeat(count: number): api.IRunQueryResponse { + return { + document: null, + readTime: {seconds: 5, nanos: 6}, + skippedResults: count, + }; +} + describe('query interface', () => { let firestore: Firestore; @@ -714,9 +722,11 @@ describe('query interface', () => { }); it('handles stream exception at initialization', () => { + let attempts = 0; const query = firestore.collection('collectionId'); query._stream = () => { + ++attempts; throw new Error('Expected error'); }; @@ -727,12 +737,16 @@ describe('query interface', () => { }) .catch(err => { expect(err.message).to.equal('Expected error'); + expect(attempts).to.equal(1); }); }); it('handles stream exception during initialization', () => { + let attempts = 0; + const overrides: ApiOverride = { runQuery: () => { + ++attempts; return stream(new Error('Expected error')); }, }; @@ -747,6 +761,7 @@ describe('query interface', () => { }) .catch(err => { expect(err.message).to.equal('Expected error'); + expect(attempts).to.equal(5); }); }); }); @@ -773,6 +788,228 @@ describe('query interface', () => { }); }); + it('handles stream exception after initialization and heartbeat', () => { + const deadlineExceededErr = new GoogleError(); + deadlineExceededErr.code = Status.DEADLINE_EXCEEDED; + deadlineExceededErr.message = 'DEADLINE_EXCEEDED error message'; + + let attempts = 0; + + const overrides: ApiOverride = { + runQuery: () => { + ++attempts; + + // A heartbeat message will initialize the stream, but it is not + // a document, so it does not represent progress made on the stream. + return stream(heartbeat(1000), deadlineExceededErr); + }, + }; + + return createInstance(overrides).then(firestoreInstance => { + firestore = firestoreInstance; + return firestore + .collection('collectionId') + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal('DEADLINE_EXCEEDED error message'); + + // The heartbeat initialized the stream before there was a stream + // exception, so we only expect a single attempt at streaming. + expect(attempts).to.equal(1); + }); + }); + }); + + function handlesRetryableExceptionUntilProgressStops( + withHeartbeat: boolean + ): Promise { + let attempts = 0; + + // count of stream initializations that are successful + // and make progress (a document is received before error) + const initializationsWithProgress = 10; + + // Receiving these error codes on a stream after the stream has received document data + // should result in the stream retrying indefinitely. + const retryableErrorCodes = [ + Status.DEADLINE_EXCEEDED, + Status.UNAVAILABLE, + Status.INTERNAL, + Status.UNAVAILABLE, + ]; + + const overrides: ApiOverride = { + runQuery: x => { + ++attempts; + + // Validate that runQuery is called with cursor of the lastReceivedDocument + // for all attempts except but the first. + if (attempts > 1) { + const docPath = + x?.structuredQuery?.startAt?.values?.[0].referenceValue || ''; + const docId = docPath.substring(docPath.lastIndexOf('/')); + expect(docId).to.equal( + `/id-${Math.min(initializationsWithProgress, attempts - 1)}` + ); + expect(x?.structuredQuery?.orderBy?.length).to.equal(1); + expect(x?.structuredQuery?.orderBy?.[0].field?.fieldPath).to.equal( + '__name__' + ); + } + + const streamElements = []; + + // A heartbeat is a message that may be received on a stream while + // a query is running. If the test is configured `withHeartbeat = true` + // then the fake stream will include a heartbeat before the first + // document. This heartbeat message has the side effect of initializing + // the stream, but it does not represent progress of streaming the results. + // Testing with and without heartbeats allows us to evaluate different + // retry logic in the SDK. + if (withHeartbeat) { + streamElements.push(heartbeat(1000)); + } + + // For the first X initializations, the stream will make progress + // receiving documents in the result set. + // For the X+1 attempt, the stream will not make progress before + // the error. If a stream gets an error without progress, the + // retry policy will close the stream. + if (attempts <= initializationsWithProgress) { + streamElements.push(result(`id-${attempts}`)); + } + + // Create an error with one of the retryable error codes + const googleError = new GoogleError(); + googleError.code = + retryableErrorCodes[attempts % retryableErrorCodes.length]; + googleError.message = 'test error message'; + streamElements.push(googleError); + + return stream(...streamElements); + }, + }; + + return createInstance(overrides).then(firestoreInstance => { + firestore = firestoreInstance; + const query = firestore.collection('collectionId'); + query._hasRetryTimedOut = () => false; + return query + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal('test error message'); + + // Assert that runQuery was retried the expected number + // of times based on the test configuration. + // + // If the test is running with heartbeat messages, then + // the test will always validate retry logic for an + // initialized stream. + // + // If the test is running without heartbeat messages, + // then the test will validate retry logic for both + // initialized and uninitialized streams. Specifically, + // the last retry will fail with an uninitialized stream. + const initilizationRetries = withHeartbeat ? 1 : 5; + expect(attempts).to.equal( + initializationsWithProgress + initilizationRetries + ); + }); + }); + } + + it('handles retryable exception until progress stops with heartbeat', async () => { + await handlesRetryableExceptionUntilProgressStops(true); + }); + + it('handles retryable exception until progress stops without heartbeat', async () => { + await handlesRetryableExceptionUntilProgressStops(false); + }); + + it('handles retryable exception with progress until timeout', async () => { + let attempts = 0; + + const overrides: ApiOverride = { + runQuery: () => { + ++attempts; + + const streamElements = []; + streamElements.push(result(`id-${attempts}`)); + + // Create an error with a retryable error code + const googleError = new GoogleError(); + googleError.code = Status.DEADLINE_EXCEEDED; + googleError.message = 'test error message'; + streamElements.push(googleError); + + return stream(...streamElements); + }, + }; + + return createInstance(overrides).then(firestoreInstance => { + firestore = firestoreInstance; + const query = firestore.collection('collectionId'); + // Fake our timeout check to fail after 10 retry attempts + query._hasRetryTimedOut = (methodName, startTime) => { + expect(methodName).to.equal('runQuery'); + expect(startTime).to.be.lessThanOrEqual(Date.now()); + return attempts >= 10; + }; + + return query + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal('test error message'); + + expect(attempts).to.equal(10); + }); + }); + }); + + it('handles non-retryable after recieving data (with get())', () => { + let attempts = 0; + + const overrides: ApiOverride = { + runQuery: () => { + ++attempts; + + const streamElements = []; + streamElements.push(result(`id-${attempts}`)); + + // Create an error with one of the retryable error codes + const googleError = new GoogleError(); + googleError.code = Status.UNKNOWN; + googleError.message = 'test error message'; + streamElements.push(googleError); + + return stream(...streamElements); + }, + }; + + return createInstance(overrides).then(firestoreInstance => { + firestore = firestoreInstance; + return firestore + .collection('collectionId') + .get() + .then(() => { + throw new Error('Unexpected success in Promise'); + }) + .catch(err => { + expect(err.message).to.equal('test error message'); + expect(attempts).to.equal(1); + }); + }); + }); + it('handles stream exception after initialization (with stream())', done => { const responses = [ () => stream(result('first'), new Error('Expected error')), From 47768485c1c5502ed9c7471501a809a252046b6c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:05:53 -0700 Subject: [PATCH 8/8] chore(main): release 7.1.1 (#1939) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bf71f7e..854fe4ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://www.npmjs.com/package/@google-cloud/firestore?activeTab=versions +## [7.1.1](https://github.com/googleapis/nodejs-firestore/compare/v7.1.0...v7.1.1) (2023-11-20) + + +### Bug Fixes + +* Remove temporary header encoding workaround ([#1935](https://github.com/googleapis/nodejs-firestore/issues/1935)) ([8a4ae5b](https://github.com/googleapis/nodejs-firestore/commit/8a4ae5b07712588bd0deae917d9e17f34f1ebb60)) +* Update retry policy to not retry streams that have not made progress receiving documents ([170d05b](https://github.com/googleapis/nodejs-firestore/commit/170d05b1fa6c720d1109506ed3d3feb525c16efe)) + ## [7.1.0](https://github.com/googleapis/nodejs-firestore/compare/v7.0.0...v7.1.0) (2023-10-11) diff --git a/package.json b/package.json index eba52a91c..04fcec5b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/firestore", "description": "Firestore Client Library for Node.js", - "version": "7.1.0", + "version": "7.1.1", "license": "Apache-2.0", "author": "Google Inc.", "engines": { diff --git a/samples/package.json b/samples/package.json index 5b69e81ea..a0f594283 100644 --- a/samples/package.json +++ b/samples/package.json @@ -11,7 +11,7 @@ "test": "mocha --timeout 600000" }, "dependencies": { - "@google-cloud/firestore": "^7.1.0" + "@google-cloud/firestore": "^7.1.1" }, "devDependencies": { "chai": "^4.2.0",