write-batch.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. "use strict";
  2. /*!
  3. * Copyright 2019 Google Inc. All Rights Reserved.
  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. Object.defineProperty(exports, "__esModule", { value: true });
  18. exports.WriteBatch = exports.WriteResult = void 0;
  19. exports.validateSetOptions = validateSetOptions;
  20. exports.validateDocumentData = validateDocumentData;
  21. exports.validateFieldValue = validateFieldValue;
  22. const document_1 = require("./document");
  23. const logger_1 = require("./logger");
  24. const path_1 = require("./path");
  25. const helpers_1 = require("./reference/helpers");
  26. const serializer_1 = require("./serializer");
  27. const timestamp_1 = require("./timestamp");
  28. const util_1 = require("./util");
  29. const validate_1 = require("./validate");
  30. const trace_util_1 = require("./telemetry/trace-util");
  31. /**
  32. * A WriteResult wraps the write time set by the Firestore servers on sets(),
  33. * updates(), and creates().
  34. *
  35. * @class WriteResult
  36. */
  37. class WriteResult {
  38. /**
  39. * @private
  40. *
  41. * @param _writeTime The time of the corresponding document write.
  42. */
  43. constructor(_writeTime) {
  44. this._writeTime = _writeTime;
  45. }
  46. /**
  47. * The write time as set by the Firestore servers.
  48. *
  49. * @type {Timestamp}
  50. * @name WriteResult#writeTime
  51. * @readonly
  52. *
  53. * @example
  54. * ```
  55. * let documentRef = firestore.doc('col/doc');
  56. *
  57. * documentRef.set({foo: 'bar'}).then(writeResult => {
  58. * console.log(`Document written at: ${writeResult.writeTime.toDate()}`);
  59. * });
  60. * ```
  61. */
  62. get writeTime() {
  63. return this._writeTime;
  64. }
  65. /**
  66. * Returns true if this `WriteResult` is equal to the provided value.
  67. *
  68. * @param {*} other The value to compare against.
  69. * @return true if this `WriteResult` is equal to the provided value.
  70. */
  71. isEqual(other) {
  72. return (this === other ||
  73. (other instanceof WriteResult &&
  74. this._writeTime.isEqual(other._writeTime)));
  75. }
  76. }
  77. exports.WriteResult = WriteResult;
  78. /**
  79. * A Firestore WriteBatch that can be used to atomically commit multiple write
  80. * operations at once.
  81. *
  82. * @class WriteBatch
  83. */
  84. class WriteBatch {
  85. /**
  86. * The number of writes in this batch.
  87. * @private
  88. * @internal
  89. */
  90. get _opCount() {
  91. return this._ops.length;
  92. }
  93. /** @private */
  94. constructor(firestore) {
  95. /**
  96. * An array of document paths and the corresponding write operations that are
  97. * executed as part of the commit. The resulting `api.IWrite` will be sent to
  98. * the backend.
  99. *
  100. * @private
  101. * @internal
  102. */
  103. this._ops = [];
  104. this._committed = false;
  105. this._firestore = firestore;
  106. this._serializer = new serializer_1.Serializer(firestore);
  107. this._allowUndefined = !!firestore._settings.ignoreUndefinedProperties;
  108. }
  109. /**
  110. * Checks if this write batch has any pending operations.
  111. *
  112. * @private
  113. * @internal
  114. */
  115. get isEmpty() {
  116. return this._ops.length === 0;
  117. }
  118. /**
  119. * Throws an error if this batch has already been committed.
  120. *
  121. * @private
  122. * @internal
  123. */
  124. verifyNotCommitted() {
  125. if (this._committed) {
  126. throw new Error('Cannot modify a WriteBatch that has been committed.');
  127. }
  128. }
  129. /**
  130. * Create a document with the provided object values. This will fail the batch
  131. * if a document exists at its location.
  132. *
  133. * @param {DocumentReference} documentRef A reference to the document to be
  134. * created.
  135. * @param {T} data The object to serialize as the document.
  136. * @throws {Error} If the provided input is not a valid Firestore document.
  137. * @returns {WriteBatch} This WriteBatch instance. Used for chaining
  138. * method calls.
  139. *
  140. * @example
  141. * ```
  142. * let writeBatch = firestore.batch();
  143. * let documentRef = firestore.collection('col').doc();
  144. *
  145. * writeBatch.create(documentRef, {foo: 'bar'});
  146. *
  147. * writeBatch.commit().then(() => {
  148. * console.log('Successfully executed batch.');
  149. * });
  150. * ```
  151. */
  152. create(documentRef, data) {
  153. const ref = (0, helpers_1.validateDocumentReference)('documentRef', documentRef);
  154. const firestoreData = ref._converter.toFirestore(data);
  155. validateDocumentData('data', firestoreData,
  156. /* allowDeletes= */ false, this._allowUndefined);
  157. this.verifyNotCommitted();
  158. const transform = document_1.DocumentTransform.fromObject(ref, firestoreData);
  159. transform.validate();
  160. const precondition = new document_1.Precondition({ exists: false });
  161. const op = () => {
  162. const document = document_1.DocumentSnapshot.fromObject(ref, firestoreData);
  163. const write = document.toWriteProto();
  164. if (!transform.isEmpty) {
  165. write.updateTransforms = transform.toProto(this._serializer);
  166. }
  167. write.currentDocument = precondition.toProto();
  168. return write;
  169. };
  170. this._ops.push({ docPath: documentRef.path, op });
  171. return this;
  172. }
  173. /**
  174. * Deletes a document from the database.
  175. *
  176. * @param {DocumentReference} documentRef A reference to the document to be
  177. * deleted.
  178. * @param {Precondition=} precondition A precondition to enforce for this
  179. * delete.
  180. * @param {Timestamp=} precondition.lastUpdateTime If set, enforces that the
  181. * document was last updated at lastUpdateTime. Fails the batch if the
  182. * document doesn't exist or was last updated at a different time.
  183. * @param {boolean= } precondition.exists If set to true, enforces that the target
  184. * document must or must not exist.
  185. * @returns {WriteBatch} This WriteBatch instance. Used for chaining
  186. * method calls.
  187. *
  188. * @example
  189. * ```
  190. * let writeBatch = firestore.batch();
  191. * let documentRef = firestore.doc('col/doc');
  192. *
  193. * writeBatch.delete(documentRef);
  194. *
  195. * writeBatch.commit().then(() => {
  196. * console.log('Successfully executed batch.');
  197. * });
  198. * ```
  199. */
  200. delete(
  201. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  202. documentRef, precondition) {
  203. const ref = (0, helpers_1.validateDocumentReference)('documentRef', documentRef);
  204. validateDeletePrecondition('precondition', precondition, { optional: true });
  205. this.verifyNotCommitted();
  206. const conditions = new document_1.Precondition(precondition);
  207. const op = () => {
  208. const write = { delete: ref.formattedName };
  209. if (!conditions.isEmpty) {
  210. write.currentDocument = conditions.toProto();
  211. }
  212. return write;
  213. };
  214. this._ops.push({ docPath: documentRef.path, op });
  215. return this;
  216. }
  217. /**
  218. * Write to the document referred to by the provided
  219. * [DocumentReference]{@link DocumentReference}. If the document does not
  220. * exist yet, it will be created. If you pass [SetOptions]{@link SetOptions},
  221. * the provided data can be merged into the existing document.
  222. *
  223. * @param {DocumentReference} documentRef A reference to the document to be
  224. * set.
  225. * @param {T|Partial<T>} data The object to serialize as the document.
  226. * @param {SetOptions=} options An object to configure the set behavior.
  227. * @param {boolean=} options.merge - If true, set() merges the values
  228. * specified in its data argument. Fields omitted from this set() call
  229. * remain untouched. If your input sets any field to an empty map, all nested
  230. * fields are overwritten.
  231. * @param {Array.<string|FieldPath>=} options.mergeFields - If provided,
  232. * set() only replaces the specified field paths. Any field path that is no
  233. * specified is ignored and remains untouched. If your input sets any field to
  234. * an empty map, all nested fields are overwritten.
  235. * @throws {Error} If the provided input is not a valid Firestore document.
  236. * @returns {WriteBatch} This WriteBatch instance. Used for chaining
  237. * method calls.
  238. *
  239. * @example
  240. * ```
  241. * let writeBatch = firestore.batch();
  242. * let documentRef = firestore.doc('col/doc');
  243. *
  244. * writeBatch.set(documentRef, {foo: 'bar'});
  245. *
  246. * writeBatch.commit().then(() => {
  247. * console.log('Successfully executed batch.');
  248. * });
  249. * ```
  250. */
  251. set(documentRef, data, options) {
  252. validateSetOptions('options', options, { optional: true });
  253. const mergeLeaves = options && 'merge' in options && options.merge;
  254. const mergePaths = options && 'mergeFields' in options;
  255. const ref = (0, helpers_1.validateDocumentReference)('documentRef', documentRef);
  256. let firestoreData;
  257. if (mergeLeaves || mergePaths) {
  258. firestoreData = ref._converter.toFirestore(data, options);
  259. }
  260. else {
  261. firestoreData = ref._converter.toFirestore(data);
  262. }
  263. validateDocumentData('data', firestoreData,
  264. /* allowDeletes= */ !!(mergePaths || mergeLeaves), this._allowUndefined);
  265. this.verifyNotCommitted();
  266. let documentMask;
  267. if (mergePaths) {
  268. documentMask = document_1.DocumentMask.fromFieldMask(options.mergeFields);
  269. firestoreData = documentMask.applyTo(firestoreData);
  270. }
  271. const transform = document_1.DocumentTransform.fromObject(ref, firestoreData);
  272. transform.validate();
  273. const op = () => {
  274. const document = document_1.DocumentSnapshot.fromObject(ref, firestoreData);
  275. if (mergePaths) {
  276. documentMask.removeFields(transform.fields);
  277. }
  278. else if (mergeLeaves) {
  279. documentMask = document_1.DocumentMask.fromObject(firestoreData);
  280. }
  281. const write = document.toWriteProto();
  282. if (!transform.isEmpty) {
  283. write.updateTransforms = transform.toProto(this._serializer);
  284. }
  285. if (mergePaths || mergeLeaves) {
  286. write.updateMask = documentMask.toProto();
  287. }
  288. return write;
  289. };
  290. this._ops.push({ docPath: documentRef.path, op });
  291. return this;
  292. }
  293. /**
  294. * Update fields of the document referred to by the provided
  295. * [DocumentReference]{@link DocumentReference}. If the document
  296. * doesn't yet exist, the update fails and the entire batch will be rejected.
  297. *
  298. * The update() method accepts either an object with field paths encoded as
  299. * keys and field values encoded as values, or a variable number of arguments
  300. * that alternate between field paths and field values. Nested fields can be
  301. * updated by providing dot-separated field path strings or by providing
  302. * FieldPath objects.
  303. *
  304. * A Precondition restricting this update can be specified as the last
  305. * argument.
  306. *
  307. * @param {DocumentReference} documentRef A reference to the document to be
  308. * updated.
  309. * @param {UpdateData|string|FieldPath} dataOrField An object
  310. * containing the fields and values with which to update the document
  311. * or the path of the first field to update.
  312. * @param {
  313. * ...(Precondition|*|string|FieldPath)} preconditionOrValues -
  314. * An alternating list of field paths and values to update or a Precondition
  315. * to restrict this update.
  316. * @throws {Error} If the provided input is not valid Firestore data.
  317. * @returns {WriteBatch} This WriteBatch instance. Used for chaining
  318. * method calls.
  319. *
  320. * @example
  321. * ```
  322. * let writeBatch = firestore.batch();
  323. * let documentRef = firestore.doc('col/doc');
  324. *
  325. * writeBatch.update(documentRef, {foo: 'bar'});
  326. *
  327. * writeBatch.commit().then(() => {
  328. * console.log('Successfully executed batch.');
  329. * });
  330. * ```
  331. */
  332. update(documentRef, dataOrField, ...preconditionOrValues) {
  333. // eslint-disable-next-line prefer-rest-params
  334. (0, validate_1.validateMinNumberOfArguments)('WriteBatch.update', arguments, 2);
  335. (0, helpers_1.validateDocumentReference)('documentRef', documentRef);
  336. this.verifyNotCommitted();
  337. const updateMap = new Map();
  338. let precondition = new document_1.Precondition({ exists: true });
  339. const argumentError = 'Update() requires either a single JavaScript ' +
  340. 'object or an alternating list of field/value pairs that can be ' +
  341. 'followed by an optional precondition.';
  342. const usesVarargs = typeof dataOrField === 'string' || dataOrField instanceof path_1.FieldPath;
  343. if (usesVarargs) {
  344. const argumentOffset = 1; // Respect 'documentRef' in the error message
  345. const fieldOrValues = [dataOrField, ...preconditionOrValues];
  346. try {
  347. for (let i = 0; i < fieldOrValues.length; i += 2) {
  348. if (i === fieldOrValues.length - 1) {
  349. const maybePrecondition = fieldOrValues[i];
  350. validateUpdatePrecondition(i + argumentOffset, maybePrecondition);
  351. precondition = new document_1.Precondition(maybePrecondition);
  352. }
  353. else {
  354. const maybeFieldPath = fieldOrValues[i];
  355. (0, path_1.validateFieldPath)(i + argumentOffset, maybeFieldPath);
  356. // Unlike the `validateMinNumberOfArguments` invocation above, this
  357. // validation can be triggered both from `WriteBatch.update()` and
  358. // `DocumentReference.update()`. Hence, we don't use the fully
  359. // qualified API name in the error message.
  360. (0, validate_1.validateMinNumberOfArguments)('update', fieldOrValues, i + 1);
  361. const fieldPath = path_1.FieldPath.fromArgument(maybeFieldPath);
  362. validateFieldValue(i + argumentOffset, fieldOrValues[i + 1], this._allowUndefined, fieldPath);
  363. updateMap.set(fieldPath, fieldOrValues[i + 1]);
  364. }
  365. }
  366. }
  367. catch (err) {
  368. (0, logger_1.logger)('WriteBatch.update', null, 'Varargs validation failed:', err);
  369. // We catch the validation error here and re-throw to provide a better
  370. // error message.
  371. throw new Error(`${argumentError} ${err.message}`);
  372. }
  373. }
  374. else {
  375. try {
  376. validateUpdateMap('dataOrField', dataOrField, this._allowUndefined);
  377. // eslint-disable-next-line prefer-rest-params
  378. (0, validate_1.validateMaxNumberOfArguments)('update', arguments, 3);
  379. Object.entries(dataOrField).forEach(([key, value]) => {
  380. // Skip `undefined` values (can be hit if `ignoreUndefinedProperties`
  381. // is set)
  382. if (value !== undefined) {
  383. (0, path_1.validateFieldPath)(key, key);
  384. updateMap.set(path_1.FieldPath.fromArgument(key), value);
  385. }
  386. });
  387. if (preconditionOrValues.length > 0) {
  388. validateUpdatePrecondition('preconditionOrValues', preconditionOrValues[0]);
  389. precondition = new document_1.Precondition(preconditionOrValues[0]);
  390. }
  391. }
  392. catch (err) {
  393. (0, logger_1.logger)('WriteBatch.update', null, 'Non-varargs validation failed:', err);
  394. // We catch the validation error here and prefix the error with a custom
  395. // message to describe the usage of update() better.
  396. throw new Error(`${argumentError} ${err.message}`);
  397. }
  398. }
  399. validateNoConflictingFields('dataOrField', updateMap);
  400. const transform = document_1.DocumentTransform.fromUpdateMap(documentRef, updateMap);
  401. transform.validate();
  402. const documentMask = document_1.DocumentMask.fromUpdateMap(updateMap);
  403. const op = () => {
  404. const document = document_1.DocumentSnapshot.fromUpdateMap(documentRef, updateMap);
  405. const write = document.toWriteProto();
  406. write.updateMask = documentMask.toProto();
  407. if (!transform.isEmpty) {
  408. write.updateTransforms = transform.toProto(this._serializer);
  409. }
  410. write.currentDocument = precondition.toProto();
  411. return write;
  412. };
  413. this._ops.push({ docPath: documentRef.path, op });
  414. return this;
  415. }
  416. /**
  417. * Atomically commits all pending operations to the database and verifies all
  418. * preconditions. Fails the entire write if any precondition is not met.
  419. *
  420. * @returns {Promise.<Array.<WriteResult>>} A Promise that resolves
  421. * when this batch completes.
  422. *
  423. * @example
  424. * ```
  425. * let writeBatch = firestore.batch();
  426. * let documentRef = firestore.doc('col/doc');
  427. *
  428. * writeBatch.set(documentRef, {foo: 'bar'});
  429. *
  430. * writeBatch.commit().then(() => {
  431. * console.log('Successfully executed batch.');
  432. * });
  433. * ```
  434. */
  435. commit() {
  436. return this._firestore._traceUtil.startActiveSpan(trace_util_1.SPAN_NAME_BATCH_COMMIT, async () => {
  437. // Capture the error stack to preserve stack tracing across async calls.
  438. const stack = Error().stack;
  439. // Commits should also be retried when they fail with status code ABORTED.
  440. const retryCodes = [10 /* StatusCode.ABORTED */, ...(0, util_1.getRetryCodes)('commit')];
  441. return this._commit({ retryCodes })
  442. .then(response => {
  443. return (response.writeResults || []).map(writeResult => new WriteResult(timestamp_1.Timestamp.fromProto(writeResult.updateTime || response.commitTime)));
  444. })
  445. .catch(err => {
  446. throw (0, util_1.wrapError)(err, stack);
  447. });
  448. }, {
  449. [trace_util_1.ATTRIBUTE_KEY_IS_TRANSACTIONAL]: false,
  450. [trace_util_1.ATTRIBUTE_KEY_DOC_COUNT]: this._opCount,
  451. });
  452. }
  453. /**
  454. * Commit method that takes an optional transaction ID.
  455. *
  456. * @private
  457. * @internal
  458. * @param commitOptions Options to use for this commit.
  459. * @param commitOptions.transactionId The transaction ID of this commit.
  460. * @param commitOptions.requestTag A unique client-assigned identifier for
  461. * this request.
  462. * @returns A Promise that resolves when this batch completes.
  463. */
  464. async _commit(commitOptions) {
  465. var _a;
  466. // Note: We don't call `verifyNotCommitted()` to allow for retries.
  467. this._committed = true;
  468. const tag = (_a = commitOptions === null || commitOptions === void 0 ? void 0 : commitOptions.requestTag) !== null && _a !== void 0 ? _a : (0, util_1.requestTag)();
  469. await this._firestore.initializeIfNeeded(tag);
  470. // Note that the request may not always be of type ICommitRequest. This is
  471. // just here to ensure type safety.
  472. const request = {
  473. database: this._firestore.formattedName,
  474. writes: this._ops.map(op => op.op()),
  475. };
  476. if (commitOptions === null || commitOptions === void 0 ? void 0 : commitOptions.transactionId) {
  477. request.transaction = commitOptions.transactionId;
  478. }
  479. (0, logger_1.logger)('WriteBatch.commit', tag, 'Sending %d writes', request.writes.length);
  480. return this._firestore.request((commitOptions === null || commitOptions === void 0 ? void 0 : commitOptions.methodName) || 'commit', request, tag, commitOptions === null || commitOptions === void 0 ? void 0 : commitOptions.retryCodes);
  481. }
  482. /**
  483. * Resets the WriteBatch and dequeues all pending operations.
  484. * @private
  485. * @internal
  486. */
  487. _reset() {
  488. this._ops.splice(0);
  489. this._committed = false;
  490. }
  491. }
  492. exports.WriteBatch = WriteBatch;
  493. /**
  494. * Validates the use of 'value' as a Precondition and enforces that 'exists'
  495. * and 'lastUpdateTime' use valid types.
  496. *
  497. * @private
  498. * @internal
  499. * @param arg The argument name or argument index (for varargs methods).
  500. * @param value The object to validate
  501. * @param options Options describing other things for this function to validate.
  502. */
  503. function validatePrecondition(arg, value, options) {
  504. if (typeof value !== 'object' || value === null) {
  505. throw new Error('Input is not an object.');
  506. }
  507. const precondition = value;
  508. let conditions = 0;
  509. if (precondition.exists !== undefined) {
  510. ++conditions;
  511. if (typeof precondition.exists !== 'boolean') {
  512. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'precondition')} "exists" is not a boolean.'`);
  513. }
  514. if ((options === null || options === void 0 ? void 0 : options.allowedExistsValues) &&
  515. options.allowedExistsValues.indexOf(precondition.exists) < 0) {
  516. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'precondition')} ` +
  517. `"exists" is not allowed to have the value ${precondition.exists} ` +
  518. `(allowed values: ${options.allowedExistsValues.join(', ')})`);
  519. }
  520. }
  521. if (precondition.lastUpdateTime !== undefined) {
  522. ++conditions;
  523. if (!(precondition.lastUpdateTime instanceof timestamp_1.Timestamp)) {
  524. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'precondition')} "lastUpdateTime" is not a Firestore Timestamp.`);
  525. }
  526. }
  527. if (conditions > 1) {
  528. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'precondition')} Input specifies more than one precondition.`);
  529. }
  530. }
  531. /**
  532. * Validates the use of 'value' as an update Precondition.
  533. *
  534. * @private
  535. * @internal
  536. * @param arg The argument name or argument index (for varargs methods).
  537. * @param value The object to validate.
  538. * @param options Optional validation options specifying whether the value can
  539. * be omitted.
  540. */
  541. function validateUpdatePrecondition(arg, value, options) {
  542. if (!(0, validate_1.validateOptional)(value, options)) {
  543. validatePrecondition(arg, value, { allowedExistsValues: [true] });
  544. }
  545. }
  546. /**
  547. * Validates the use of 'value' as a delete Precondition.
  548. *
  549. * @private
  550. * @internal
  551. * @param arg The argument name or argument index (for varargs methods).
  552. * @param value The object to validate.
  553. * @param options Optional validation options specifying whether the value can
  554. * be omitted.
  555. */
  556. function validateDeletePrecondition(arg, value, options) {
  557. if (!(0, validate_1.validateOptional)(value, options)) {
  558. validatePrecondition(arg, value);
  559. }
  560. }
  561. /**
  562. * Validates the use of 'value' as SetOptions and enforces that 'merge' is a
  563. * boolean.
  564. *
  565. * @private
  566. * @internal
  567. * @param arg The argument name or argument index (for varargs methods).
  568. * @param value The object to validate.
  569. * @param options Optional validation options specifying whether the value can
  570. * be omitted.
  571. * @throws if the input is not a valid SetOptions object.
  572. */
  573. function validateSetOptions(arg, value, options) {
  574. if (!(0, validate_1.validateOptional)(value, options)) {
  575. if (!(0, util_1.isObject)(value)) {
  576. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'set() options argument')} Input is not an object.`);
  577. }
  578. const setOptions = value;
  579. if ('mergeFields' in setOptions) {
  580. for (let i = 0; i < setOptions.mergeFields.length; ++i) {
  581. try {
  582. (0, path_1.validateFieldPath)(i, setOptions.mergeFields[i]);
  583. }
  584. catch (err) {
  585. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'set() options argument')} "mergeFields" is not valid: ${err.message}`);
  586. }
  587. }
  588. }
  589. if ('merge' in setOptions && 'mergeFields' in setOptions) {
  590. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'set() options argument')} You cannot specify both "merge" and "mergeFields".`);
  591. }
  592. }
  593. }
  594. /**
  595. * Validates a JavaScript object for usage as a Firestore document.
  596. *
  597. * @private
  598. * @internal
  599. * @param arg The argument name or argument index (for varargs methods).
  600. * @param obj JavaScript object to validate.
  601. * @param allowDeletes Whether to allow FieldValue.delete() sentinels.
  602. * @param allowUndefined Whether to allow nested properties that are `undefined`.
  603. * @throws when the object is invalid.
  604. */
  605. function validateDocumentData(arg, obj, allowDeletes, allowUndefined) {
  606. if (!(0, util_1.isPlainObject)(obj)) {
  607. throw new Error((0, validate_1.customObjectMessage)(arg, obj));
  608. }
  609. (0, serializer_1.validateUserInput)(arg, obj, 'Firestore document', {
  610. allowDeletes: allowDeletes ? 'all' : 'none',
  611. allowTransforms: true,
  612. allowUndefined,
  613. });
  614. }
  615. /**
  616. * Validates that a value can be used as field value during an update.
  617. *
  618. * @private
  619. * @internal
  620. * @param arg The argument name or argument index (for varargs methods).
  621. * @param val The value to verify.
  622. * @param allowUndefined Whether to allow nested properties that are `undefined`.
  623. * @param path The path to show in the error message.
  624. */
  625. function validateFieldValue(arg, val, allowUndefined, path) {
  626. (0, serializer_1.validateUserInput)(arg, val, 'Firestore value', { allowDeletes: 'root', allowTransforms: true, allowUndefined }, path);
  627. }
  628. /**
  629. * Validates that the update data does not contain any ambiguous field
  630. * definitions (such as 'a.b' and 'a').
  631. *
  632. * @private
  633. * @internal
  634. * @param arg The argument name or argument index (for varargs methods).
  635. * @param data An update map with field/value pairs.
  636. */
  637. function validateNoConflictingFields(arg, data) {
  638. const fields = [];
  639. data.forEach((value, key) => {
  640. fields.push(key);
  641. });
  642. fields.sort((left, right) => left.compareTo(right));
  643. for (let i = 1; i < fields.length; ++i) {
  644. if (fields[i - 1].isPrefixOf(fields[i])) {
  645. throw new Error(`${(0, validate_1.invalidArgumentMessage)(arg, 'update map')} Field "${fields[i - 1]}" was specified multiple times.`);
  646. }
  647. }
  648. }
  649. /**
  650. * Validates that a JavaScript object is a map of field paths to field values.
  651. *
  652. * @private
  653. * @internal
  654. * @param arg The argument name or argument index (for varargs methods).
  655. * @param obj JavaScript object to validate.
  656. * @param allowUndefined Whether to allow nested properties that are `undefined`.
  657. * @throws when the object is invalid.
  658. */
  659. function validateUpdateMap(arg, obj, allowUndefined) {
  660. if (!(0, util_1.isPlainObject)(obj)) {
  661. throw new Error((0, validate_1.customObjectMessage)(arg, obj));
  662. }
  663. if (Object.keys(obj).length === 0) {
  664. throw new Error('At least one field must be updated.');
  665. }
  666. validateFieldValue(arg, obj, allowUndefined);
  667. }
  668. //# sourceMappingURL=write-batch.js.map