Skip to content

Commit dfab18a

Browse files
authored
Add TransactionOptions (#6189)
* Add TransactionOptions * Fix whitespace * Fix whitespace * Add changeset * Update comments
1 parent 5ce0676 commit dfab18a

File tree

13 files changed

+278
-133
lines changed

13 files changed

+278
-133
lines changed

.changeset/lazy-nails-guess.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/firestore': patch
3+
---
4+
5+
Add `TransactionOptions` param to `runTransaction` method

common/api-review/firestore-lite.api.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export class QuerySnapshot<T = DocumentData> {
254254
export function refEqual<T>(left: DocumentReference<T> | CollectionReference<T>, right: DocumentReference<T> | CollectionReference<T>): boolean;
255255

256256
// @public
257-
export function runTransaction<T>(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise<T>): Promise<T>;
257+
export function runTransaction<T>(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise<T>, options?: TransactionOptions): Promise<T>;
258258

259259
// @public
260260
export function serverTimestamp(): FieldValue;
@@ -331,6 +331,11 @@ export class Transaction {
331331
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this;
332332
}
333333

334+
// @public
335+
export interface TransactionOptions {
336+
readonly maxAttempts?: number;
337+
}
338+
334339
// @public
335340
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
336341

common/api-review/firestore.api.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ export class QuerySnapshot<T = DocumentData> {
385385
export function refEqual<T>(left: DocumentReference<T> | CollectionReference<T>, right: DocumentReference<T> | CollectionReference<T>): boolean;
386386

387387
// @public
388-
export function runTransaction<T>(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise<T>): Promise<T>;
388+
export function runTransaction<T>(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise<T>, options?: TransactionOptions): Promise<T>;
389389

390390
// @public
391391
export function serverTimestamp(): FieldValue;
@@ -475,6 +475,11 @@ export class Transaction {
475475
update(documentRef: DocumentReference<unknown>, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this;
476476
}
477477

478+
// @public
479+
export interface TransactionOptions {
480+
readonly maxAttempts?: number;
481+
}
482+
478483
// @public
479484
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
480485

packages/firestore/lite/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ export {
111111

112112
export { WriteBatch, writeBatch } from '../src/lite-api/write_batch';
113113

114+
export { TransactionOptions } from '../src/lite-api/transaction_options';
115+
114116
export { Transaction, runTransaction } from '../src/lite-api/transaction';
115117

116118
export { setLogLevel, LogLevelString as LogLevel } from '../src/util/log';

packages/firestore/src/api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export {
8989

9090
export { Unsubscribe, SnapshotListenOptions } from './api/reference_impl';
9191

92+
export { TransactionOptions } from './api/transaction_options';
93+
9294
export { runTransaction, Transaction } from './api/transaction';
9395

9496
export {

packages/firestore/src/api/transaction.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
import { firestoreClientTransaction } from '../core/firestore_client';
1919
import { Transaction as InternalTransaction } from '../core/transaction';
20+
import {
21+
TransactionOptions as TranasactionOptionsInternal,
22+
DEFAULT_TRANSACTION_OPTIONS,
23+
validateTransactionOptions
24+
} from '../core/transaction_options';
2025
import { DocumentReference } from '../lite-api/reference';
2126
import { Transaction as LiteTransaction } from '../lite-api/transaction';
2227
import { validateReference } from '../lite-api/write_batch';
@@ -25,6 +30,7 @@ import { cast } from '../util/input_validation';
2530
import { ensureFirestoreConfigured, Firestore } from './database';
2631
import { ExpUserDataWriter } from './reference_impl';
2732
import { DocumentSnapshot, SnapshotMetadata } from './snapshot';
33+
import { TransactionOptions } from './transaction_options';
2834

2935
/**
3036
* A reference to a transaction.
@@ -92,11 +98,20 @@ export class Transaction extends LiteTransaction {
9298
*/
9399
export function runTransaction<T>(
94100
firestore: Firestore,
95-
updateFunction: (transaction: Transaction) => Promise<T>
101+
updateFunction: (transaction: Transaction) => Promise<T>,
102+
options?: TransactionOptions
96103
): Promise<T> {
97104
firestore = cast(firestore, Firestore);
105+
const optionsWithDefaults: TranasactionOptionsInternal = {
106+
...DEFAULT_TRANSACTION_OPTIONS,
107+
...options
108+
};
109+
validateTransactionOptions(optionsWithDefaults);
98110
const client = ensureFirestoreConfigured(firestore);
99-
return firestoreClientTransaction(client, internalTransaction =>
100-
updateFunction(new Transaction(firestore, internalTransaction))
111+
return firestoreClientTransaction(
112+
client,
113+
internalTransaction =>
114+
updateFunction(new Transaction(firestore, internalTransaction)),
115+
optionsWithDefaults
101116
);
102117
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
export { TransactionOptions } from '../lite-api/transaction_options';

packages/firestore/src/core/firestore_client.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
syncEngineWrite
8383
} from './sync_engine_impl';
8484
import { Transaction } from './transaction';
85+
import { TransactionOptions } from './transaction_options';
8586
import { TransactionRunner } from './transaction_runner';
8687
import { View } from './view';
8788
import { ViewSnapshot } from './view_snapshot';
@@ -483,14 +484,16 @@ export function firestoreClientAddSnapshotsInSyncListener(
483484
*/
484485
export function firestoreClientTransaction<T>(
485486
client: FirestoreClient,
486-
updateFunction: (transaction: Transaction) => Promise<T>
487+
updateFunction: (transaction: Transaction) => Promise<T>,
488+
options: TransactionOptions
487489
): Promise<T> {
488490
const deferred = new Deferred<T>();
489491
client.asyncQueue.enqueueAndForget(async () => {
490492
const datastore = await getDatastore(client);
491493
new TransactionRunner<T>(
492494
client.asyncQueue,
493495
datastore,
496+
options,
494497
updateFunction,
495498
deferred
496499
).run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Code, FirestoreError } from '../util/error';
19+
20+
export const DEFAULT_TRANSACTION_OPTIONS: TransactionOptions = {
21+
maxAttempts: 5
22+
};
23+
24+
/**
25+
* Options to customize transaction behavior.
26+
*/
27+
export declare interface TransactionOptions {
28+
/** Maximum number of attempts to commit, after which transaction fails. Default is 5. */
29+
readonly maxAttempts: number;
30+
}
31+
32+
export function validateTransactionOptions(options: TransactionOptions): void {
33+
if (options.maxAttempts < 1) {
34+
throw new FirestoreError(
35+
Code.INVALID_ARGUMENT,
36+
'Max attempts must be at least 1'
37+
);
38+
}
39+
}

packages/firestore/src/core/transaction_runner.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,24 @@ import { Deferred } from '../util/promise';
2424
import { isNullOrUndefined } from '../util/types';
2525

2626
import { Transaction } from './transaction';
27-
28-
export const DEFAULT_MAX_ATTEMPTS_COUNT = 5;
27+
import { TransactionOptions } from './transaction_options';
2928

3029
/**
3130
* TransactionRunner encapsulates the logic needed to run and retry transactions
3231
* with backoff.
3332
*/
3433
export class TransactionRunner<T> {
35-
private attemptsRemaining = DEFAULT_MAX_ATTEMPTS_COUNT;
34+
private attemptsRemaining: number;
3635
private backoff: ExponentialBackoff;
3736

3837
constructor(
3938
private readonly asyncQueue: AsyncQueue,
4039
private readonly datastore: Datastore,
40+
private readonly options: TransactionOptions,
4141
private readonly updateFunction: (transaction: Transaction) => Promise<T>,
4242
private readonly deferred: Deferred<T>
4343
) {
44+
this.attemptsRemaining = options.maxAttempts;
4445
this.backoff = new ExponentialBackoff(
4546
this.asyncQueue,
4647
TimerId.TransactionRetry

packages/firestore/src/lite-api/transaction.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
import { getModularInstance } from '@firebase/util';
1919

2020
import { Transaction as InternalTransaction } from '../core/transaction';
21+
import {
22+
DEFAULT_TRANSACTION_OPTIONS,
23+
TransactionOptions as TranasactionOptionsInternal,
24+
validateTransactionOptions
25+
} from '../core/transaction_options';
2126
import { TransactionRunner } from '../core/transaction_runner';
2227
import { fail } from '../util/assert';
2328
import { newAsyncQueue } from '../util/async_queue_impl';
@@ -39,6 +44,7 @@ import {
3944
LiteUserDataWriter
4045
} from './reference_impl';
4146
import { DocumentSnapshot } from './snapshot';
47+
import { TransactionOptions } from './transaction_options';
4248
import {
4349
newUserDataReader,
4450
parseSetData,
@@ -266,14 +272,21 @@ export class Transaction {
266272
*/
267273
export function runTransaction<T>(
268274
firestore: Firestore,
269-
updateFunction: (transaction: Transaction) => Promise<T>
275+
updateFunction: (transaction: Transaction) => Promise<T>,
276+
options?: TransactionOptions
270277
): Promise<T> {
271278
firestore = cast(firestore, Firestore);
272279
const datastore = getDatastore(firestore);
280+
const optionsWithDefaults: TranasactionOptionsInternal = {
281+
...DEFAULT_TRANSACTION_OPTIONS,
282+
...options
283+
};
284+
validateTransactionOptions(optionsWithDefaults);
273285
const deferred = new Deferred<T>();
274286
new TransactionRunner<T>(
275287
newAsyncQueue(),
276288
datastore,
289+
optionsWithDefaults,
277290
internalTransaction =>
278291
updateFunction(new Transaction(firestore, internalTransaction)),
279292
deferred
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* Options to customize transaction behavior.
20+
*/
21+
export declare interface TransactionOptions {
22+
/** Maximum number of attempts to commit, after which transaction fails. Default is 5. */
23+
readonly maxAttempts?: number;
24+
}

0 commit comments

Comments
 (0)