/** * @flow * Firestore Transaction representation wrapper */ import { mergeFieldPathData } from './utils'; import { buildNativeMap } from './utils/serialize'; import type Firestore from './'; import type { TransactionMeta } from './TransactionHandler'; import type DocumentReference from './DocumentReference'; import type DocumentSnapshot from './DocumentSnapshot'; import { isObject, isString } from '../../utils'; import FieldPath from './FieldPath'; import { getNativeModule } from '../../utils/native'; type Command = { type: 'set' | 'update' | 'delete', path: string, data: ?{ [string]: any }, options: ?{ merge: boolean }, }; type SetOptions = { merge: boolean, }; /** * @class Transaction */ export default class Transaction { _pendingResult: ?any; _firestore: Firestore; _meta: TransactionMeta; _commandBuffer: Array; constructor(firestore: Firestore, meta: TransactionMeta) { this._meta = meta; this._commandBuffer = []; this._firestore = firestore; this._pendingResult = undefined; } /** * ------------- * INTERNAL API * ------------- */ /** * Clears the command buffer and any pending result in prep for * the next transaction iteration attempt. * * @private */ _prepare() { this._commandBuffer = []; this._pendingResult = undefined; } /** * ------------- * PUBLIC API * ------------- */ /** * Reads the document referenced by the provided DocumentReference. * * @param documentRef DocumentReference A reference to the document to be retrieved. Value must not be null. * * @returns Promise */ get(documentRef: DocumentReference): Promise { // todo validate doc ref return getNativeModule(this._firestore).transactionGetDocument( this._meta.id, documentRef.path ); } /** * Writes to the document referred to by the provided DocumentReference. * If the document does not exist yet, it will be created. If you pass options, * the provided data can be merged into the existing document. * * @param documentRef DocumentReference A reference to the document to be created. Value must not be null. * @param data Object An object of the fields and values for the document. * @param options SetOptions An object to configure the set behavior. * Pass {merge: true} to only replace the values specified in the data argument. * Fields omitted will remain untouched. * * @returns {Transaction} */ set( documentRef: DocumentReference, data: Object, options?: SetOptions ): Transaction { // todo validate doc ref // todo validate data is object this._commandBuffer.push({ type: 'set', path: documentRef.path, data: buildNativeMap(data), options, }); return this; } /** * Updates fields in the document referred to by this DocumentReference. * The update will fail if applied to a document that does not exist. Nested * fields can be updated by providing dot-separated field path strings or by providing FieldPath objects. * * @param documentRef DocumentReference A reference to the document to be updated. Value must not be null. * @param args any Either an object containing all of the fields and values to update, * or a series of arguments alternating between fields (as string or FieldPath * objects) and values. * * @returns {Transaction} */ update(documentRef: DocumentReference, ...args: Array): Transaction { // todo validate doc ref let data = {}; if (args.length === 1) { if (!isObject(args[0])) { throw new Error( 'Transaction.update failed: If using a single data argument, it must be an object.' ); } [data] = args; } else if (args.length % 2 === 1) { throw new Error( 'Transaction.update failed: Must have either a single object data argument, or equal numbers of data key/value pairs.' ); } else { for (let i = 0; i < args.length; i += 2) { const key = args[i]; const value = args[i + 1]; if (isString(key)) { data[key] = value; } else if (key instanceof FieldPath) { data = mergeFieldPathData(data, key._segments, value); } else { throw new Error( `Transaction.update failed: Argument at index ${i} must be a string or FieldPath` ); } } } this._commandBuffer.push({ type: 'update', path: documentRef.path, data: buildNativeMap(data), }); return this; } /** * Deletes the document referred to by the provided DocumentReference. * * @param documentRef DocumentReference A reference to the document to be deleted. Value must not be null. * * @returns {Transaction} */ delete(documentRef: DocumentReference): Transaction { // todo validate doc ref this._commandBuffer.push({ type: 'delete', path: documentRef.path, }); return this; } }